Skip to content

Commit f52dbf1

Browse files
authored
chore(ckbtc): Remove empty ReceivedUtxos events from event log (dfinity#3434)
XC-239: ckBTC minter: Clean-up event logs For historical reasons, ckBTC mainnet canister has a lot of ReceivedUtxos with empty Utxos, which is removed by this PR.
1 parent 744f468 commit f52dbf1

File tree

4 files changed

+130
-12
lines changed

4 files changed

+130
-12
lines changed

rs/bitcoin/ckbtc/minter/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ rust_test(
145145
"@crate_index//:candid",
146146
"@crate_index//:flate2",
147147
"@crate_index//:ic-agent",
148+
"@crate_index//:ic-stable-structures",
148149
"@crate_index//:serde",
149150
"@crate_index//:tokio",
150151
],

rs/bitcoin/ckbtc/minter/src/lifecycle/upgrade.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::logs::P0;
22
use crate::state::eventlog::{replay, EventType};
33
use crate::state::invariants::CheckInvariantsImpl;
44
use crate::state::{replace_state, Mode};
5-
use crate::storage::{count_events, events, record_event};
5+
use crate::storage::{count_events, events, migrate_old_events_if_not_empty, record_event};
66
use crate::IC_CANISTER_RUNTIME;
77
use candid::{CandidType, Deserialize};
88
use ic_base_types::CanisterId;
@@ -58,6 +58,9 @@ pub fn post_upgrade(upgrade_args: Option<UpgradeArgs>) {
5858

5959
let start = ic_cdk::api::instruction_counter();
6060

61+
if let Some(removed) = migrate_old_events_if_not_empty() {
62+
log!(P0, "[upgrade]: {} empty events removed", removed)
63+
}
6164
log!(P0, "[upgrade]: replaying {} events", count_events());
6265

6366
let state = replay::<CheckInvariantsImpl>(events()).unwrap_or_else(|e| {

rs/bitcoin/ckbtc/minter/src/storage.rs

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ use ic_stable_structures::{
1111
use serde::Deserialize;
1212
use std::cell::RefCell;
1313

14-
const LOG_INDEX_MEMORY_ID: MemoryId = MemoryId::new(0);
15-
const LOG_DATA_MEMORY_ID: MemoryId = MemoryId::new(1);
14+
const V0_LOG_INDEX_MEMORY_ID: MemoryId = MemoryId::new(0);
15+
const V0_LOG_DATA_MEMORY_ID: MemoryId = MemoryId::new(1);
16+
17+
const V1_LOG_INDEX_MEMORY_ID: MemoryId = MemoryId::new(2);
18+
const V1_LOG_DATA_MEMORY_ID: MemoryId = MemoryId::new(3);
1619

1720
type VMem = VirtualMemory<DefaultMemoryImpl>;
1821
type EventLog = StableLog<Vec<u8>, VMem, VMem>;
@@ -22,13 +25,24 @@ thread_local! {
2225
MemoryManager::init(DefaultMemoryImpl::default())
2326
);
2427

25-
/// The log of the ckBTC state modifications.
26-
static EVENTS: RefCell<EventLog> = MEMORY_MANAGER
28+
/// The v0 log of the ckBTC state modifications that should be migrated to v1 and then set to empty.
29+
static V0_EVENTS: RefCell<EventLog> = MEMORY_MANAGER
30+
.with(|m|
31+
RefCell::new(
32+
StableLog::init(
33+
m.borrow().get(V0_LOG_INDEX_MEMORY_ID),
34+
m.borrow().get(V0_LOG_DATA_MEMORY_ID)
35+
).expect("failed to initialize stable log")
36+
)
37+
);
38+
39+
/// The latest log of the ckBTC state modifications.
40+
static V1_EVENTS: RefCell<EventLog> = MEMORY_MANAGER
2741
.with(|m|
2842
RefCell::new(
2943
StableLog::init(
30-
m.borrow().get(LOG_INDEX_MEMORY_ID),
31-
m.borrow().get(LOG_DATA_MEMORY_ID)
44+
m.borrow().get(V1_LOG_INDEX_MEMORY_ID),
45+
m.borrow().get(V1_LOG_DATA_MEMORY_ID)
3246
).expect("failed to initialize stable log")
3347
)
3448
);
@@ -43,7 +57,7 @@ impl Iterator for EventIterator {
4357
type Item = Event;
4458

4559
fn next(&mut self) -> Option<Event> {
46-
EVENTS.with(|events| {
60+
V1_EVENTS.with(|events| {
4761
let events = events.borrow();
4862

4963
match events.read_entry(self.pos, &mut self.buf) {
@@ -63,7 +77,7 @@ impl Iterator for EventIterator {
6377
}
6478

6579
/// Encodes an event into a byte array.
66-
fn encode_event(event: &Event) -> Vec<u8> {
80+
pub fn encode_event(event: &Event) -> Vec<u8> {
6781
let mut buf = Vec::new();
6882
ciborium::ser::into_writer(event, &mut buf).expect("failed to encode a minter event");
6983
buf
@@ -72,7 +86,7 @@ fn encode_event(event: &Event) -> Vec<u8> {
7286
/// # Panics
7387
///
7488
/// This function panics if the event decoding fails.
75-
fn decode_event(buf: &[u8]) -> Event {
89+
pub fn decode_event(buf: &[u8]) -> Event {
7690
// For backwards compatibility, we have to handle two cases:
7791
// 1. Legacy events: raw instances of the event type enum
7892
// 2. New events: a struct containing a timestamp and an event type
@@ -101,9 +115,49 @@ pub fn events() -> impl Iterator<Item = Event> {
101115
}
102116
}
103117

118+
pub fn migrate_old_events_if_not_empty() -> Option<u64> {
119+
let mut num_events_removed = None;
120+
V0_EVENTS.with(|old_events| {
121+
let mut old = old_events.borrow_mut();
122+
if old.len() > 0 {
123+
V1_EVENTS.with(|new| {
124+
num_events_removed = Some(migrate_events(&old, &new.borrow()));
125+
});
126+
*old = MEMORY_MANAGER.with(|m| {
127+
StableLog::new(
128+
m.borrow().get(V0_LOG_INDEX_MEMORY_ID),
129+
m.borrow().get(V0_LOG_DATA_MEMORY_ID),
130+
)
131+
});
132+
}
133+
});
134+
assert_eq!(
135+
V0_EVENTS.with(|events| events.borrow().len()),
136+
0,
137+
"Old events is not emptied after data migration"
138+
);
139+
num_events_removed
140+
}
141+
142+
pub fn migrate_events(old_events: &EventLog, new_events: &EventLog) -> u64 {
143+
let mut removed = 0;
144+
for bytes in old_events.iter() {
145+
let event = decode_event(&bytes);
146+
match event.payload {
147+
EventType::ReceivedUtxos { utxos, .. } if utxos.is_empty() => removed += 1,
148+
_ => {
149+
new_events
150+
.append(&bytes)
151+
.expect("failed to append an entry to the new event log");
152+
}
153+
}
154+
}
155+
removed
156+
}
157+
104158
/// Returns the current number of events in the log.
105159
pub fn count_events() -> u64 {
106-
EVENTS.with(|events| events.borrow().len())
160+
V1_EVENTS.with(|events| events.borrow().len())
107161
}
108162

109163
/// Records a new minter event.
@@ -112,7 +166,7 @@ pub fn record_event<R: CanisterRuntime>(payload: EventType, runtime: &R) {
112166
timestamp: Some(runtime.time()),
113167
payload,
114168
});
115-
EVENTS.with(|events| {
169+
V1_EVENTS.with(|events| {
116170
events
117171
.borrow()
118172
.append(&bytes)

rs/bitcoin/ckbtc/minter/tests/replay_events.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,66 @@ use ic_ckbtc_minter::state::invariants::{CheckInvariants, CheckInvariantsImpl};
55
use ic_ckbtc_minter::state::{CkBtcMinterState, Network};
66
use std::path::PathBuf;
77

8+
fn assert_useless_events_is_empty(events: impl Iterator<Item = Event>) {
9+
let mut count = 0;
10+
for event in events {
11+
match &event.payload {
12+
EventType::ReceivedUtxos { utxos, .. } if utxos.is_empty() => {
13+
count += 1;
14+
}
15+
_ => {}
16+
}
17+
}
18+
assert_eq!(count, 0);
19+
}
20+
21+
async fn should_migrate_events_for(file: GetEventsFile) -> CkBtcMinterState {
22+
use ic_ckbtc_minter::storage::{decode_event, encode_event, migrate_events};
23+
use ic_stable_structures::{
24+
log::Log as StableLog,
25+
memory_manager::{MemoryId, MemoryManager},
26+
DefaultMemoryImpl,
27+
};
28+
29+
file.retrieve_and_store_events_if_env().await;
30+
31+
let mgr = MemoryManager::init(DefaultMemoryImpl::default());
32+
let old_events = StableLog::new(mgr.get(MemoryId::new(0)), mgr.get(MemoryId::new(1)));
33+
let new_events = StableLog::new(mgr.get(MemoryId::new(2)), mgr.get(MemoryId::new(3)));
34+
let events = file.deserialize().events;
35+
events.iter().for_each(|event| {
36+
old_events.append(&encode_event(event)).unwrap();
37+
});
38+
let removed = migrate_events(&old_events, &new_events);
39+
assert!(removed > 0);
40+
assert!(!new_events.is_empty());
41+
assert_eq!(new_events.len() + removed, old_events.len());
42+
assert_useless_events_is_empty(new_events.iter().map(|bytes| decode_event(&bytes)));
43+
44+
let state =
45+
replay::<SkipCheckInvariantsImpl>(new_events.iter().map(|bytes| decode_event(&bytes)))
46+
.expect("Failed to replay events");
47+
state
48+
.check_invariants()
49+
.expect("Failed to check invariants");
50+
51+
state
52+
}
53+
54+
#[tokio::test]
55+
async fn should_migrate_events_for_mainnet() {
56+
let state = should_migrate_events_for(GetEventsFile::Mainnet).await;
57+
assert_eq!(state.btc_network, Network::Mainnet);
58+
assert_eq!(state.get_total_btc_managed(), 21_723_786_340);
59+
}
60+
61+
#[tokio::test]
62+
async fn should_migrate_events_for_testnet() {
63+
let state = should_migrate_events_for(GetEventsFile::Testnet).await;
64+
assert_eq!(state.btc_network, Network::Testnet);
65+
assert_eq!(state.get_total_btc_managed(), 16578205978);
66+
}
67+
868
#[tokio::test]
969
async fn should_replay_events_for_mainnet() {
1070
GetEventsFile::Mainnet

0 commit comments

Comments
 (0)