Skip to content

Commit

Permalink
chore: improve UTXOs burning task
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Jun 30, 2024
1 parent 3817dda commit ef746a0
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 91 deletions.
9 changes: 8 additions & 1 deletion src/ck-doge-minter/ck-doge-minter.did
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type BurnInput = record { fee_rate : nat64; address : text; amount : nat64 };
type BurnOutput = record {
tip_height : nat64;
block_index : nat64;
txid : blob;
instructions : nat64;
};
Expand Down Expand Up @@ -37,10 +38,15 @@ type Result_3 = variant { Ok : State; Err };
type Result_4 = variant { Ok : vec MintedUtxo; Err : text };
type Result_5 = variant { Ok : MintOutput; Err : text };
type State = record {
tokens_minted_count : nat64;
ecdsa_key_name : opt text;
managers : vec principal;
burning_utxos : vec record {
nat64;
record { principal; blob; nat64; nat64; text };
};
chain : text;
utxos_retry_burning_queue : vec record { nat64; blob; nat64; nat64; nat8 };
tokens_burned_count : nat64;
collected_utxos : nat64;
tokens_burned : nat64;
accounts : nat64;
Expand All @@ -66,5 +72,6 @@ service : (opt MinterArgs) -> {
list_collected_utxos : (nat64, nat16) -> (vec CollectedUtxo) query;
list_minted_utxos : (opt principal) -> (Result_4) query;
mint_ckdoge : () -> (Result_5);
retry_burn_ckdoge : (nat64, opt nat64) -> (Result_1);
validate_admin_set_managers : (vec principal) -> (Result);
}
18 changes: 9 additions & 9 deletions src/ck-doge-minter/src/api_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use candid::{CandidType, Principal};
use serde::Deserialize;
use std::time::Duration;

use crate::{job, store};
use crate::{store, task};

#[derive(Clone, Debug, CandidType, Deserialize)]
pub enum MinterArgs {
Expand Down Expand Up @@ -40,12 +40,12 @@ fn init(args: Option<MinterArgs>) {
ic_cdk::spawn(store::state::init_ecdsa_public_key())
});

ic_cdk_timers::set_timer_interval(Duration::from_secs(job::FINALIZE_BURNING_INTERVAL), || {
ic_cdk::spawn(job::finalize_burning())
ic_cdk_timers::set_timer_interval(Duration::from_secs(task::FINALIZE_BURNING_INTERVAL), || {
ic_cdk::spawn(task::finalize_burning())
});

ic_cdk_timers::set_timer_interval(Duration::from_secs(job::CLEAR_UTXOS_INTERVAL), || {
ic_cdk::spawn(job::collect_and_clear_utxos())
ic_cdk_timers::set_timer_interval(Duration::from_secs(task::CLEAR_UTXOS_INTERVAL), || {
ic_cdk::spawn(task::collect_and_clear_utxos())
});
}

Expand Down Expand Up @@ -83,11 +83,11 @@ fn post_upgrade(args: Option<MinterArgs>) {
_ => {}
}

ic_cdk_timers::set_timer_interval(Duration::from_secs(job::FINALIZE_BURNING_INTERVAL), || {
ic_cdk::spawn(job::finalize_burning())
ic_cdk_timers::set_timer_interval(Duration::from_secs(task::FINALIZE_BURNING_INTERVAL), || {
ic_cdk::spawn(task::finalize_burning())
});

ic_cdk_timers::set_timer_interval(Duration::from_secs(job::CLEAR_UTXOS_INTERVAL), || {
ic_cdk::spawn(job::collect_and_clear_utxos())
ic_cdk_timers::set_timer_interval(Duration::from_secs(task::CLEAR_UTXOS_INTERVAL), || {
ic_cdk::spawn(task::collect_and_clear_utxos())
});
}
10 changes: 7 additions & 3 deletions src/ck-doge-minter/src/api_query.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use candid::{CandidType, Principal};
use dogecoin::canister;
use std::collections::BTreeSet;
use std::collections::{BTreeMap, BTreeSet};

use crate::{
is_authenticated, is_controller_or_manager, minter_account, store, types, user_account,
Expand All @@ -17,6 +17,8 @@ pub struct State {

pub tokens_minted: u64,
pub tokens_burned: u64,
pub tokens_minted_count: u64,
pub tokens_burned_count: u64,
pub accounts: u64,
pub collected_utxos: u64,
pub burned_utxos: u64,
Expand All @@ -25,7 +27,7 @@ pub struct State {
pub managers: BTreeSet<Principal>,
// manager info
pub ecdsa_key_name: Option<String>,
pub utxos_retry_burning_queue: Vec<(u64, canister::Address, u64, u64, u8)>,
pub burning_utxos: BTreeMap<u64, (Principal, canister::Address, u64, u64, String)>,
pub minter_address: Option<String>,
pub minter_subaddress: Option<String>,
}
Expand All @@ -37,6 +39,8 @@ fn get_state() -> Result<State, ()> {
chain: s.chain_params().chain_name.to_string(),
tokens_minted: s.tokens_minted,
tokens_burned: s.tokens_burned,
tokens_minted_count: s.tokens_minted_count,
tokens_burned_count: s.tokens_burned_count,
accounts: store::state::get_accounts_len(),
collected_utxos: store::state::get_collected_utxos_len(),
burned_utxos: store::state::get_burned_utxos_len(),
Expand All @@ -48,7 +52,7 @@ fn get_state() -> Result<State, ()> {

if is_controller_or_manager().is_ok() {
res.ecdsa_key_name = Some(s.ecdsa_key_name.clone());
res.utxos_retry_burning_queue = s.utxos_retry_burning_queue.clone().into();
res.burning_utxos = s.burning_utxos.clone();
res.minter_address = s.get_address(&minter_account()).map(|v| v.to_string()).ok();
res.minter_subaddress = s
.get_address(&user_account(&ic_cdk::id()))
Expand Down
26 changes: 18 additions & 8 deletions src/ck-doge-minter/src/api_update.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{is_authenticated, store, types};
use crate::{is_authenticated, is_controller_or_manager, store, types};

#[ic_cdk::update(guard = "is_authenticated")]
async fn mint_ckdoge() -> Result<types::MintOutput, String> {
Expand All @@ -11,12 +11,22 @@ async fn mint_ckdoge() -> Result<types::MintOutput, String> {

#[ic_cdk::update(guard = "is_authenticated")]
async fn burn_ckdoge(args: types::BurnInput) -> Result<types::BurnOutput, String> {
let res =
store::burn_ckdoge(ic_cdk::caller(), args.address, args.amount, args.fee_rate).await?;
store::burn_ckdoge(ic_cdk::caller(), args.address, args.amount, args.fee_rate).await
}

Ok(types::BurnOutput {
txid: res.txid,
tip_height: res.tip_height,
instructions: ic_cdk::api::performance_counter(1),
})
#[ic_cdk::update(guard = "is_authenticated")]
async fn retry_burn_ckdoge(
block_index: u64,
fee_rate: Option<u64>,
) -> Result<types::BurnOutput, String> {
store::retry_burn_utxos(
block_index,
fee_rate,
if is_controller_or_manager().is_ok() {
None
} else {
Some(ic_cdk::caller())
},
)
.await
}
2 changes: 1 addition & 1 deletion src/ck-doge-minter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ mod api_query;
mod api_update;
mod chain;
mod ecdsa;
mod job;
mod ledger;
mod store;
mod task;
mod types;

static ANONYMOUS: Principal = Principal::anonymous();
Expand Down
168 changes: 99 additions & 69 deletions src/ck-doge-minter/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use std::{
cell::RefCell,
collections::{BTreeMap, BTreeSet, VecDeque},
ops,
time::Duration,
};

use crate::{
Expand Down Expand Up @@ -58,9 +57,14 @@ pub struct State {

pub managers: BTreeSet<Principal>,

// VecDeque<(block_index, receiver, amount, fee_rate, retry)>
#[serde(default)]
pub utxos_retry_burning_queue: VecDeque<(u64, canister::Address, u64, u64, u8)>,
pub tokens_minted_count: u64,
#[serde(default)]
pub tokens_burned_count: u64,

// BTreeMap<block_index, (receiver, amount, fee_rate, error)>
#[serde(default)]
pub burning_utxos: BTreeMap<u64, (Principal, canister::Address, u64, u64, String)>,

// VecDeque<(block_index, timestamp_ms)>
#[serde(default)]
Expand Down Expand Up @@ -385,6 +389,10 @@ pub async fn mint_ckdoge(caller: Principal) -> Result<u64, String> {
}
.await;

state::with_mut(|s| {
s.tokens_minted_count = s.tokens_minted_count.saturating_add(1);
});

match res {
Ok(_) => Ok(total_amount),
Err(err) => {
Expand All @@ -406,7 +414,7 @@ pub async fn burn_ckdoge(
address: String,
amount: u64,
fee_rate: u64,
) -> Result<canister::SendTxOutput, String> {
) -> Result<types::BurnOutput, String> {
if amount < DUST_LIMIT * 10 {
return Err("amount is too small".to_string());
}
Expand Down Expand Up @@ -466,6 +474,7 @@ pub async fn burn_ckdoge(

state::with_mut(|s| {
s.tokens_burned = s.tokens_burned.saturating_add(amount);
s.tokens_burned_count = s.tokens_burned_count.saturating_add(1);
});

COLLECTED_UTXOS_HEAP.with(|r| {
Expand All @@ -476,25 +485,96 @@ pub async fn burn_ckdoge(
}
});

match burn_utxos(block_index, receiver.clone(), amount, fee_rate, utxos).await {
Ok(res) => Ok(res),
state::with_mut(|s| {
s.burning_utxos.insert(
block_index,
(
caller,
receiver.clone().into(),
amount,
fee_rate,
"".to_string(),
),
)
});

match burn_utxos(block_index, receiver, amount, fee_rate, utxos).await {
Ok(res) => {
state::with_mut(|s| s.burning_utxos.remove(&block_index));

Ok(types::BurnOutput {
block_index,
txid: res.txid,
tip_height: res.tip_height,
instructions: ic_cdk::api::performance_counter(1),
})
}
Err(err) => {
state::with_mut(|s| {
s.utxos_retry_burning_queue.push_back((
block_index,
receiver.into(),
amount,
fee_rate,
0,
));
s.burning_utxos
.entry(block_index)
.and_modify(|v| v.4.clone_from(&err));
});
Err(format!(
"burn_utxos failed: {err}, block_index: {block_index}"
))
}
}
}

// retry burn utxos after 30 seconds
ic_cdk_timers::set_timer(
Duration::from_secs(30),
|| ic_cdk::spawn(retry_burn_utxos()),
);
Err(err)
// we can retry burn utxos if it failed in the previous burn_ckdoge call
pub async fn retry_burn_utxos(
block_index: u64,
new_fee_rate: Option<u64>,
caller: Option<Principal>,
) -> Result<types::BurnOutput, String> {
let (owner, receiver, amount, fee_rate, _) =
state::with(|s| s.burning_utxos.get(&block_index).cloned())
.ok_or_else(|| format!("no burning utxos found for block_index: {block_index}"))?;

if let Some(caller) = caller {
if caller != owner {
return Err("caller is not the owner of the burning utxos".to_string());
}
}

let new_fee_rate = new_fee_rate.unwrap_or(fee_rate);

let utxos = COLLECTED_UTXOS_HEAP.with(|r| {
let m = r.borrow();
let mut utxos: Vec<(UtxoState, Principal)> = vec![];
for (utxo, v) in m.iter() {
if v.1 == block_index && v.2 == 0 {
utxos.push((utxo.clone(), v.0));
}
}
utxos
});

if utxos.is_empty() {
return Err(format!("no utxos found for block_index: {block_index}"));
}

match burn_utxos(block_index, receiver.into(), amount, new_fee_rate, utxos).await {
Ok(res) => {
state::with_mut(|s| s.burning_utxos.remove(&block_index));

Ok(types::BurnOutput {
block_index,
txid: res.txid,
tip_height: res.tip_height,
instructions: ic_cdk::api::performance_counter(1),
})
}
Err(err) => {
state::with_mut(|s| {
s.burning_utxos
.entry(block_index)
.and_modify(|v| v.4.clone_from(&err));
});
Err(format!(
"burn_utxos failed: {err}, block_index: {block_index}"
))
}
}
}
Expand Down Expand Up @@ -548,56 +628,6 @@ pub fn list_burned_utxos(start_index: u64, take: usize) -> Vec<types::BurnedUtxo
})
}

// we can retry burn utxos if it failed in the previous burn_ckdoge call
pub async fn retry_burn_utxos() {
if let Some((block_index, receiver, amount, fee_rate, retry)) =
state::with_mut(|s| s.utxos_retry_burning_queue.pop_front())
{
ic_cdk::print(format!(
"retry burn utxos after 30 seconds, block index: {block_index}, receiver: {receiver:?}, amount: {amount}"
));

let utxos = COLLECTED_UTXOS_HEAP.with(|r| {
let m = r.borrow();
let mut utxos: Vec<(UtxoState, Principal)> = vec![];
for (utxo, v) in m.iter() {
if v.1 == block_index && v.2 == 0 {
utxos.push((utxo.clone(), v.0));
}
}
utxos
});

if burn_utxos(
block_index,
receiver.clone().into(),
amount,
fee_rate,
utxos,
)
.await
.is_err()
&& retry < 3
{
state::with_mut(|s| {
s.utxos_retry_burning_queue.push_back((
block_index,
receiver,
amount,
fee_rate,
retry + 1,
));
});
}

// retry burn utxos after 30 seconds
ic_cdk_timers::set_timer(
Duration::from_secs(30),
|| ic_cdk::spawn(retry_burn_utxos()),
);
}
}

pub async fn collect_and_clear_utxos() -> Result<u64, String> {
let minter = ic_cdk::id();
let acc = user_account(&minter);
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions src/ck-doge-minter/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ pub struct BurnInput {

#[derive(CandidType, Clone, Debug, Serialize, Deserialize)]
pub struct BurnOutput {
pub block_index: u64,
pub txid: Txid,
pub tip_height: u64,
pub instructions: u64,
Expand Down

0 comments on commit ef746a0

Please sign in to comment.