From 9ed6706630febeb9114d94a72b27423e511f5603 Mon Sep 17 00:00:00 2001 From: Hamish Peebles Date: Thu, 18 Jul 2024 08:51:13 +0100 Subject: [PATCH] Expire old BTC Miami referral codes (#6053) --- .../canisters/local_user_index/CHANGELOG.md | 4 + .../impl/src/jobs/make_btc_miami_payments.rs | 125 ------------------ .../local_user_index/impl/src/jobs/mod.rs | 2 - .../local_user_index/impl/src/lib.rs | 8 +- .../impl/src/lifecycle/post_upgrade.rs | 9 +- .../src/model/btc_miami_payments_queue.rs | 30 ----- .../local_user_index/impl/src/model/mod.rs | 1 - .../impl/src/model/referral_codes.rs | 14 +- .../impl/src/updates/register_user.rs | 34 +---- 9 files changed, 30 insertions(+), 197 deletions(-) delete mode 100644 backend/canisters/local_user_index/impl/src/jobs/make_btc_miami_payments.rs delete mode 100644 backend/canisters/local_user_index/impl/src/model/btc_miami_payments_queue.rs diff --git a/backend/canisters/local_user_index/CHANGELOG.md b/backend/canisters/local_user_index/CHANGELOG.md index 11b9bd0927..946d8b1aba 100644 --- a/backend/canisters/local_user_index/CHANGELOG.md +++ b/backend/canisters/local_user_index/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [unreleased] +### Changed + +- Expire old BTC Miami referral codes ([#6053](https://github.com/open-chat-labs/open-chat/pull/6053)) + ## [[2.0.1241](https://github.com/open-chat-labs/open-chat/releases/tag/v2.0.1241-local_user_index)] - 2024-07-17 ### Added diff --git a/backend/canisters/local_user_index/impl/src/jobs/make_btc_miami_payments.rs b/backend/canisters/local_user_index/impl/src/jobs/make_btc_miami_payments.rs deleted file mode 100644 index dd97e9558d..0000000000 --- a/backend/canisters/local_user_index/impl/src/jobs/make_btc_miami_payments.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::model::btc_miami_payments_queue::PendingPayment; -use crate::{mutate_state, RuntimeState}; -use candid::Principal; -use ic_cdk_timers::TimerId; -use icrc_ledger_types::icrc1::account::Account; -use icrc_ledger_types::icrc1::transfer::{BlockIndex, TransferArg}; -use std::cell::Cell; -use std::time::Duration; -use tracing::{error, trace}; -use types::{ - icrc1, CompletedCryptoTransaction, CryptoContent, CryptoTransaction, Cryptocurrency, CustomContent, MessageContent, - TextContent, -}; -use utils::consts::OPENCHAT_BOT_USER_ID; - -thread_local! { - static TIMER_ID: Cell> = Cell::default(); -} - -pub(crate) fn start_job_if_required(state: &RuntimeState) -> bool { - if TIMER_ID.get().is_none() && !state.data.btc_miami_payments_queue.is_empty() { - let timer_id = ic_cdk_timers::set_timer_interval(Duration::ZERO, run); - TIMER_ID.set(Some(timer_id)); - trace!("'make_btc_miami_payments' job started"); - true - } else { - false - } -} - -pub fn run() { - if let Some(pending_payment) = mutate_state(|state| state.data.btc_miami_payments_queue.pop()) { - ic_cdk::spawn(process_payment(pending_payment)); - } else if let Some(timer_id) = TIMER_ID.take() { - ic_cdk_timers::clear_timer(timer_id); - trace!("'make_btc_miami_payments' job stopped"); - } -} - -async fn process_payment(pending_payment: PendingPayment) { - match make_payment(&pending_payment).await { - Ok(block_index) => { - mutate_state(|state| send_oc_bot_messages(&pending_payment, block_index, state)); - } - Err(_) => { - mutate_state(|state| { - state.data.btc_miami_payments_queue.push(pending_payment); - start_job_if_required(state); - }); - } - } -} - -async fn make_payment(pending_payment: &PendingPayment) -> Result { - let to = Account::from(pending_payment.recipient); - - let args = TransferArg { - from_subaccount: None, - to, - fee: None, - created_at_time: Some(pending_payment.timestamp), - memo: None, - amount: pending_payment.amount.into(), - }; - - match icrc_ledger_canister_c2c_client::icrc1_transfer(Cryptocurrency::CKBTC.ledger_canister_id().unwrap(), &args).await { - Ok(Ok(block_index)) => return Ok(block_index), - Ok(Err(transfer_error)) => error!("Transfer failed. {transfer_error:?}"), - Err((code, msg)) => error!("Transfer failed. {code:?}: {msg}"), - } - - Err(()) -} - -fn send_oc_bot_messages(pending_payment: &PendingPayment, block_index: BlockIndex, state: &mut RuntimeState) { - let user_id = pending_payment.recipient.into(); - let amount = pending_payment.amount as u128; - - let messages = vec![ - MessageContent::Crypto(CryptoContent { - recipient: user_id, - transfer: CryptoTransaction::Completed(CompletedCryptoTransaction::ICRC1(icrc1::CompletedCryptoTransaction { - ledger: Cryptocurrency::CKBTC.ledger_canister_id().unwrap(), - token: Cryptocurrency::CKBTC, - amount, - fee: 10, - from: Account::from(Principal::from(OPENCHAT_BOT_USER_ID)).into(), - to: Account::from(Principal::from(user_id)).into(), - memo: None, - created: pending_payment.timestamp, - block_index: block_index.0.try_into().unwrap(), - })), - caption: Some("Here are your 50,000 SATS as [ckBTC](https://internetcomputer.org/ckbtc)!".to_string()), - }), - MessageContent::Text(TextContent { - text: "🤔 No one to send your ckBTC to? Invite your friends to chat!".to_string(), - }), - MessageContent::Custom(CustomContent { - kind: "user_referral_card".to_string(), - data: Vec::new(), - }), - MessageContent::Text(TextContent { - text: format!( - "🤝 ...or connect with fellow Bitcoiners and win prizes in the [Operation Miami](/{}) chat", - if state.data.test_mode { "ueyan-5iaaa-aaaaf-bifxa-cai" } else { "pbo6v-oiaaa-aaaar-ams6q-cai" } - ), - }), - MessageContent::Text(TextContent { - text: format!( - "🎲 ...or play Satoshi Dice with the [Satoshi Dice](/{}) chat bot", - if state.data.test_mode { "uuw5d-uiaaa-aaaar-anzeq-cai" } else { "wznbi-caaaa-aaaar-anvea-cai" } - ), - }), - MessageContent::Text(TextContent { - text: "👀 View projects, wallets, and DEXs that support ckBTC [here](https://internetcomputer.org/ecosystem/?tag=Bitcoin)".to_string(), - }), - MessageContent::Text(TextContent { - text: "🧐 Find out more about OpenChat [here](/home)".to_string(), - }), - ]; - - for message in messages { - state.push_oc_bot_message_to_user(user_id, message, Vec::new()); - } -} diff --git a/backend/canisters/local_user_index/impl/src/jobs/mod.rs b/backend/canisters/local_user_index/impl/src/jobs/mod.rs index d7dc81fd3d..0313b2eed6 100644 --- a/backend/canisters/local_user_index/impl/src/jobs/mod.rs +++ b/backend/canisters/local_user_index/impl/src/jobs/mod.rs @@ -1,7 +1,6 @@ use crate::RuntimeState; pub mod delete_users; -pub mod make_btc_miami_payments; pub mod sync_events_to_user_canisters; pub mod sync_events_to_user_index_canister; pub mod topup_canister_pool; @@ -9,7 +8,6 @@ pub mod upgrade_canisters; pub(crate) fn start(state: &RuntimeState) { delete_users::start_job_if_required(state, None); - make_btc_miami_payments::start_job_if_required(state); sync_events_to_user_canisters::start_job_if_required(state); sync_events_to_user_index_canister::start_job_if_required(state); topup_canister_pool::start_job_if_required(state); diff --git a/backend/canisters/local_user_index/impl/src/lib.rs b/backend/canisters/local_user_index/impl/src/lib.rs index 6bbb513e76..844c313305 100644 --- a/backend/canisters/local_user_index/impl/src/lib.rs +++ b/backend/canisters/local_user_index/impl/src/lib.rs @@ -1,4 +1,3 @@ -use crate::model::btc_miami_payments_queue::BtcMiamiPaymentsQueue; use crate::model::referral_codes::{ReferralCodes, ReferralTypeMetrics}; use crate::timer_job_types::TimerJob; use candid::Principal; @@ -165,6 +164,7 @@ impl RuntimeState { } pub fn metrics(&self) -> Metrics { + let now = self.env.now(); let canister_upgrades_metrics = self.data.canisters_requiring_upgrade.metrics(); let event_store_client_info = self.data.event_store_client.info(); let event_relay_canister_id = event_store_client_info.event_store_canister_id; @@ -172,7 +172,7 @@ impl RuntimeState { Metrics { heap_memory_used: utils::memory::heap(), stable_memory_used: utils::memory::stable(), - now: self.env.now(), + now, cycles_balance: self.env.cycles_balance(), wasm_version: WASM_VERSION.with_borrow(|v| **v), git_commit_id: utils::git::git_commit_id().to_string(), @@ -189,7 +189,7 @@ impl RuntimeState { user_upgrade_concurrency: self.data.user_upgrade_concurrency, user_events_queue_length: self.data.user_event_sync_queue.len(), users_to_delete_queue_length: self.data.users_to_delete_queue.len(), - referral_codes: self.data.referral_codes.metrics(), + referral_codes: self.data.referral_codes.metrics(now), event_store_client_info, canister_ids: CanisterIds { user_index: self.data.user_index_canister_id, @@ -230,7 +230,6 @@ struct Data { pub platform_moderators_group: Option, pub referral_codes: ReferralCodes, pub timer_jobs: TimerJobs, - pub btc_miami_payments_queue: BtcMiamiPaymentsQueue, pub rng_seed: [u8; 32], pub video_call_operators: Vec, pub oc_secret_key_der: Option>, @@ -292,7 +291,6 @@ impl Data { platform_moderators_group: None, referral_codes: ReferralCodes::default(), timer_jobs: TimerJobs::default(), - btc_miami_payments_queue: BtcMiamiPaymentsQueue::default(), rng_seed: [0; 32], video_call_operators, oc_secret_key_der, diff --git a/backend/canisters/local_user_index/impl/src/lifecycle/post_upgrade.rs b/backend/canisters/local_user_index/impl/src/lifecycle/post_upgrade.rs index 1529c2593a..e14a4f25fa 100644 --- a/backend/canisters/local_user_index/impl/src/lifecycle/post_upgrade.rs +++ b/backend/canisters/local_user_index/impl/src/lifecycle/post_upgrade.rs @@ -1,11 +1,12 @@ use crate::lifecycle::{init_env, init_state}; use crate::memory::get_upgrades_memory; -use crate::Data; +use crate::{mutate_state, Data}; use canister_logger::LogEntry; use canister_tracing_macros::trace; use ic_cdk::post_upgrade; use local_user_index_canister::post_upgrade::Args; use stable_memory::get_reader; +use std::time::Duration; use tracing::info; use utils::cycles::init_cycles_dispenser_client; @@ -24,4 +25,10 @@ fn post_upgrade(args: Args) { init_state(env, data, args.wasm_version); info!(version = %args.wasm_version, "Post-upgrade complete"); + + ic_cdk_timers::set_timer(Duration::from_secs(600), || { + mutate_state(|state| { + state.data.referral_codes.set_expired(state.env.now()); + }); + }); } diff --git a/backend/canisters/local_user_index/impl/src/model/btc_miami_payments_queue.rs b/backend/canisters/local_user_index/impl/src/model/btc_miami_payments_queue.rs deleted file mode 100644 index 06dc321a0c..0000000000 --- a/backend/canisters/local_user_index/impl/src/model/btc_miami_payments_queue.rs +++ /dev/null @@ -1,30 +0,0 @@ -use candid::Principal; -use serde::{Deserialize, Serialize}; -use std::collections::VecDeque; -use types::TimestampNanos; - -#[derive(Serialize, Deserialize, Default)] -pub struct BtcMiamiPaymentsQueue { - pending_payments: VecDeque, -} - -impl BtcMiamiPaymentsQueue { - pub fn push(&mut self, pending_payment: PendingPayment) { - self.pending_payments.push_back(pending_payment); - } - - pub fn pop(&mut self) -> Option { - self.pending_payments.pop_front() - } - - pub fn is_empty(&self) -> bool { - self.pending_payments.is_empty() - } -} - -#[derive(Serialize, Deserialize)] -pub struct PendingPayment { - pub amount: u64, - pub timestamp: TimestampNanos, - pub recipient: Principal, -} diff --git a/backend/canisters/local_user_index/impl/src/model/mod.rs b/backend/canisters/local_user_index/impl/src/model/mod.rs index eb57d23985..ebbe4a6e9d 100644 --- a/backend/canisters/local_user_index/impl/src/model/mod.rs +++ b/backend/canisters/local_user_index/impl/src/model/mod.rs @@ -1,4 +1,3 @@ -pub mod btc_miami_payments_queue; pub mod global_user_map; pub mod local_user_map; pub mod referral_codes; diff --git a/backend/canisters/local_user_index/impl/src/model/referral_codes.rs b/backend/canisters/local_user_index/impl/src/model/referral_codes.rs index f119b38f89..1753043f34 100644 --- a/backend/canisters/local_user_index/impl/src/model/referral_codes.rs +++ b/backend/canisters/local_user_index/impl/src/model/referral_codes.rs @@ -23,6 +23,14 @@ pub struct ReferralCodes { codes: HashMap, } +impl ReferralCodes { + pub fn set_expired(&mut self, now: TimestampMillis) { + for code in self.codes.values_mut() { + code.expiry = Some(now); + } + } +} + #[derive(Serialize, Deserialize)] pub struct ReferralCodeDetails { referral_type: ReferralType, @@ -40,6 +48,7 @@ pub struct ReferralCodeClaim { #[derive(Serialize, Debug, Default)] pub struct ReferralTypeMetrics { pub claimed: usize, + pub expired: usize, pub total: usize, } @@ -71,6 +80,7 @@ impl ReferralCodes { } } + #[allow(dead_code)] pub fn claim(&mut self, code: String, user_id: UserId, now: TimestampMillis) -> bool { match self.codes.entry(code) { Entry::Occupied(mut e) => { @@ -107,7 +117,7 @@ impl ReferralCodes { } } - pub fn metrics(&self) -> HashMap { + pub fn metrics(&self, now: TimestampMillis) -> HashMap { let mut metrics = HashMap::new(); for details in self.codes.values() { @@ -115,6 +125,8 @@ impl ReferralCodes { ms.total += 1; if details.claimed.is_some() { ms.claimed += 1; + } else if details.expiry.is_some_and(|ts| ts < now) { + ms.expired += 1; } } diff --git a/backend/canisters/local_user_index/impl/src/updates/register_user.rs b/backend/canisters/local_user_index/impl/src/updates/register_user.rs index 8017f5f536..a284d60dc9 100644 --- a/backend/canisters/local_user_index/impl/src/updates/register_user.rs +++ b/backend/canisters/local_user_index/impl/src/updates/register_user.rs @@ -1,6 +1,4 @@ -use crate::model::btc_miami_payments_queue::PendingPayment; use crate::model::referral_codes::{ReferralCode, ReferralCodeError}; -use crate::timer_job_types::{AddUserToSatoshiDice, TimerJob}; use crate::{mutate_state, RuntimeState, USER_CANISTER_INITIAL_CYCLES_BALANCE}; use candid::Principal; use canister_tracing_macros::trace; @@ -10,7 +8,7 @@ use local_user_index_canister::register_user::{Response::*, *}; use types::{BuildVersion, CanisterId, CanisterWasm, Cycles, MessageContentInitial, TextContent, UserId}; use user_canister::init::Args as InitUserCanisterArgs; use user_canister::{Event as UserEvent, ReferredUserRegistered}; -use user_index_canister::{Event as UserIndexEvent, JoinUserToGroup, UserRegistered}; +use user_index_canister::{Event as UserIndexEvent, UserRegistered}; use utils::canister; use utils::canister::CreateAndInstallError; use utils::consts::{min_cycles_balance, CREATE_CANISTER_CYCLES_FEE}; @@ -212,35 +210,7 @@ fn commit( ); } } - Some(ReferralCode::BtcMiami(code)) => { - let test_mode = state.data.test_mode; - - // This referral code can only be used once so claim it - state.data.referral_codes.claim(code, user_id, now); - - state.data.btc_miami_payments_queue.push(PendingPayment { - amount: if test_mode { 50 } else { 50_000 }, // Approx $14 - timestamp: state.env.now_nanos(), - recipient: user_id.into(), - }); - crate::jobs::make_btc_miami_payments::start_job_if_required(state); - - let btc_miami_group = - Principal::from_text(if test_mode { "ueyan-5iaaa-aaaaf-bifxa-cai" } else { "pbo6v-oiaaa-aaaar-ams6q-cai" }) - .unwrap() - .into(); - - state.push_event_to_user_index(UserIndexEvent::JoinUserToGroup(Box::new(JoinUserToGroup { - user_id, - chat_id: btc_miami_group, - }))); - state.data.timer_jobs.enqueue_job( - TimerJob::AddUserToSatoshiDice(AddUserToSatoshiDice { user_id, attempt: 0 }), - now, - now, - ) - } - None => {} + Some(ReferralCode::BtcMiami(_)) | None => {} } }