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

blocks in stable structures #3297

Draft
wants to merge 60 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
78fa838
stable balances
maciejdfinity Nov 29, 2024
17135dd
remove account trimming
maciejdfinity Nov 29, 2024
996abf8
clippy
maciejdfinity Nov 29, 2024
84551a9
build fix
maciejdfinity Nov 29, 2024
1005cb5
clippy
maciejdfinity Nov 29, 2024
33cfeb1
clippy
maciejdfinity Nov 29, 2024
e0ad62b
clippy
maciejdfinity Nov 29, 2024
9de583d
Revert "clippy"
maciejdfinity Nov 30, 2024
5562268
Revert "clippy"
maciejdfinity Nov 30, 2024
a917348
Revert "clippy"
maciejdfinity Nov 30, 2024
bfdf78e
Revert "build fix"
maciejdfinity Nov 30, 2024
2749fe0
Revert "clippy"
maciejdfinity Nov 30, 2024
40062fc
Revert "remove account trimming"
maciejdfinity Nov 30, 2024
fccd631
disable balance/approval pruning for icrc ledger
maciejdfinity Dec 2, 2024
c9d957e
fix comments
maciejdfinity Dec 2, 2024
d729ebe
clippy
maciejdfinity Dec 2, 2024
e3bb73f
balances_len not a member fn
maciejdfinity Dec 2, 2024
0dca3a4
update canbench results
maciejdfinity Dec 2, 2024
362bc8a
update mainnet u64 wasm, add v1 and v2 wasms
maciejdfinity Dec 2, 2024
b528d1f
add v1 and v2 to bazel
maciejdfinity Dec 2, 2024
2634596
add v2 and mainnet testing to some tests
maciejdfinity Dec 3, 2024
c519690
add test
maciejdfinity Dec 3, 2024
a6d3efd
frozen test
maciejdfinity Dec 3, 2024
c0b5b67
add u256 v1 and v2 ledgers
maciejdfinity Dec 3, 2024
3aa03ac
test upgrade from v1 not possible
maciejdfinity Dec 3, 2024
192fe5c
fix log message
maciejdfinity Dec 3, 2024
43e3e63
clippy
maciejdfinity Dec 3, 2024
813da56
remove account trimming init and upgrade args
maciejdfinity Dec 4, 2024
ab6fbc1
clippy
maciejdfinity Dec 4, 2024
be7510f
clippy
maciejdfinity Dec 4, 2024
ed8a4a9
Merge branch 'master' into maciej-icrc-v4
maciejdfinity Dec 4, 2024
98f81db
Merge branch 'master' into maciej-icrc-v4
maciejdfinity Dec 5, 2024
8a3bf31
Merge branch 'master' into maciej-icrc-v4
maciejdfinity Dec 11, 2024
cf1d502
build fix
maciejdfinity Dec 11, 2024
33a0fd6
fix comments
maciejdfinity Dec 11, 2024
c730fc2
rename function
maciejdfinity Dec 11, 2024
b36f5b6
update cketh hash
maciejdfinity Dec 11, 2024
2ab57f1
refactor tests
maciejdfinity Dec 12, 2024
a5847ff
remove default values for max accounts
maciejdfinity Dec 12, 2024
49e7bb8
update ICRC v3 git commit and wasm hash
maciejdfinity Dec 13, 2024
563eafe
Update rs/ledger_suite/icrc1/ledger/src/main.rs
maciejdfinity Dec 13, 2024
259badc
Merge branch 'master' into maciej-icrc-v4
maciejdfinity Dec 18, 2024
2e91703
stable blockchain
maciejdfinity Dec 18, 2024
91a092e
fix sm tests
maciejdfinity Dec 18, 2024
54bf390
fix upgrade downgrade tests
maciejdfinity Dec 18, 2024
892b03c
reenable ignored tests
maciejdfinity Dec 18, 2024
49ebc99
fix golden state tests
maciejdfinity Dec 18, 2024
e94336d
Merge branch 'maciej-icrc-v4' into maciej-stable-blocks
maciejdfinity Dec 18, 2024
ac67adf
endpoints
maciejdfinity Dec 18, 2024
2c7b88e
log instead of vec
maciejdfinity Dec 18, 2024
1ebe564
gen blocks
maciejdfinity Dec 18, 2024
f487087
error msg
maciejdfinity Dec 18, 2024
88f6d5d
fix blocks stable structure and test
maciejdfinity Dec 19, 2024
a8536e3
add assert
maciejdfinity Dec 27, 2024
b7bc656
Merge branch 'master' into maciej-stable-blocks
maciejdfinity Jan 8, 2025
f2d9228
stable btree map
maciejdfinity Jan 9, 2025
f19fb5d
Merge branch 'master' into maciej-stable-blocks
maciejdfinity Jan 13, 2025
a4a1a8e
test tx and upgrade
maciejdfinity Jan 15, 2025
49a7d30
Merge branch 'master' into maciej-stable-blocks
maciejdfinity Jan 15, 2025
7aec9e0
test single tx instruction count
maciejdfinity Jan 21, 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
33 changes: 28 additions & 5 deletions rs/ledger_suite/common/ledger_canister_core/src/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ pub trait LedgerAccess {
fn with_ledger_mut<R>(f: impl FnOnce(&mut Self::Ledger) -> R) -> R;
}

