Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ICRC-Ledger): FI-1441: migrate ledger blocks to stable structures #3695

Merged
merged 45 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
502b08c
add stable map for blocks
maciejdfinity Jan 27, 2025
3c20ea4
replace blocks vector with HeapBlockData
maciejdfinity Jan 30, 2025
bea5311
Merge branch 'master' into maciej-icrc-v5
maciejdfinity Jan 30, 2025
4b70f04
impl stable block data
maciejdfinity Jan 31, 2025
15c6c27
some tests
maciejdfinity Jan 31, 2025
5fbdb57
readd index in add_block
maciejdfinity Feb 3, 2025
0238097
fix icp ledger tests
maciejdfinity Feb 3, 2025
cf07916
fix icp iter blocks
maciejdfinity Feb 3, 2025
e1d9e7d
increase 256 ledger wasm size limit
maciejdfinity Feb 3, 2025
e9b66a3
verify blocks
maciejdfinity Feb 4, 2025
db6a4dd
use encoded blocks for calculating hash
maciejdfinity Feb 4, 2025
e53fff7
disable block endpoints while migrating
maciejdfinity Feb 4, 2025
f13c8ec
disable get_transactions while migrating, add test
maciejdfinity Feb 5, 2025
3421649
fix upgrade downgrade test
maciejdfinity Feb 5, 2025
244ed80
add comments
maciejdfinity Feb 5, 2025
73654ab
remove comment
maciejdfinity Feb 5, 2025
0d6c786
Merge branch 'master' into maciej-icrc-v5
maciejdfinity Feb 5, 2025
e6495b1
add tests from mainnet
maciejdfinity Feb 5, 2025
7bdb2bf
update canbench results
maciejdfinity Feb 5, 2025
919f4b1
checked_sub
maciejdfinity Feb 5, 2025
eea231e
simplify range
maciejdfinity Feb 5, 2025
a0775b7
Merge branch 'master' into maciej-icrc-v5
maciejdfinity Feb 6, 2025
a2385ef
remove todo
maciejdfinity Feb 6, 2025
133879c
comment
maciejdfinity Feb 6, 2025
f0ae72c
small refactor
maciejdfinity Feb 6, 2025
5b16bd5
build fix
maciejdfinity Feb 6, 2025
f6e54e1
Update rs/ledger_suite/common/ledger_canister_core/src/blockchain.rs
maciejdfinity Feb 7, 2025
3ff3a43
Update rs/ledger_suite/common/ledger_canister_core/src/blockchain.rs
maciejdfinity Feb 7, 2025
993e546
clear stable blocks data before migration
maciejdfinity Feb 7, 2025
d03a97d
rename block_slice to get_blocks
maciejdfinity Feb 7, 2025
64673b0
remove oldest blocks
maciejdfinity Feb 7, 2025
0a2fe7e
disable certificate getters while migrating
maciejdfinity Feb 7, 2025
1205982
small refactor
maciejdfinity Feb 7, 2025
c555a83
Merge branch 'master' into maciej-icrc-v5
maciejdfinity Feb 10, 2025
fb769c8
Merge branch 'master' into maciej-icrc-v5
maciejdfinity Feb 24, 2025
834770f
fix test
maciejdfinity Feb 24, 2025
635937a
Merge branch 'master' into maciej-icrc-v5
maciejdfinity Feb 24, 2025
37e224a
remove unused fn
maciejdfinity Feb 24, 2025
cad2ce3
small refactor
maciejdfinity Feb 24, 2025
f08e3d3
update canbench
maciejdfinity Feb 25, 2025
e591ad9
rewrite comments
maciejdfinity Feb 26, 2025
8d1b413
clamp range inside get_blocks
maciejdfinity Feb 26, 2025
0b9899f
Merge branch 'master' into maciej-icrc-v5
maciejdfinity Feb 27, 2025
9420d73
build fix
maciejdfinity Feb 27, 2025
60ce7c7
build fix
maciejdfinity Feb 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion publish/canisters/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ CANISTERS_MAX_SIZE_COMPRESSED_E5_BYTES = {
# we are setting the check to 7 to leave some space for growth
# but enough to get an alert in case of a spike in size.
"ic-icrc1-ledger.wasm.gz": "7",
"ic-icrc1-ledger-u256.wasm.gz": "7",
# The size is currently at 704585 bytes.
"ic-icrc1-ledger-u256.wasm.gz": "8",
# Size when constraint addded: 841_234 bytes
"ledger-canister.wasm.gz": "9",
# Size when constraint addded: 906_940 bytes
Expand Down
140 changes: 115 additions & 25 deletions rs/ledger_suite/common/ledger_canister_core/src/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,95 @@ use crate::{
};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::convert::TryFrom;
use std::sync::{Arc, RwLock};

use ic_ledger_core::block::{BlockIndex, BlockType, EncodedBlock};
use ic_ledger_core::timestamp::TimeStamp;
use ic_ledger_hash_of::HashOf;
use std::ops::Range;

// There is a discrepancy in the way the trait uses indices for
// adding and getting blocks - `add_block` uses global indices
// while `get_blocks` uses 0-based indices (local).
// This is due to the fact that `HeapBlockData` doesn't store
// block indices. Once `HeapBlockData` is removed, the getters
// can be switched to global indices and `Blockchain` code can
// be simplified - it currently needs to offset indices passed
// to getters.
pub trait BlockData {
// The `index` should take into account archived blocks.
// I.e. if there are 10 archived blocks and we add 11th block
// to the ledger, it should be added with index 10.
fn add_block(&mut self, index: u64, block: EncodedBlock);
// The `range` should be 0 based - independently of the number
// of archived blocks. I.e. `get_blocks(0..1)` should always return
// the first block stored in the ledger.
fn get_blocks(&self, range: Range<u64>) -> Vec<EncodedBlock>;
// The `index` should be 0 based - independently of the number
// of archived blocks. I.e. `get_block(0)` should always return
// the first block stored in the ledger.
fn get_block(&self, index: u64) -> Option<EncodedBlock>;
/// Removes `num_blocks` with the smallest index.
fn remove_oldest_blocks(&mut self, num_blocks: u64);
fn len(&self) -> u64;
fn is_empty(&self) -> bool;
fn last(&self) -> Option<EncodedBlock>;
fn migrate_one_block(&mut self, num_archived_blocks: u64) -> bool;
}

#[derive(Debug, Deserialize, Serialize, Default)]
#[serde(transparent)]
pub struct HeapBlockData {
blocks: Vec<EncodedBlock>,
}

impl BlockData for HeapBlockData {
fn add_block(&mut self, _index: u64, block: EncodedBlock) {
self.blocks.push(block);
}

fn get_blocks(&self, range: Range<u64>) -> Vec<EncodedBlock> {
let range = Range {
start: range.start as usize,
end: range.end as usize,
};
self.blocks[range].to_vec()
}

fn get_block(&self, index: u64) -> Option<EncodedBlock> {
self.blocks.get(index as usize).cloned()
}

fn remove_oldest_blocks(&mut self, num_blocks: u64) {
self.blocks = self.blocks.split_off(num_blocks as usize);
}

fn len(&self) -> u64 {
self.blocks.len() as u64
}

fn is_empty(&self) -> bool {
self.blocks.is_empty()
}

fn last(&self) -> Option<EncodedBlock> {
self.blocks.last().cloned()
}

fn migrate_one_block(&mut self, _num_archived_blocks: u64) -> bool {
panic!("HeapBlockData cannot perform migration!");
}
}

/// Stores a chain of transactions with their metadata
#[derive(Debug, Deserialize, Serialize)]
#[serde(bound = "")]
pub struct Blockchain<Rt: Runtime, Wasm: ArchiveCanisterWasm> {
pub blocks: Vec<EncodedBlock>,
#[serde(bound = "BD: Serialize, for<'a> BD: Deserialize<'a>")]
pub struct Blockchain<Rt: Runtime, Wasm: ArchiveCanisterWasm, BD>
where
BD: BlockData + Serialize + Default,
for<'a> BD: Deserialize<'a>,
{
pub blocks: BD,
pub last_hash: Option<HashOf<EncodedBlock>>,

/// The timestamp of the most recent block. Must be monotonically
Expand All @@ -32,10 +109,14 @@ pub struct Blockchain<Rt: Runtime, Wasm: ArchiveCanisterWasm> {
pub num_archived_blocks: u64,
}

impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Default for Blockchain<Rt, Wasm> {
impl<Rt: Runtime, Wasm: ArchiveCanisterWasm, BD> Default for Blockchain<Rt, Wasm, BD>
where
BD: BlockData + Serialize + Default,
for<'a> BD: Deserialize<'a>,
{
fn default() -> Self {
Self {
blocks: vec![],
blocks: Default::default(),
last_hash: None,
last_timestamp: TimeStamp::from_nanos_since_unix_epoch(0),
archive: Arc::new(RwLock::new(None)),
Expand All @@ -44,7 +125,11 @@ impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Default for Blockchain<Rt, Wasm> {
}
}

impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Blockchain<Rt, Wasm> {
impl<Rt: Runtime, Wasm: ArchiveCanisterWasm, BD> Blockchain<Rt, Wasm, BD>
where
BD: BlockData + Serialize + Default,
for<'a> BD: Deserialize<'a>,
{
pub fn new_with_archive(archive_options: ArchiveOptions) -> Self {
Self {
archive: Arc::new(RwLock::new(Some(Archive::new(archive_options)))),
Expand All @@ -68,20 +153,20 @@ impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Blockchain<Rt, Wasm> {
self.last_timestamp = block.timestamp();
let encoded_block = block.encode();
self.last_hash = Some(B::block_hash(&encoded_block));
self.blocks.push(encoded_block);
self.blocks.add_block(self.chain_length(), encoded_block);
Ok(self.chain_length().checked_sub(1).unwrap())
}

pub fn get(&self, height: BlockIndex) -> Option<&EncodedBlock> {
pub fn get(&self, height: BlockIndex) -> Option<EncodedBlock> {
if height < self.num_archived_blocks() {
None
} else {
self.blocks
.get(usize::try_from(height - self.num_archived_blocks()).unwrap())
.get_block(height.checked_sub(self.num_archived_blocks()).unwrap())
}
}

pub fn last(&self) -> Option<&EncodedBlock> {
pub fn last(&self) -> Option<EncodedBlock> {
self.blocks.last()
}

Expand All @@ -90,20 +175,20 @@ impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Blockchain<Rt, Wasm> {
}

pub fn num_unarchived_blocks(&self) -> u64 {
self.blocks.len() as u64
self.blocks.len()
}

/// The range of block indices that are not archived yet.
pub fn local_block_range(&self) -> std::ops::Range<u64> {
self.num_archived_blocks..self.num_archived_blocks + self.blocks.len() as u64
self.num_archived_blocks..self.num_archived_blocks + self.blocks.len()
}

/// Returns the slice of blocks stored locally.
/// Returns the blocks stored locally.
///
/// # Panic
///
/// This function panics if the specified range is not a subset of locally available blocks.
pub fn block_slice(&self, local_blocks: std::ops::Range<u64>) -> &[EncodedBlock] {
pub fn get_blocks(&self, local_blocks: std::ops::Range<u64>) -> Vec<EncodedBlock> {
use crate::range_utils::{is_subrange, offset};

assert!(
Expand All @@ -113,24 +198,23 @@ impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Blockchain<Rt, Wasm> {
self.local_block_range()
);

&self.blocks[offset(&local_blocks, self.num_archived_blocks)]
self.blocks
.get_blocks(offset(&local_blocks, self.num_archived_blocks))
}

pub fn chain_length(&self) -> BlockIndex {
self.num_archived_blocks() + self.num_unarchived_blocks() as BlockIndex
}

pub fn remove_archived_blocks(&mut self, len: usize) {
// redundant since split_off would panic, but here we can give a more
// descriptive message
if len > self.blocks.len() {
if len as u64 > self.blocks.len() {
panic!(
"Asked to remove more blocks than present. Present: {}, to remove: {}",
self.blocks.len(),
len
);
}
self.blocks = self.blocks.split_off(len);
self.blocks.remove_oldest_blocks(len as u64);
self.num_archived_blocks += len as u64;
}

Expand All @@ -142,16 +226,18 @@ impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Blockchain<Rt, Wasm> {
// Upon reaching the `trigger_threshold` we will archive
// `num_blocks_to_archive`. For example, when set to (2000, 1000)
// archiving will trigger when there are 2000 blocks in the ledger and
// the 1000 oldest bocks will be archived, leaving the remaining 1000
// the 1000 oldest blocks will be archived, leaving the remaining 1000
// blocks in place.
let num_blocks_before = self.num_unarchived_blocks() as usize;
let num_blocks_before = self.num_unarchived_blocks();

if num_blocks_before < trigger_threshold {
if num_blocks_before < trigger_threshold as u64 {
return VecDeque::new();
}

let blocks_to_archive: VecDeque<EncodedBlock> =
VecDeque::from(self.blocks[0..num_blocks_to_archive.min(num_blocks_before)].to_vec());
let blocks_to_archive: VecDeque<EncodedBlock> = VecDeque::from(
self.blocks
.get_blocks(0..(num_blocks_to_archive as u64).min(num_blocks_before)),
);

println!(
"get_blocks_for_archiving(): trigger_threshold: {}, num_blocks: {}, blocks before archiving: {}, blocks to archive: {}",
Expand All @@ -163,4 +249,8 @@ impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Blockchain<Rt, Wasm> {

blocks_to_archive
}

pub fn migrate_one_block(&mut self) -> bool {
self.blocks.migrate_one_block(self.num_archived_blocks)
}
}
14 changes: 11 additions & 3 deletions rs/ledger_suite/common/ledger_canister_core/src/ledger.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use crate::{archive::ArchiveCanisterWasm, blockchain::Blockchain, range_utils, runtime::Runtime};
use crate::{
archive::ArchiveCanisterWasm,
blockchain::{BlockData, Blockchain},
range_utils,
runtime::Runtime,
};
use ic_base_types::CanisterId;
use ic_canister_log::{log, Sink};
use ic_ledger_core::approvals::{
Expand Down Expand Up @@ -141,6 +146,7 @@ pub trait LedgerData: LedgerContext {
type Transaction: LedgerTransaction<AccountId = Self::AccountId, Tokens = Self::Tokens>
+ Ord
+ Clone;
type BlockData: BlockData + Serialize + Default + for<'a> Deserialize<'a>;

// Purge configuration

Expand All @@ -164,8 +170,10 @@ pub trait LedgerData: LedgerContext {

// Ledger data structures

fn blockchain(&self) -> &Blockchain<Self::Runtime, Self::ArchiveWasm>;
fn blockchain_mut(&mut self) -> &mut Blockchain<Self::Runtime, Self::ArchiveWasm>;
fn blockchain(&self) -> &Blockchain<Self::Runtime, Self::ArchiveWasm, Self::BlockData>;
fn blockchain_mut(
&mut self,
) -> &mut Blockchain<Self::Runtime, Self::ArchiveWasm, Self::BlockData>;

fn transactions_by_hash(&self) -> &BTreeMap<HashOf<Self::Transaction>, BlockIndex>;
fn transactions_by_hash_mut(&mut self) -> &mut BTreeMap<HashOf<Self::Transaction>, BlockIndex>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,9 @@ pub fn as_indices(r: &Range<u64>) -> Range<usize> {
}

/// Converts the specified range into a range of indices relative to the specified offset.
pub fn offset(r: &Range<u64>, offset: u64) -> Range<usize> {
pub fn offset(r: &Range<u64>, offset: u64) -> Range<u64> {
debug_assert!(offset <= r.start);
let start = r.start.saturating_sub(offset);
let end = start + range_len(r);
Range {
start: start as usize,
end: end as usize,
}
Range { start, end }
}
11 changes: 7 additions & 4 deletions rs/ledger_suite/icp/ledger/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use dfn_core::api::{now, trap_with};
use ic_base_types::{CanisterId, PrincipalId};
use ic_ledger_canister_core::archive::ArchiveCanisterWasm;
use ic_ledger_canister_core::blockchain::Blockchain;
use ic_ledger_canister_core::blockchain::{Blockchain, HeapBlockData};
use ic_ledger_canister_core::ledger::{
self as core_ledger, LedgerContext, LedgerData, TransactionInfo,
};
Expand Down Expand Up @@ -193,7 +193,7 @@ pub struct Ledger {
approvals: LedgerAllowances,
#[serde(default)]
stable_approvals: AllowanceTable<StableAllowancesData>,
pub blockchain: Blockchain<dfn_runtime::DfnRuntime, IcpLedgerArchiveWasm>,
pub blockchain: Blockchain<dfn_runtime::DfnRuntime, IcpLedgerArchiveWasm, HeapBlockData>,
// DEPRECATED
pub maximum_number_of_accounts: usize,
// DEPRECATED
Expand Down Expand Up @@ -273,6 +273,7 @@ impl LedgerData for Ledger {
type ArchiveWasm = IcpLedgerArchiveWasm;
type Transaction = Transaction;
type Block = Block;
type BlockData = HeapBlockData;

fn transaction_window(&self) -> Duration {
self.transaction_window
Expand All @@ -294,11 +295,13 @@ impl LedgerData for Ledger {
&self.token_symbol
}

fn blockchain(&self) -> &Blockchain<Self::Runtime, Self::ArchiveWasm> {
fn blockchain(&self) -> &Blockchain<Self::Runtime, Self::ArchiveWasm, Self::BlockData> {
&self.blockchain
}

fn blockchain_mut(&mut self) -> &mut Blockchain<Self::Runtime, Self::ArchiveWasm> {
fn blockchain_mut(
&mut self,
) -> &mut Blockchain<Self::Runtime, Self::ArchiveWasm, Self::BlockData> {
&mut self.blockchain
}

Expand Down
Loading