Skip to content

Commit

Permalink
Add parrot analytics library
Browse files Browse the repository at this point in the history
  • Loading branch information
dleutenegger committed Nov 8, 2023
1 parent 2a0885d commit 6cbc022
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ members = [
"graphql",
"honey-badger",
"mole",
"parrot",
]
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ and always returns the latest state of them when requested.
## Crow
Crows are known for their love to collect stuff.
The library allows to register for and list withdraw collect offers (e.g. Lightning Address).

## Parrot
This feathered companion, similar to its namesakes, repeats what he hears and as such delivers,
important analytics data about payments to the backend. This data is pseudonymized and used to improve our services.
4 changes: 4 additions & 0 deletions graphql/schemas/operations.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -172,3 +172,7 @@ query MigrationBalance($nodePubKey: String) {
mutation MigrateFunds($invoice: String, $base16InvoiceSignature: String, $ldkNodePubKey: String) {
migrate_funds(invoice: $invoice, base16InvoiceSignature: $base16InvoiceSignature, ldkNodePubKey: $ldkNodePubKey)
}

mutation ReportPaymentTelemetry($telemetryId: String!, $events: PaymentTelemetryEventsInput) {
report_payment_telemetry(telemetryId: $telemetryId, events: $events)
}
70 changes: 70 additions & 0 deletions graphql/schemas/schema_wallet_read.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -751,6 +751,9 @@ input float8_comparison_exp {

"""mutation root"""
type mutation_root {

report_payment_telemetry(telemetryId: String!, events: PaymentTelemetryEventsInput): ReportPaymentTelemetryResponse

"""
execute VOLATILE function "accept_terms" which returns "accepted_terms_conditions"
"""
Expand Down Expand Up @@ -1787,3 +1790,70 @@ input wallet_acl_stream_cursor_value_input {
ownerWalletPubKeyId: uuid
role: String
}

type ReportPaymentTelemetryResponse {
payFailed: String
payInitiated: String
paySucceeded: String
requestInitiated: String
requestSucceeded: String
}

enum PayFailureReason {
NO_ROUTE
UNKNOWN
}

enum PaySource {
CAMERA
CLIPBOARD
NFC
MANUAL
}

input PayFailedInput {
failedAt: DateTime!
paymentHash: String!
reason: PayFailureReason!
}

input PayInitiatedInput {
processStartedAt: DateTime!
executedAt: DateTime!
paidAmountMSat: BigInteger!
paymentHash: String!
requestedAmountMSat: BigInteger!
satsPerUserCurrency: Int!
source: PaySource!
userCurrency: String!
}

input PaySucceededInput {
confirmedAt: DateTime!
lnFeesPaidMSat: BigInteger!
paymentHash: String!
}

input PaymentTelemetryEventsInput {
payFailed: PayFailedInput
payInitiated: PayInitiatedInput
paySucceeded: PaySucceededInput
requestInitiated: RequestInitiatedInput
requestSucceeded: RequestSucceededInput
}

input RequestInitiatedInput {
createdAt: DateTime!
enteredAmountMSat: BigInteger!
paymentHash: String!
requestCurrency: String!
satsPerUserCurrency: Int!
userCurrency: String!
}

input RequestSucceededInput {
channelOpeningFeeMSat: BigInteger!
paidAmountMSat: BigInteger!
paymentHash: String!
paymentReceivedAt: DateTime!
}
11 changes: 11 additions & 0 deletions graphql/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub use crate::errors::*;
pub use perro;
pub use reqwest;

use chrono::DateTime;
use graphql_client::reqwest::post_graphql_blocking;
use graphql_client::Response;
use perro::{permanent_failure, runtime_error, MapToError, OptionToError};
Expand Down Expand Up @@ -159,3 +160,13 @@ mod tests {
assert_eq!(timestamp, 1695314361 + 2 * 3600);
}
}

pub trait ToRfc3339 {
fn to_rfc3339(&self) -> String;
}

impl ToRfc3339 for SystemTime {
fn to_rfc3339(&self) -> String {
DateTime::from(self.clone()).to_rfc3339()
}
}
10 changes: 10 additions & 0 deletions graphql/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ type numeric = u32;
#[allow(non_camel_case_types)]
type timestamptz = String;
#[allow(non_camel_case_types)]
type DateTime = String;
#[allow(non_camel_case_types)]
type uuid = String;

#[derive(GraphQLQuery)]
Expand Down Expand Up @@ -190,3 +192,11 @@ pub struct MigrationBalance;
response_derives = "Debug"
)]
pub struct MigrateFunds;

#[derive(GraphQLQuery)]
#[graphql(
schema_path = "schemas/schema_wallet_read.graphql",
query_path = "schemas/operations.graphql",
response_derives = "Debug"
)]
pub struct ReportPaymentTelemetry;
14 changes: 14 additions & 0 deletions parrot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "parrot"
version = "0.1.0"
edition = "2021"

[dependencies]
hex = "0.4.3"
log = "0.4.17"

graphql = { path = "../graphql" }
honey-badger = { path = "../honey-badger" }

[dev-dependencies]
simplelog = { version ="0.12.0", features = ["test"] }
220 changes: 220 additions & 0 deletions parrot/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
use graphql::schema::report_payment_telemetry::{
PayFailedInput, PayInitiatedInput, PaySource, PaySucceededInput, PaymentTelemetryEventsInput,
RequestInitiatedInput, RequestSucceededInput,
};
use graphql::schema::{report_payment_telemetry, ReportPaymentTelemetry};
use graphql::{build_client, post_blocking, ToRfc3339};
use honey_badger::Auth;
use std::sync::Arc;
use std::time::SystemTime;

pub enum PaymentSource {
Camera,
Clipboard,
Nfc,
Manual,
}
pub enum PayFailureReason {
NoRoute,
Unkown,
}