pub trait ArchivelessBlockchain {
fn add_block(&mut self, index: u64, block: EncodedBlock) -> Result<u64, String>;
fn get_blocks(&self, range: std::ops::Range<u64>) -> Vec<EncodedBlock>;
fn get_block(&self, index: u64) -> Option<EncodedBlock>;
fn len(&self) -> u64;
fn is_empty(&self) -> bool;
fn remove_blocks(&mut self, num_blocks: u64) -> Vec<EncodedBlock>;
fn last_hash(&self) -> Option<HashOf<EncodedBlock>>;
}

pub trait LedgerData: LedgerContext {
type ArchiveWasm: ArchiveCanisterWasm;
type Runtime: Runtime;
Expand Down Expand Up @@ -178,6 +188,9 @@ pub trait LedgerData: LedgerContext {
fn blockchain(&self) -> &Blockchain<Self::Runtime, Self::ArchiveWasm>;
fn blockchain_mut(&mut self) -> &mut Blockchain<Self::Runtime, Self::ArchiveWasm>;

fn archiveless_blockchain(&self) -> &dyn ArchivelessBlockchain;
fn archiveless_blockchain_mut(&mut self) -> &mut dyn ArchivelessBlockchain;

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 Expand Up @@ -296,10 +309,20 @@ where
);
let block_timestamp = block.timestamp();

let height = ledger
.blockchain_mut()
.add_block(block)
.expect("failed to add block");
// let height = ledger
// .blockchain_mut()
// .add_block(block.clone())
// .expect("failed to add block");
// let last_hash = ledger.blockchain().last_hash.unwrap();

let height = ledger.archiveless_blockchain().len();
let _height_stable = ledger
.archiveless_blockchain_mut()
.add_block(height, block.encode())
.expect("failed to add block to stable blockchain");
let last_hash = ledger.archiveless_blockchain().last_hash().unwrap();

// assert_eq!(height, height_stable);
if let Some(fee_collector) = ledger.fee_collector_mut().as_mut() {
if fee_collector.block_index.is_none() {
fee_collector.block_index = Some(height);
Expand All @@ -319,7 +342,7 @@ where
});
}

Ok((height, ledger.blockchain().last_hash.unwrap()))
Ok((height, last_hash))
}

/// Trim balances. Can be used e.g. if the ledger is low on heap memory.
Expand Down
15 changes: 15 additions & 0 deletions rs/ledger_suite/common/ledger_core/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ use crate::timestamp::TimeStamp;

use candid::CandidType;
use ic_ledger_hash_of::HashOf;
use ic_stable_structures::storable::Bound;
use ic_stable_structures::Storable;
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;
use std::borrow::Cow;

/// Position of a block in the chain. The first block has position 0.
pub type BlockIndex = u64;
Expand Down Expand Up @@ -55,6 +58,18 @@ impl<Account> From<Account> for FeeCollector<Account> {
}
}

impl Storable for EncodedBlock {
fn to_bytes(&self) -> Cow<[u8]> {
Cow::Owned(self.clone().into_vec())
}

fn from_bytes(bytes: Cow<[u8]>) -> Self {
EncodedBlock::from_vec(bytes.to_vec())
}

const BOUND: Bound = Bound::Unbounded;
}