pub enum AnalyticsEvent {
PayInitiated {
payment_hash: String,

paid_amount_msat: u64,
requested_amount_msat: Option<u64>,
sats_per_user_currency: Option<u32>,

source: PaymentSource,
user_currency: String,

process_started_at: SystemTime,
executed_at: SystemTime,
},
PaySucceeded {
payment_hash: String,

ln_fees_paid_msat: u64,
confirmed_at: SystemTime,
},
PayFailed {
payment_hash: String,

reason: PayFailureReason,
failed_at: SystemTime,
},
RequestInitiated {
payment_hash: String,

entered_amount_msat: Option<u64>,
sats_per_user_currency: Option<u32>,

user_currency: String,
request_currency: String,

created_at: SystemTime,
},
RequestSucceeded {
payment_hash: String,

paid_amount_sat: u64,
channel_opening_fee_msat: u64,

received_at: SystemTime,
},
}

pub struct AnalyticsClient {
backend_url: String,
analytics_id: String,
auth: Arc<Auth>,
}

impl AnalyticsClient {
pub fn new(backend_url: String, analytics_id: String, auth: Arc<Auth>) -> Self {
Self {
backend_url,
analytics_id,
auth,
}
}

pub fn report_event(&self, analytics_event: AnalyticsEvent) -> graphql::Result<()> {
let variables = match analytics_event {
AnalyticsEvent::PayInitiated {
payment_hash,
paid_amount_msat,
requested_amount_msat,
sats_per_user_currency,
source,
user_currency,
process_started_at,
executed_at,
} => report_payment_telemetry::Variables {
telemetry_id: self.analytics_id.clone(),
events: Some(PaymentTelemetryEventsInput {
pay_failed: None,
pay_initiated: Some(PayInitiatedInput {
process_started_at: process_started_at.to_rfc3339(),
executed_at: executed_at.to_rfc3339(),
paid_amount_m_sat: paid_amount_msat,
payment_hash,
requested_amount_m_sat: requested_amount_msat.unwrap_or(0),
sats_per_user_currency: sats_per_user_currency.unwrap_or(0) as i64,
source: map_payment_source(source),
user_currency,
}),
pay_succeeded: None,
request_initiated: None,
request_succeeded: None,
}),
},
AnalyticsEvent::PaySucceeded {
payment_hash,
ln_fees_paid_msat,
confirmed_at,
} => report_payment_telemetry::Variables {
telemetry_id: self.analytics_id.clone(),
events: Some(PaymentTelemetryEventsInput {
pay_failed: None,
pay_initiated: None,
pay_succeeded: Some(PaySucceededInput {
confirmed_at: confirmed_at.to_rfc3339(),
ln_fees_paid_m_sat: ln_fees_paid_msat,
payment_hash,
}),
request_initiated: None,
request_succeeded: None,
}),
},
AnalyticsEvent::PayFailed {
payment_hash,
reason,
failed_at,
} => report_payment_telemetry::Variables {
telemetry_id: self.analytics_id.clone(),
events: Some(PaymentTelemetryEventsInput {
pay_failed: Some(PayFailedInput {
failed_at: failed_at.to_rfc3339(),
payment_hash,
reason: map_pay_failure_reason(reason),
}),
pay_initiated: None,
pay_succeeded: None,
request_initiated: None,
request_succeeded: None,
}),
},
AnalyticsEvent::RequestInitiated {
payment_hash,
entered_amount_msat,
sats_per_user_currency,
user_currency,
request_currency,
created_at,
} => report_payment_telemetry::Variables {
telemetry_id: self.analytics_id.clone(),
events: Some(PaymentTelemetryEventsInput {
pay_failed: None,
pay_initiated: None,
pay_succeeded: None,
request_initiated: Some(RequestInitiatedInput {
created_at: created_at.to_rfc3339(),
entered_amount_m_sat: entered_amount_msat.unwrap_or(0),
payment_hash,
request_currency,
sats_per_user_currency: sats_per_user_currency.unwrap_or(0) as i64,
user_currency,
}),
request_succeeded: None,
}),
},
AnalyticsEvent::RequestSucceeded {
payment_hash,
paid_amount_sat,
channel_opening_fee_msat,
received_at,
} => report_payment_telemetry::Variables {
telemetry_id: self.analytics_id.clone(),
events: Some(PaymentTelemetryEventsInput {
pay_failed: None,
pay_initiated: None,
pay_succeeded: None,
request_initiated: None,
request_succeeded: Some(RequestSucceededInput {
channel_opening_fee_m_sat: channel_opening_fee_msat,
paid_amount_m_sat: paid_amount_sat,
payment_hash,
payment_received_at: received_at.to_rfc3339(),
}),
}),
},
};

let token = self.auth.query_token()?;
let client = build_client(Some(&token))?;
post_blocking::<ReportPaymentTelemetry>(&client, &self.backend_url, variables)?;

Ok(())
}
}

fn map_payment_source(payment_source: PaymentSource) -> PaySource {
match payment_source {
PaymentSource::Camera => PaySource::CAMERA,
PaymentSource::Clipboard => PaySource::CLIPBOARD,
PaymentSource::Nfc => PaySource::NFC,
PaymentSource::Manual => PaySource::MANUAL,
}
}

fn map_pay_failure_reason(
pay_failure_reason: PayFailureReason,
) -> report_payment_telemetry::PayFailureReason {
match pay_failure_reason {
PayFailureReason::NoRoute => report_payment_telemetry::PayFailureReason::NO_ROUTE,
PayFailureReason::Unkown => report_payment_telemetry::PayFailureReason::UNKNOWN,
}
}

0 comments on commit 6cbc022

Please sign in to comment.