pub trait BlockType: Sized + Clone {
type Transaction;
type AccountId;
Expand Down
147 changes: 143 additions & 4 deletions rs/ledger_suite/icrc1/ledger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use candid::{
};
use ic_base_types::PrincipalId;
use ic_canister_log::{log, Sink};
use ic_cdk::api::instruction_counter;
use ic_certification::{
hash_tree::{empty, fork, label, leaf, Label},
HashTree,
Expand All @@ -23,7 +24,8 @@ use ic_ledger_canister_core::{
archive::ArchiveCanisterWasm,
blockchain::Blockchain,
ledger::{
apply_transaction_no_trimming, block_locations, LedgerContext, LedgerData, TransactionInfo,
apply_transaction_no_trimming, block_locations, ArchivelessBlockchain, LedgerContext,
LedgerData, TransactionInfo,
},
range_utils,
};
Expand All @@ -36,6 +38,7 @@ use ic_ledger_core::{
};
use ic_ledger_hash_of::HashOf;
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
// use ic_stable_structures::StableLog;
use ic_stable_structures::{storable::Bound, Storable};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};
use icrc_ledger_types::icrc3::transactions::Transaction as Tx;
Expand Down Expand Up @@ -65,7 +68,7 @@ const TRANSACTION_WINDOW: Duration = Duration::from_secs(24 * 60 * 60);
/// The maximum number of transactions the ledger should return for a single
/// get_transactions request.
const MAX_TRANSACTIONS_PER_REQUEST: usize = 2_000;
const MAX_TRANSACTIONS_IN_WINDOW: usize = 3_000_000;
const MAX_TRANSACTIONS_IN_WINDOW: usize = 3_000_000_000;
const MAX_TRANSACTIONS_TO_PURGE: usize = 100_000;
#[allow(dead_code)]
const MAX_U64_ENCODING_BYTES: usize = 10;
Expand Down Expand Up @@ -496,6 +499,9 @@ const UPGRADES_MEMORY_ID: MemoryId = MemoryId::new(0);
const ALLOWANCES_MEMORY_ID: MemoryId = MemoryId::new(1);
const ALLOWANCES_EXPIRATIONS_MEMORY_ID: MemoryId = MemoryId::new(2);
const BALANCES_MEMORY_ID: MemoryId = MemoryId::new(3);
// const BLOCKS_INDEX_MEMORY_ID: MemoryId = MemoryId::new(4);
// const BLOCKS_DATA_MEMORY_ID: MemoryId = MemoryId::new(5);
const BLOCKS_MEMORY_ID: MemoryId = MemoryId::new(4);

thread_local! {
static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> = RefCell::new(
Expand All @@ -521,6 +527,15 @@ thread_local! {
// account -> tokens - map storing ledger balances.
pub static BALANCES_MEMORY: RefCell<StableBTreeMap<Account, Tokens, VirtualMemory<DefaultMemoryImpl>>> =
MEMORY_MANAGER.with(|memory_manager| RefCell::new(StableBTreeMap::init(memory_manager.borrow().get(BALANCES_MEMORY_ID))));

// // vector storing ledger blocks.
// pub static BLOCKS_MEMORY: RefCell<StableLog<EncodedBlock, VirtualMemory<DefaultMemoryImpl>, VirtualMemory<DefaultMemoryImpl>>> =
// MEMORY_MANAGER.with(|memory_manager| RefCell::new(StableLog::init(memory_manager.borrow().get(BLOCKS_INDEX_MEMORY_ID),
// memory_manager.borrow().get(BLOCKS_DATA_MEMORY_ID)).expect("failed to initialize blocks stable memory")));

// block_index -> block
pub static BLOCKS_MEMORY: RefCell<StableBTreeMap<u64, EncodedBlock, VirtualMemory<DefaultMemoryImpl>>> =
MEMORY_MANAGER.with(|memory_manager| RefCell::new(StableBTreeMap::init(memory_manager.borrow().get(BLOCKS_MEMORY_ID))));
}

#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -554,7 +569,9 @@ pub struct Ledger {
approvals: LedgerAllowances<Tokens>,
#[serde(default)]
stable_approvals: AllowanceTable<StableAllowancesData>,
blockchain: Blockchain<CdkRuntime, Icrc1ArchiveWasm>,
pub blockchain: Blockchain<CdkRuntime, Icrc1ArchiveWasm>,
#[serde(default)]
pub stable_blockchain: StableBlockchain,

minting_account: Account,
fee_collector: Option<FeeCollector<Account>>,
Expand Down Expand Up @@ -645,6 +662,7 @@ impl Ledger {
approvals: Default::default(),
stable_approvals: Default::default(),
blockchain: Blockchain::new_with_archive(archive_options),
stable_blockchain: StableBlockchain::default(),
transactions_by_hash: BTreeMap::new(),
transactions_by_height: VecDeque::new(),
minting_account,
Expand Down Expand Up @@ -806,6 +824,14 @@ impl LedgerData for Ledger {
&mut self.blockchain
}

fn archiveless_blockchain(&self) -> &dyn ArchivelessBlockchain {
&self.stable_blockchain
}

fn archiveless_blockchain_mut(&mut self) -> &mut dyn ArchivelessBlockchain {
&mut self.stable_blockchain
}

fn transactions_by_hash(&self) -> &BTreeMap<HashOf<Self::Transaction>, BlockIndex> {
&self.transactions_by_hash
}
Expand Down Expand Up @@ -1001,6 +1027,22 @@ impl Ledger {
(locations.local_blocks.start, local_blocks, archived_blocks)
}

fn query_archiveless_blocks<B>(
&self,
start: BlockIndex,
length: usize,
decode: impl Fn(&EncodedBlock) -> B,
) -> Vec<B> {
let range = range_utils::make_range(start, length);
let max_range = range_utils::take(&range, MAX_TRANSACTIONS_PER_REQUEST);

self.stable_blockchain
.get_blocks(max_range)
.iter()
.map(decode)
.collect()
}

/// Returns transactions in the specified range.
pub fn get_transactions(&self, start: BlockIndex, length: usize) -> GetTransactionsResponse {
let (first_index, local_transactions, archived_transactions) = self.query_blocks(
Expand All @@ -1022,6 +1064,37 @@ impl Ledger {
}
}

/// Returns blocks in the specified range.
pub fn get_archiveless_blocks(&self, start: BlockIndex, length: usize) -> GetBlocksResponse {
let blocks = self.query_archiveless_blocks(start, length, encoded_block_to_generic_block);

GetBlocksResponse {
first_index: Nat::from(start),
chain_length: instruction_counter(),
certificate: ic_cdk::api::data_certificate().map(serde_bytes::ByteBuf::from),
blocks,
archived_blocks: vec![],
}
}

pub fn bench_block_removal(&mut self) {
let mut curr_index = self.stable_blockchain.len();
let mut num_blocks = self.stable_blockchain.len();
let block = self.stable_blockchain.get_block(num_blocks - 1).unwrap();
for i in 1..1000 {
while num_blocks < 2000 {
let _ = self.stable_blockchain.add_block(curr_index, block.clone());
curr_index += 1;
num_blocks += 1;
}
let start = instruction_counter();
let _removed_blocks = self.stable_blockchain.remove_blocks(1000);
let diff = instruction_counter() - start;
ic_cdk::println!("iteration {}, instructions: {}", i, diff);
num_blocks -= 1000;
}
}

/// Returns blocks in the specified range.
pub fn get_blocks(&self, start: BlockIndex, length: usize) -> GetBlocksResponse {
let (first_index, local_blocks, archived_blocks) = self.query_blocks(
Expand All @@ -1033,7 +1106,7 @@ impl Ledger {

GetBlocksResponse {
first_index: Nat::from(first_index),
chain_length: self.blockchain.chain_length(),
chain_length: instruction_counter(),
certificate: ic_cdk::api::data_certificate().map(serde_bytes::ByteBuf::from),
blocks: local_blocks,
archived_blocks,
Expand Down Expand Up @@ -1299,3 +1372,69 @@ impl BalancesStore for StableBalances {
}
}
}

#[derive(Serialize, Deserialize, Debug, Default, PartialEq)]
pub struct StableBlockchain {
pub last_hash: Option<HashOf<EncodedBlock>>,
}

impl ArchivelessBlockchain for StableBlockchain {
fn add_block(&mut self, index: u64, block: EncodedBlock) -> Result<u64, String> {
BLOCKS_MEMORY.with_borrow_mut(|blocks| blocks.insert(index, block.clone()));
self.last_hash = Some(
ic_icrc1::hash::hash_cbor(block.as_slice())
.map(HashOf::new)
.unwrap_or_else(|err| {
panic!(
"bug: encoded block {} is not hashable cbor: {}",
hex::encode(block.as_slice()),
err
)
}),
);
Ok(index)
}

fn get_blocks(&self, range: std::ops::Range<u64>) -> Vec<EncodedBlock> {
let mut result = vec![];
BLOCKS_MEMORY.with_borrow(|blocks| {
let first_index = blocks
.first_key_value()
.unwrap_or((0u64, EncodedBlock::from(vec![])))
.0;
let available_range = range_utils::make_range(first_index, self.len() as usize);
let intersection = range_utils::intersect(&range, &available_range)
.unwrap_or_else(|_| range_utils::make_range(0, 0));
for block in blocks.range(intersection) {
result.push(block.1)
}
});
result
}

fn len(&self) -> u64 {
BLOCKS_MEMORY.with_borrow(|blocks| blocks.len())
}

fn is_empty(&self) -> bool {
self.len() == 0
}

fn remove_blocks(&mut self, num_blocks: u64) -> Vec<EncodedBlock> {
let mut result = vec![];
BLOCKS_MEMORY.with_borrow_mut(|blocks| {
while result.len() < num_blocks as usize && !blocks.is_empty() {
result.push(blocks.pop_first().unwrap().1);
}
});
result
}

fn get_block(&self, index: u64) -> Option<EncodedBlock> {
BLOCKS_MEMORY.with_borrow(|blocks| blocks.get(&index))
}

fn last_hash(&self) -> Option<HashOf<EncodedBlock>> {
self.last_hash
}
}
Loading
Loading