diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index dd128089dd9..09c62d8bd34 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -26,9 +26,13 @@ canisters( "sns-wasm": "sns-wasm-canister.wasm.gz", "ck_btc_archive": "ic-icrc1-archive.wasm.gz", "ck_btc_ledger": "ic-icrc1-ledger.wasm.gz", + "ck_btc_ledger_v1": "ic-icrc1-ledger.wasm.gz", + "ck_btc_ledger_v2": "ic-icrc1-ledger.wasm.gz", "ck_btc_index": "ic-icrc1-index-ng.wasm.gz", "ck_eth_archive": "ic-icrc1-archive-u256.wasm.gz", "ck_eth_ledger": "ic-icrc1-ledger-u256.wasm.gz", + "ck_eth_ledger_v1": "ic-icrc1-ledger-u256.wasm.gz", + "ck_eth_ledger_v2": "ic-icrc1-ledger-u256.wasm.gz", "ck_eth_index": "ic-icrc1-index-ng-u256.wasm.gz", "sns_root": "sns-root-canister.wasm.gz", "sns_governance": "sns-governance-canister.wasm.gz", @@ -52,9 +56,13 @@ canisters( "sns-wasm": "mainnet_nns_sns-wasm-canister", "ck_btc_archive": "mainnet_ckbtc_ic-icrc1-archive", "ck_btc_ledger": "mainnet_ckbtc_ic-icrc1-ledger", + "ck_btc_ledger_v1": "mainnet_ckbtc_ic-icrc1-ledger-v1", + "ck_btc_ledger_v2": "mainnet_ckbtc_ic-icrc1-ledger-v2", "ck_btc_index": "mainnet_ckbtc-index-ng", "ck_eth_archive": "mainnet_cketh_ic-icrc1-archive-u256", "ck_eth_ledger": "mainnet_cketh_ic-icrc1-ledger-u256", + "ck_eth_ledger_v1": "mainnet_cketh_ic-icrc1-ledger-u256-v1", + "ck_eth_ledger_v2": "mainnet_cketh_ic-icrc1-ledger-u256-v2", "ck_eth_index": "mainnet_cketh-index-ng", "sns_root": "mainnet_sns-root-canister", "sns_governance": "mainnet_sns-governance-canister", diff --git a/mainnet-canister-revisions.json b/mainnet-canister-revisions.json index f044f082ec7..f940ecf64b1 100644 --- a/mainnet-canister-revisions.json +++ b/mainnet-canister-revisions.json @@ -15,6 +15,14 @@ "rev": "2190613d3b5bcd9b74c382b22d151580b8ac271a", "sha256": "25071c2c55ad4571293e00d8e277f442aec7aed88109743ac52df3125209ff45" }, + "ck_btc_ledger_v1": { + "rev": "d4ee25b0865e89d3eaac13a60f0016d5e3296b31", + "sha256": "a170bfdce5d66e751a3cc03747cb0f06b450af500e75e15976ec08a3f5691f4c" + }, + "ck_btc_ledger_v2": { + "rev": "e54d3fa34ded227c885d04e64505fa4b5d564743", + "sha256": "3d808fa63a3d8ebd4510c0400aa078e99a31afaa0515f0b68778f929ce4b2a46" + }, "ck_eth_archive": { "rev": "2190613d3b5bcd9b74c382b22d151580b8ac271a", "sha256": "2d25f7831894100d48aa9043c65e87c293487523f0958c15760027d004fbbda9" @@ -27,6 +35,14 @@ "rev": "2190613d3b5bcd9b74c382b22d151580b8ac271a", "sha256": "9637743e1215a4db376a62ee807a0986faf20833be2b332df09b3d5dbdd7339e" }, + "ck_eth_ledger_v1": { + "rev": "d4ee25b0865e89d3eaac13a60f0016d5e3296b31", + "sha256": "e6072806ae22868ee09c07923d093b1b0b687dba540d22cfc1e1a5392bfcca46" + }, + "ck_eth_ledger_v2": { + "rev": "e54d3fa34ded227c885d04e64505fa4b5d564743", + "sha256": "98a7b7391608dc4a554d6964bad24157b6aaf890a05bbaad3fcc92033d9c7b02" + }, "cycles-minting": { "rev": "ee52ab3056cf5f39b09b08de70bdd20485c8b2dc", "sha256": "bbb8995cb749ba9e2c721ff507f5e5313f32e69b1adf3df20e3901ed56a70b42" diff --git a/rs/ethereum/ledger-suite-orchestrator/src/scheduler/mod.rs b/rs/ethereum/ledger-suite-orchestrator/src/scheduler/mod.rs index 8848c70062a..af80736d513 100644 --- a/rs/ethereum/ledger-suite-orchestrator/src/scheduler/mod.rs +++ b/rs/ethereum/ledger-suite-orchestrator/src/scheduler/mod.rs @@ -932,8 +932,6 @@ fn icrc1_ledger_init_arg( ), max_memo_length: Some(MAX_MEMO_LENGTH), feature_flags: Some(ICRC2_FEATURE), - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, } } diff --git a/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs b/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs index c8190e968e0..30e9d972a98 100644 --- a/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs +++ b/rs/ledger_suite/common/ledger_canister_core/src/ledger.rs @@ -208,6 +208,7 @@ pub enum TransferError { const APPROVE_PRUNE_LIMIT: usize = 100; /// Adds a new block with the specified transaction to the ledger. +/// Trim balances if necessary. pub fn apply_transaction( ledger: &mut L, transaction: L::Transaction, @@ -217,6 +218,22 @@ pub fn apply_transaction( where L: LedgerData, L::BalancesStore: InspectableBalancesStore, +{ + let result = apply_transaction_no_trimming(ledger, transaction, now, effective_fee); + trim_balances(ledger, now); + result +} + +/// Adds a new block with the specified transaction to the ledger. +/// Do not perform any balance trimming. +pub fn apply_transaction_no_trimming( + ledger: &mut L, + transaction: L::Transaction, + now: TimeStamp, + effective_fee: L::Tokens, +) -> Result<(BlockIndex, HashOf), TransferError> +where + L: LedgerData, { let num_pruned = purge_old_transactions(ledger, now); @@ -301,6 +318,16 @@ where transaction_hash: tx_hash, }); } + + Ok((height, ledger.blockchain().last_hash.unwrap())) +} + +/// Trim balances. Can be used e.g. if the ledger is low on heap memory. +fn trim_balances(ledger: &mut L, now: TimeStamp) +where + L: LedgerData, + L::BalancesStore: InspectableBalancesStore, +{ let effective_max_number_of_accounts = ledger.max_number_of_accounts() + ledger.accounts_overflow_trim_quantity() - 1; @@ -331,8 +358,6 @@ where )) .unwrap(); } - - Ok((height, ledger.blockchain().last_hash.unwrap())) } /// Finds the archive canister that contains the block with the specified height. diff --git a/rs/ledger_suite/icp/ledger/tests/tests.rs b/rs/ledger_suite/icp/ledger/tests/tests.rs index b3362beb36a..4b7573de082 100644 --- a/rs/ledger_suite/icp/ledger/tests/tests.rs +++ b/rs/ledger_suite/icp/ledger/tests/tests.rs @@ -1250,6 +1250,7 @@ fn test_upgrade_serialization() { upgrade_args, minter, false, + false, ); } diff --git a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs index 9edffa62dde..6a1bb3e35f9 100644 --- a/rs/ledger_suite/icrc1/index-ng/tests/tests.rs +++ b/rs/ledger_suite/icrc1/index-ng/tests/tests.rs @@ -55,7 +55,6 @@ fn upgrade_ledger( change_fee_collector, max_memo_length: None, feature_flags: None, - accounts_overflow_trim_quantity: None, change_archive_options: None, })); env.upgrade_canister(ledger_id, ledger_wasm(), Encode!(&args).unwrap()) diff --git a/rs/ledger_suite/icrc1/ledger/BUILD.bazel b/rs/ledger_suite/icrc1/ledger/BUILD.bazel index 3a283db1377..afaec0a3b60 100644 --- a/rs/ledger_suite/icrc1/ledger/BUILD.bazel +++ b/rs/ledger_suite/icrc1/ledger/BUILD.bazel @@ -278,14 +278,22 @@ rust_test( "//rs/ledger_suite/icrc1/archive:archive_canister" + name_suffix + ".wasm.gz", "//rs/ledger_suite/icrc1/ledger:ledger_canister_icrc3_compatible_data_certificate", "//rs/universal_canister/impl:universal_canister.wasm.gz", + "@mainnet_ckbtc_ic-icrc1-ledger-v1//file", + "@mainnet_ckbtc_ic-icrc1-ledger-v2//file", "@mainnet_ckbtc_ic-icrc1-ledger//file", + "@mainnet_cketh_ic-icrc1-ledger-u256-v1//file", + "@mainnet_cketh_ic-icrc1-ledger-u256-v2//file", "@mainnet_cketh_ic-icrc1-ledger-u256//file", "@mainnet_ic-icrc1-ledger//file", ], env = { "CARGO_MANIFEST_DIR": "rs/ledger_suite/icrc1/ledger", "CKBTC_IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH": "$(rootpath @mainnet_ckbtc_ic-icrc1-ledger//file)", + "CKBTC_IC_ICRC1_LEDGER_V1_VERSION_WASM_PATH": "$(rootpath @mainnet_ckbtc_ic-icrc1-ledger-v1//file)", + "CKBTC_IC_ICRC1_LEDGER_V2_VERSION_WASM_PATH": "$(rootpath @mainnet_ckbtc_ic-icrc1-ledger-v2//file)", "CKETH_IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH": "$(rootpath @mainnet_cketh_ic-icrc1-ledger-u256//file)", + "CKETH_IC_ICRC1_LEDGER_V1_VERSION_WASM_PATH": "$(rootpath @mainnet_cketh_ic-icrc1-ledger-u256-v1//file)", + "CKETH_IC_ICRC1_LEDGER_V2_VERSION_WASM_PATH": "$(rootpath @mainnet_cketh_ic-icrc1-ledger-u256-v2//file)", "IC_ICRC1_ARCHIVE_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/archive:archive_canister" + name_suffix + ".wasm.gz)", "IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH": "$(rootpath @mainnet_ic-icrc1-ledger//file)", "IC_ICRC1_LEDGER_ICRC3_COMPATIBLE_DATA_CERTIFICATE_WASM_PATH": "$(rootpath //rs/ledger_suite/icrc1/ledger:ledger_canister_icrc3_compatible_data_certificate)", diff --git a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml index 77770b0cfb7..4fe24655c4a 100644 --- a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml +++ b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u256.yml @@ -1,42 +1,42 @@ benches: bench_icrc1_transfers: total: - instructions: 6398923208 - heap_increase: 227 + instructions: 13798815361 + heap_increase: 172 stable_memory_increase: 128 scopes: before_upgrade: - instructions: 5790054103 - heap_increase: 71 + instructions: 13630531777 + heap_increase: 43 stable_memory_increase: 0 post_upgrade: - instructions: 424320009 - heap_increase: 27 + instructions: 118601062 + heap_increase: 0 stable_memory_increase: 0 pre_upgrade: - instructions: 184546072 + instructions: 49679478 heap_increase: 129 stable_memory_increase: 128 upgrade: - instructions: 608867671 - heap_increase: 156 + instructions: 168282130 + heap_increase: 129 stable_memory_increase: 128 bench_upgrade_baseline: total: - instructions: 8755062 + instructions: 8684974 heap_increase: 258 - stable_memory_increase: 129 + stable_memory_increase: 128 scopes: post_upgrade: - instructions: 8627524 + instructions: 8606422 heap_increase: 129 stable_memory_increase: 0 pre_upgrade: - instructions: 125131 + instructions: 76145 heap_increase: 129 - stable_memory_increase: 129 + stable_memory_increase: 128 upgrade: - instructions: 8754373 + instructions: 8684285 heap_increase: 258 - stable_memory_increase: 129 + stable_memory_increase: 128 version: 0.1.7 diff --git a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml index 22fe755925c..7e7164f34e5 100644 --- a/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml +++ b/rs/ledger_suite/icrc1/ledger/canbench_results/canbench_u64.yml @@ -1,42 +1,42 @@ benches: bench_icrc1_transfers: total: - instructions: 5926738616 - heap_increase: 214 + instructions: 13237283790 + heap_increase: 171 stable_memory_increase: 128 scopes: before_upgrade: - instructions: 5341274673 - heap_increase: 64 + instructions: 13068913917 + heap_increase: 42 stable_memory_increase: 0 post_upgrade: - instructions: 406888378 - heap_increase: 21 + instructions: 118797275 + heap_increase: 0 stable_memory_increase: 0 pre_upgrade: - instructions: 178572258 + instructions: 49569466 heap_increase: 129 stable_memory_increase: 128 upgrade: - instructions: 585462226 - heap_increase: 150 + instructions: 168368331 + heap_increase: 129 stable_memory_increase: 128 bench_upgrade_baseline: total: - instructions: 8758206 + instructions: 8686052 heap_increase: 258 - stable_memory_increase: 129 + stable_memory_increase: 128 scopes: post_upgrade: - instructions: 8627876 + instructions: 8606533 heap_increase: 129 stable_memory_increase: 0 pre_upgrade: - instructions: 127923 + instructions: 77112 heap_increase: 129 - stable_memory_increase: 129 + stable_memory_increase: 128 upgrade: - instructions: 8757517 + instructions: 8685363 heap_increase: 258 - stable_memory_increase: 129 + stable_memory_increase: 128 version: 0.1.7 diff --git a/rs/ledger_suite/icrc1/ledger/ledger.did b/rs/ledger_suite/icrc1/ledger/ledger.did index dece15d9bba..b3a6c474c35 100644 --- a/rs/ledger_suite/icrc1/ledger/ledger.did +++ b/rs/ledger_suite/icrc1/ledger/ledger.did @@ -107,8 +107,6 @@ type InitArgs = record { metadata : vec record { text; MetadataValue }; initial_balances : vec record { Account; nat }; feature_flags : opt FeatureFlags; - maximum_number_of_accounts : opt nat64; - accounts_overflow_trim_quantity : opt nat64; archive_options : record { num_blocks_to_archive : nat64; max_transactions_per_response : opt nat64; @@ -144,7 +142,6 @@ type UpgradeArgs = record { change_fee_collector : opt ChangeFeeCollector; max_memo_length : opt nat16; feature_flags : opt FeatureFlags; - accounts_overflow_trim_quantity: opt nat64; change_archive_options : opt ChangeArchiveOptions; }; diff --git a/rs/ledger_suite/icrc1/ledger/src/benches/mod.rs b/rs/ledger_suite/icrc1/ledger/src/benches/mod.rs index f99196d8c27..68d36334239 100644 --- a/rs/ledger_suite/icrc1/ledger/src/benches/mod.rs +++ b/rs/ledger_suite/icrc1/ledger/src/benches/mod.rs @@ -1,10 +1,8 @@ -use crate::{execute_transfer_not_async, post_upgrade, pre_upgrade, Access, Tokens}; +use crate::{balances_len, execute_transfer_not_async, post_upgrade, pre_upgrade, Tokens}; use assert_matches::assert_matches; use candid::{Nat, Principal}; use ic_canister_log::Sink; -use ic_ledger_canister_core::ledger::{ - blocks_to_archive, remove_archived_blocks, LedgerAccess, LedgerContext, -}; +use ic_ledger_canister_core::ledger::{blocks_to_archive, remove_archived_blocks, LedgerAccess}; use ic_ledger_core::block::BlockIndex; use icrc_ledger_types::icrc1::account::Account; use icrc_ledger_types::icrc1::transfer::TransferArg; @@ -42,9 +40,7 @@ pub fn icrc1_transfer( } fn assert_has_num_balances(num_balances: u32) { - Access::with_ledger(|ledger| { - assert_eq!(ledger.balances().store.len() as u32, num_balances); - }); + assert_eq!(balances_len() as u32, num_balances); } pub fn max_length_principal(index: u32) -> Principal { diff --git a/rs/ledger_suite/icrc1/ledger/src/lib.rs b/rs/ledger_suite/icrc1/ledger/src/lib.rs index c1c5839db3e..ee7d2fc00bf 100644 --- a/rs/ledger_suite/icrc1/ledger/src/lib.rs +++ b/rs/ledger_suite/icrc1/ledger/src/lib.rs @@ -22,9 +22,12 @@ use ic_ledger_canister_core::runtime::Runtime; use ic_ledger_canister_core::{ archive::ArchiveCanisterWasm, blockchain::Blockchain, - ledger::{apply_transaction, block_locations, LedgerContext, LedgerData, TransactionInfo}, + ledger::{ + apply_transaction_no_trimming, block_locations, LedgerContext, LedgerData, TransactionInfo, + }, range_utils, }; +use ic_ledger_core::balances::BalancesStore; use ic_ledger_core::{ approvals::{Allowance, AllowanceTable, AllowancesData}, balances::Balances, @@ -59,11 +62,9 @@ use std::ops::DerefMut; use std::time::Duration; const TRANSACTION_WINDOW: Duration = Duration::from_secs(24 * 60 * 60); -const MAX_ACCOUNTS: usize = 28_000_000; /// The maximum number of transactions the ledger should return for a single /// get_transactions request. const MAX_TRANSACTIONS_PER_REQUEST: usize = 2_000; -const ACCOUNTS_OVERFLOW_TRIM_QUANTITY: usize = 100_000; const MAX_TRANSACTIONS_IN_WINDOW: usize = 3_000_000; const MAX_TRANSACTIONS_TO_PURGE: usize = 100_000; #[allow(dead_code)] @@ -82,11 +83,12 @@ pub type Tokens = ic_icrc1_tokens_u256::U256; /// We have the following ledger versions: /// * 0 - the whole ledger state is stored on the heap. /// * 1 - the allowances are stored in stable structures. +/// * 2 - the balances are stored in stable structures. #[cfg(not(feature = "next-ledger-version"))] -pub const LEDGER_VERSION: u64 = 1; +pub const LEDGER_VERSION: u64 = 2; #[cfg(feature = "next-ledger-version")] -pub const LEDGER_VERSION: u64 = 2; +pub const LEDGER_VERSION: u64 = 3; #[derive(Clone, Debug)] pub struct Icrc1ArchiveWasm; @@ -179,8 +181,6 @@ impl InitArgsBuilder { }, max_memo_length: None, feature_flags: None, - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, }) } @@ -263,8 +263,6 @@ pub struct InitArgs { pub archive_options: ArchiveOptions, pub max_memo_length: Option, pub feature_flags: Option, - pub maximum_number_of_accounts: Option, - pub accounts_overflow_trim_quantity: Option, } #[derive(Clone, Eq, PartialEq, Debug, CandidType, Deserialize)] @@ -340,8 +338,6 @@ pub struct UpgradeArgs { #[serde(default, skip_serializing_if = "Option::is_none")] pub feature_flags: Option, #[serde(default, skip_serializing_if = "Option::is_none")] - pub accounts_overflow_trim_quantity: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] pub change_archive_options: Option, } @@ -499,6 +495,7 @@ pub enum LedgerArgument { 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); thread_local! { static MEMORY_MANAGER: RefCell> = RefCell::new( @@ -520,12 +517,17 @@ thread_local! { #[allow(clippy::type_complexity)] pub static ALLOWANCES_EXPIRATIONS_MEMORY: RefCell>> = MEMORY_MANAGER.with(|memory_manager| RefCell::new(StableBTreeMap::init(memory_manager.borrow().get(ALLOWANCES_EXPIRATIONS_MEMORY_ID)))); + + // account -> tokens - map storing ledger balances. + pub static BALANCES_MEMORY: RefCell>> = + MEMORY_MANAGER.with(|memory_manager| RefCell::new(StableBTreeMap::init(memory_manager.borrow().get(BALANCES_MEMORY_ID)))); } #[derive(Copy, Clone, Serialize, Deserialize, Debug)] pub enum LedgerField { Allowances, AllowancesExpirations, + Balances, } #[derive(Copy, Clone, Serialize, Deserialize, Debug)] @@ -540,11 +542,15 @@ impl Default for LedgerState { } } +type StableLedgerBalances = Balances; + #[derive(Debug, Deserialize, Serialize)] #[serde(bound = "")] pub struct Ledger { balances: LedgerBalances, #[serde(default)] + stable_balances: StableLedgerBalances, + #[serde(default)] approvals: LedgerAllowances, #[serde(default)] stable_approvals: AllowanceTable, @@ -569,23 +575,17 @@ pub struct Ledger { #[serde(default)] feature_flags: FeatureFlags, - #[serde(default = "default_maximum_number_of_accounts")] + // DEPRECATED + #[serde(default)] maximum_number_of_accounts: usize, - #[serde(default = "default_accounts_overflow_trim_quantity")] + // DEPRECATED + #[serde(default)] accounts_overflow_trim_quantity: usize, #[serde(default = "default_ledger_version")] pub ledger_version: u64, } -fn default_maximum_number_of_accounts() -> usize { - MAX_ACCOUNTS -} - -fn default_accounts_overflow_trim_quantity() -> usize { - ACCOUNTS_OVERFLOW_TRIM_QUANTITY -} - fn default_ledger_version() -> u64 { LEDGER_VERSION } @@ -630,8 +630,6 @@ impl Ledger { fee_collector_account, max_memo_length, feature_flags, - maximum_number_of_accounts, - accounts_overflow_trim_quantity, }: InitArgs, now: TimeStamp, ) -> Self { @@ -643,6 +641,7 @@ impl Ledger { } let mut ledger = Self { balances: LedgerBalances::default(), + stable_balances: StableLedgerBalances::default(), approvals: Default::default(), stable_approvals: Default::default(), blockchain: Blockchain::new_with_archive(archive_options), @@ -665,14 +664,8 @@ impl Ledger { .collect(), max_memo_length: max_memo_length.unwrap_or(DEFAULT_MAX_MEMO_LENGTH), feature_flags: feature_flags.unwrap_or_default(), - maximum_number_of_accounts: maximum_number_of_accounts - .unwrap_or_else(|| MAX_ACCOUNTS.try_into().unwrap()) - .try_into() - .unwrap(), - accounts_overflow_trim_quantity: accounts_overflow_trim_quantity - .unwrap_or_else(|| ACCOUNTS_OVERFLOW_TRIM_QUANTITY.try_into().unwrap()) - .try_into() - .unwrap(), + maximum_number_of_accounts: 0, + accounts_overflow_trim_quantity: 0, ledger_version: LEDGER_VERSION, }; @@ -684,12 +677,14 @@ impl Ledger { ) }); let mint = Transaction::mint(account, amount, Some(now), None); - apply_transaction(&mut ledger, mint, now, Tokens::ZERO).unwrap_or_else(|err| { - panic!( - "failed to mint {} tokens to {}: {:?}", - balance, account, err - ) - }); + apply_transaction_no_trimming(&mut ledger, mint, now, Tokens::ZERO).unwrap_or_else( + |err| { + panic!( + "failed to mint {} tokens to {}: {:?}", + balance, account, err + ) + }, + ); } ledger @@ -719,25 +714,39 @@ impl Ledger { } } + pub fn migrate_one_balance(&mut self) -> bool { + match self.balances.store.pop_first() { + Some((account, tokens)) => { + self.stable_balances.credit(&account, tokens); + true + } + None => false, + } + } + pub fn clear_arrivals(&mut self) { self.approvals.allowances_data.clear_arrivals(); } + + pub fn copy_token_pool(&mut self) { + self.stable_balances.token_pool = self.balances.token_pool; + } } impl LedgerContext for Ledger { type AccountId = Account; type AllowancesData = StableAllowancesData; - type BalancesStore = BTreeMap; + type BalancesStore = StableBalances; type Tokens = Tokens; fn balances(&self) -> &Balances { panic_if_not_ready(); - &self.balances + &self.stable_balances } fn balances_mut(&mut self) -> &mut Balances { panic_if_not_ready(); - &mut self.balances + &mut self.stable_balances } fn approvals(&self) -> &AllowanceTable { @@ -904,10 +913,6 @@ impl Ledger { } self.feature_flags = feature_flags; } - if let Some(accounts_overflow_trim_quantity) = args.accounts_overflow_trim_quantity { - self.accounts_overflow_trim_quantity = - accounts_overflow_trim_quantity.try_into().unwrap(); - } if let Some(change_archive_options) = args.change_archive_options { let mut maybe_archive = self.blockchain.archive.write().expect( "BUG: should be unreachable since upgrade has exclusive write access to the ledger", @@ -1149,6 +1154,16 @@ pub fn clear_stable_allowance_data() { }); } +pub fn clear_stable_balances_data() { + BALANCES_MEMORY.with_borrow_mut(|balances| { + balances.clear_new(); + }); +} + +pub fn balances_len() -> u64 { + BALANCES_MEMORY.with_borrow(|balances| balances.len()) +} + #[derive(Serialize, Deserialize, Debug, Default)] pub struct StableAllowancesData {} @@ -1247,3 +1262,40 @@ impl AllowancesData for StableAllowancesData { panic!("The method `clear_arrivals` should not be called for StableAllowancesData") } } + +#[derive(Serialize, Deserialize, Debug, Default, PartialEq)] +pub struct StableBalances {} + +impl BalancesStore for StableBalances { + type AccountId = Account; + type Tokens = Tokens; + + fn get_balance(&self, k: &Account) -> Option { + BALANCES_MEMORY.with_borrow(|balances| balances.get(k)) + } + + fn update(&mut self, k: Account, mut f: F) -> Result + where + F: FnMut(Option<&Tokens>) -> Result, + { + let entry = BALANCES_MEMORY.with_borrow(|balances| balances.get(&k)); + match entry { + Some(v) => { + let new_v = f(Some(&v))?; + if new_v != Tokens::ZERO { + BALANCES_MEMORY.with_borrow_mut(|balances| balances.insert(k, new_v)); + } else { + BALANCES_MEMORY.with_borrow_mut(|balances| balances.remove(&k)); + } + Ok(new_v) + } + None => { + let new_v = f(None)?; + if new_v != Tokens::ZERO { + BALANCES_MEMORY.with_borrow_mut(|balances| balances.insert(k, new_v)); + } + Ok(new_v) + } + } + } +} diff --git a/rs/ledger_suite/icrc1/ledger/src/main.rs b/rs/ledger_suite/icrc1/ledger/src/main.rs index 507224c7732..18643abf59e 100644 --- a/rs/ledger_suite/icrc1/ledger/src/main.rs +++ b/rs/ledger_suite/icrc1/ledger/src/main.rs @@ -16,12 +16,12 @@ use ic_icrc1::{ Operation, Transaction, }; use ic_icrc1_ledger::{ - clear_stable_allowance_data, is_ready, ledger_state, panic_if_not_ready, set_ledger_state, - LEDGER_VERSION, UPGRADES_MEMORY, + balances_len, clear_stable_allowance_data, clear_stable_balances_data, is_ready, ledger_state, + panic_if_not_ready, set_ledger_state, LEDGER_VERSION, UPGRADES_MEMORY, }; use ic_icrc1_ledger::{InitArgs, Ledger, LedgerArgument, LedgerField, LedgerState}; use ic_ledger_canister_core::ledger::{ - apply_transaction, archive_blocks, LedgerAccess, LedgerContext, LedgerData, + apply_transaction_no_trimming, archive_blocks, LedgerAccess, LedgerContext, LedgerData, TransferError as CoreTransferError, }; use ic_ledger_canister_core::runtime::heap_memory_size_bytes; @@ -238,6 +238,14 @@ fn post_upgrade(args: Option) { PRE_UPGRADE_INSTRUCTIONS_CONSUMED.with(|n| *n.borrow_mut() = pre_upgrade_instructions_consumed); + if upgrade_from_version < 2 { + set_ledger_state(LedgerState::Migrating(LedgerField::Balances)); + log_message(format!("Upgrading from version {upgrade_from_version} which does not store balances in stable structures, clearing stable balances data.").as_str()); + clear_stable_balances_data(); + Access::with_ledger_mut(|ledger| { + ledger.copy_token_pool(); + }); + } if upgrade_from_version == 0 { set_ledger_state(LedgerState::Migrating(LedgerField::Allowances)); log_message("Upgrading from version 0 which does not use stable structures, clearing stable allowance data."); @@ -263,6 +271,7 @@ fn migrate_next_part(instruction_limit: u64) { STABLE_UPGRADE_MIGRATION_STEPS.with(|n| *n.borrow_mut() += 1); let mut migrated_allowances = 0; let mut migrated_expirations = 0; + let mut migrated_balances = 0; log_message("Migrating part of the ledger state."); @@ -285,6 +294,13 @@ fn migrate_next_part(instruction_limit: u64) { LedgerField::AllowancesExpirations => { if ledger.migrate_one_expiration() { migrated_expirations += 1; + } else { + set_ledger_state(LedgerState::Migrating(LedgerField::Balances)); + } + } + LedgerField::Balances => { + if ledger.migrate_one_balance() { + migrated_balances += 1; } else { set_ledger_state(LedgerState::Ready); } @@ -292,7 +308,7 @@ fn migrate_next_part(instruction_limit: u64) { } } let instructions_migration = instruction_counter() - instructions_migration_start; - let msg = format!("Number of elements migrated: allowances: {migrated_allowances} expirations: {migrated_expirations}. Migration step instructions: {instructions_migration}, total instructions used in message: {}." , + let msg = format!("Number of elements migrated: allowances: {migrated_allowances} expirations: {migrated_expirations} balances: {migrated_balances}. Migration step instructions: {instructions_migration}, total instructions used in message: {}." , instruction_counter()); if !is_ready() { log_message( @@ -404,7 +420,7 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder>) -> std::i )?; w.encode_gauge( "ledger_balance_store_entries", - ledger.balances().store.len() as f64, + balances_len() as f64, "Total number of accounts in the balance store.", )?; } @@ -654,7 +670,7 @@ fn execute_transfer_not_async( ) }; - let (block_idx, _) = apply_transaction(ledger, tx, now, effective_fee)?; + let (block_idx, _) = apply_transaction_no_trimming(ledger, tx, now, effective_fee)?; Ok(block_idx) }) } @@ -853,7 +869,7 @@ async fn icrc2_approve(arg: ApproveArgs) -> Result { memo: arg.memo, }; - let (block_idx, _) = apply_transaction(ledger, tx, now, expected_fee_tokens) + let (block_idx, _) = apply_transaction_no_trimming(ledger, tx, now, expected_fee_tokens) .map_err(convert_transfer_error) .map_err(|err| { let err: ApproveError = match err.try_into() { diff --git a/rs/ledger_suite/icrc1/ledger/src/tests.rs b/rs/ledger_suite/icrc1/ledger/src/tests.rs index ca5634b81d4..5b177a13dbc 100644 --- a/rs/ledger_suite/icrc1/ledger/src/tests.rs +++ b/rs/ledger_suite/icrc1/ledger/src/tests.rs @@ -85,8 +85,6 @@ fn default_init_args() -> InitArgs { }, max_memo_length: None, feature_flags: None, - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, } } diff --git a/rs/ledger_suite/icrc1/ledger/tests/tests.rs b/rs/ledger_suite/icrc1/ledger/tests/tests.rs index dd0caf85769..e3b0fee8020 100644 --- a/rs/ledger_suite/icrc1/ledger/tests/tests.rs +++ b/rs/ledger_suite/icrc1/ledger/tests/tests.rs @@ -77,16 +77,52 @@ fn ledger_mainnet_wasm() -> Vec { mainnet_wasm } +fn ledger_mainnet_v2_wasm() -> Vec { + #[cfg(not(feature = "u256-tokens"))] + let mainnet_wasm = ledger_mainnet_v2_u64_wasm(); + #[cfg(feature = "u256-tokens")] + let mainnet_wasm = ledger_mainnet_v2_u256_wasm(); + mainnet_wasm +} + +fn ledger_mainnet_v1_wasm() -> Vec { + #[cfg(not(feature = "u256-tokens"))] + let mainnet_wasm = ledger_mainnet_v1_u64_wasm(); + #[cfg(feature = "u256-tokens")] + let mainnet_wasm = ledger_mainnet_v1_u256_wasm(); + mainnet_wasm +} + fn ledger_mainnet_u64_wasm() -> Vec { std::fs::read(std::env::var("CKBTC_IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH").unwrap()) .unwrap() } +#[cfg(not(feature = "u256-tokens"))] +fn ledger_mainnet_v2_u64_wasm() -> Vec { + std::fs::read(std::env::var("CKBTC_IC_ICRC1_LEDGER_V2_VERSION_WASM_PATH").unwrap()).unwrap() +} + +#[cfg(not(feature = "u256-tokens"))] +fn ledger_mainnet_v1_u64_wasm() -> Vec { + std::fs::read(std::env::var("CKBTC_IC_ICRC1_LEDGER_V1_VERSION_WASM_PATH").unwrap()).unwrap() +} + fn ledger_mainnet_u256_wasm() -> Vec { std::fs::read(std::env::var("CKETH_IC_ICRC1_LEDGER_DEPLOYED_VERSION_WASM_PATH").unwrap()) .unwrap() } +#[cfg(feature = "u256-tokens")] +fn ledger_mainnet_v2_u256_wasm() -> Vec { + std::fs::read(std::env::var("CKETH_IC_ICRC1_LEDGER_V2_VERSION_WASM_PATH").unwrap()).unwrap() +} + +#[cfg(feature = "u256-tokens")] +fn ledger_mainnet_v1_u256_wasm() -> Vec { + std::fs::read(std::env::var("CKETH_IC_ICRC1_LEDGER_V1_VERSION_WASM_PATH").unwrap()).unwrap() +} + fn ledger_wasm() -> Vec { ic_test_utilities_load_wasm::load_wasm( std::env::var("CARGO_MANIFEST_DIR").unwrap(), @@ -153,8 +189,6 @@ fn encode_init_args(args: ic_ledger_suite_state_machine_tests::InitArgs) -> Ledg }, max_memo_length: None, feature_flags: args.feature_flags, - maximum_number_of_accounts: args.maximum_number_of_accounts, - accounts_overflow_trim_quantity: args.accounts_overflow_trim_quantity, }) } @@ -390,11 +424,6 @@ fn test_transfer_from_burn() { ic_ledger_suite_state_machine_tests::test_transfer_from_burn(ledger_wasm(), encode_init_args); } -#[test] -fn test_balances_overflow() { - ic_ledger_suite_state_machine_tests::test_balances_overflow(ledger_wasm(), encode_init_args); -} - #[test] fn test_archive_controllers() { ic_ledger_suite_state_machine_tests::test_archive_controllers(ledger_wasm()); @@ -438,7 +467,16 @@ fn test_block_transformation() { } #[test] -fn icrc1_test_upgrade_serialization() { +fn icrc1_test_upgrade_serialization_from_mainnet() { + icrc1_test_upgrade_serialization(ledger_mainnet_wasm()); +} + +#[test] +fn icrc1_test_upgrade_serialization_from_v2() { + icrc1_test_upgrade_serialization(ledger_mainnet_v2_wasm()); +} + +fn icrc1_test_upgrade_serialization(ledger_mainnet_wasm: Vec) { let minter = Arc::new(minter_identity()); let builder = LedgerInitArgsBuilder::with_symbol_and_name(TOKEN_SYMBOL, TOKEN_NAME) .with_minting_account(minter.sender().unwrap()) @@ -446,18 +484,18 @@ fn icrc1_test_upgrade_serialization() { let init_args = Encode!(&LedgerArgument::Init(builder.build())).unwrap(); let upgrade_args = Encode!(&LedgerArgument::Upgrade(None)).unwrap(); ic_ledger_suite_state_machine_tests::test_upgrade_serialization::( - ledger_mainnet_wasm(), + ledger_mainnet_wasm, ledger_wasm(), init_args, upgrade_args, minter, true, + true, ); } -#[ignore] // TODO: Re-enable as part of FI-1440 #[test] -fn icrc1_test_multi_step_migration() { +fn icrc1_test_multi_step_migration_from_mainnet() { ic_ledger_suite_state_machine_tests::icrc1_test_multi_step_migration( ledger_mainnet_wasm(), ledger_wasm_lowupgradeinstructionlimits(), @@ -465,6 +503,15 @@ fn icrc1_test_multi_step_migration() { ); } +#[test] +fn icrc1_test_multi_step_migration_from_v2() { + ic_ledger_suite_state_machine_tests::icrc1_test_multi_step_migration( + ledger_mainnet_v2_wasm(), + ledger_wasm_lowupgradeinstructionlimits(), + encode_init_args, + ); +} + #[test] fn icrc1_test_downgrade_from_incompatible_version() { ic_ledger_suite_state_machine_tests::test_downgrade_from_incompatible_version( @@ -472,13 +519,12 @@ fn icrc1_test_downgrade_from_incompatible_version() { ledger_wasm_nextledgerversion(), ledger_wasm(), encode_init_args, - true, + false, ); } -#[ignore] // TODO: Re-enable as part of FI-1440 #[test] -fn icrc1_test_stable_migration_endpoints_disabled() { +fn icrc1_test_stable_migration_endpoints_disabled_from_mainnet() { ic_ledger_suite_state_machine_tests::icrc1_test_stable_migration_endpoints_disabled( ledger_mainnet_wasm(), ledger_wasm_lowupgradeinstructionlimits(), @@ -487,9 +533,18 @@ fn icrc1_test_stable_migration_endpoints_disabled() { ); } -#[ignore] // TODO: Re-enable as part of FI-1440 #[test] -fn icrc1_test_incomplete_migration() { +fn icrc1_test_stable_migration_endpoints_disabled_from_v2() { + ic_ledger_suite_state_machine_tests::icrc1_test_stable_migration_endpoints_disabled( + ledger_mainnet_v2_wasm(), + ledger_wasm_lowupgradeinstructionlimits(), + encode_init_args, + vec![], + ); +} + +#[test] +fn icrc1_test_incomplete_migration_from_mainnet() { ic_ledger_suite_state_machine_tests::test_incomplete_migration( ledger_mainnet_wasm(), ledger_wasm_lowupgradeinstructionlimits(), @@ -497,9 +552,17 @@ fn icrc1_test_incomplete_migration() { ); } -#[ignore] // TODO: Re-enable as part of FI-1440 #[test] -fn icrc1_test_incomplete_migration_to_current() { +fn icrc1_test_incomplete_migration_from_v2() { + ic_ledger_suite_state_machine_tests::test_incomplete_migration( + ledger_mainnet_v2_wasm(), + ledger_wasm_lowupgradeinstructionlimits(), + encode_init_args, + ); +} + +#[test] +fn icrc1_test_incomplete_migration_to_current_from_mainnet() { ic_ledger_suite_state_machine_tests::test_incomplete_migration_to_current( ledger_mainnet_wasm(), ledger_wasm_lowupgradeinstructionlimits(), @@ -507,9 +570,17 @@ fn icrc1_test_incomplete_migration_to_current() { ); } -#[ignore] // TODO: Re-enable as part of FI-1440 #[test] -fn icrc1_test_migration_resumes_from_frozen() { +fn icrc1_test_incomplete_migration_to_current_from_v2() { + ic_ledger_suite_state_machine_tests::test_incomplete_migration_to_current( + ledger_mainnet_v2_wasm(), + ledger_wasm_lowupgradeinstructionlimits(), + encode_init_args, + ); +} + +#[test] +fn icrc1_test_migration_resumes_from_frozen_from_mainnet() { ic_ledger_suite_state_machine_tests::test_migration_resumes_from_frozen( ledger_mainnet_wasm(), ledger_wasm_lowupgradeinstructionlimits(), @@ -517,9 +588,17 @@ fn icrc1_test_migration_resumes_from_frozen() { ); } -#[ignore] // TODO: Re-enable as part of FI-1440 #[test] -fn icrc1_test_metrics_while_migrating() { +fn icrc1_test_migration_resumes_from_frozen_from_v2() { + ic_ledger_suite_state_machine_tests::test_migration_resumes_from_frozen( + ledger_mainnet_v2_wasm(), + ledger_wasm_lowupgradeinstructionlimits(), + encode_init_args, + ); +} + +#[test] +fn icrc1_test_metrics_while_migrating_from_mainnet() { ic_ledger_suite_state_machine_tests::test_metrics_while_migrating( ledger_mainnet_wasm(), ledger_wasm_lowupgradeinstructionlimits(), @@ -527,6 +606,24 @@ fn icrc1_test_metrics_while_migrating() { ); } +#[test] +fn icrc1_test_metrics_while_migrating_from_v2() { + ic_ledger_suite_state_machine_tests::test_metrics_while_migrating( + ledger_mainnet_v2_wasm(), + ledger_wasm_lowupgradeinstructionlimits(), + encode_init_args, + ); +} + +#[test] +fn icrc1_test_upgrade_from_v1_not_possible() { + ic_ledger_suite_state_machine_tests::test_upgrade_from_v1_not_possible( + ledger_mainnet_v1_wasm(), + ledger_wasm(), + encode_init_args, + ); +} + mod metrics { use crate::{encode_init_args, encode_upgrade_args, ledger_wasm}; use ic_ledger_suite_state_machine_tests::metrics::LedgerSuiteType; @@ -638,8 +735,6 @@ fn test_icrc2_feature_flag_doesnt_disable_icrc2_endpoints() { }, max_memo_length: None, feature_flags: Some(FeatureFlags { icrc2: false }), - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, })) .unwrap(); let ledger_id = env @@ -814,8 +909,6 @@ fn test_icrc3_get_archives() { }, max_memo_length: None, feature_flags: None, - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, }); let args = Encode!(&args).unwrap(); let ledger_id = env @@ -891,8 +984,6 @@ fn test_icrc3_get_blocks() { }, max_memo_length: None, feature_flags: None, - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, }); let args = Encode!(&args).unwrap(); let ledger_id = env @@ -1167,8 +1258,6 @@ fn test_icrc3_get_blocks_number_of_blocks_limit() { }, max_memo_length: None, feature_flags: None, - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, }); let args = Encode!(&args).unwrap(); @@ -1603,8 +1692,6 @@ mod verify_written_blocks { }, max_memo_length: None, feature_flags: Some(FeatureFlags { icrc2: true }), - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, }); let args = Encode!(&ledger_arg_init).unwrap(); @@ -1821,8 +1908,6 @@ mod incompatible_token_type_upgrade { }, max_memo_length: None, feature_flags: Some(FeatureFlags { icrc2: false }), - maximum_number_of_accounts: None, - accounts_overflow_trim_quantity: None, })) .unwrap() } diff --git a/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs b/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs index 7b97715f99d..bfa0c0d13f3 100644 --- a/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs +++ b/rs/ledger_suite/icrc1/tests/golden_state_upgrade_downgrade.rs @@ -14,7 +14,7 @@ use ic_ledger_suite_state_machine_tests::{ wait_ledger_ready, TransactionGenerationParameters, }; use ic_nns_test_utils_golden_nns_state::new_state_machine_with_golden_fiduciary_state_or_panic; -use ic_state_machine_tests::{StateMachine, UserError}; +use ic_state_machine_tests::{ErrorCode, StateMachine, UserError}; use icrc_ledger_types::icrc1::account::Account; use lazy_static::lazy_static; use std::str::FromStr; @@ -351,18 +351,19 @@ impl LedgerSuiteConfig { // Upgrade each canister twice to exercise pre-upgrade self.upgrade_index_or_panic(state_machine, &self.mainnet_wasms.index_wasm); self.upgrade_index_or_panic(state_machine, &self.mainnet_wasms.index_wasm); - self.upgrade_ledger( - state_machine, - &self.mainnet_wasms.ledger_wasm, - ExpectMigration::No, - ) - .expect("should downgrade to mainnet ledger version"); - self.upgrade_ledger( + match self.upgrade_ledger( state_machine, &self.mainnet_wasms.ledger_wasm, ExpectMigration::No, - ) - .expect("should downgrade to mainnet ledger version"); + ) { + Ok(_) => { + panic!("should not successfully downgrade ledger"); + } + Err(user_error) => user_error.assert_contains( + ErrorCode::CanisterCalledTrap, + "Trying to downgrade from incompatible version", + ), + } self.upgrade_archives_or_panic(state_machine, &self.mainnet_wasms.archive_wasm); self.upgrade_archives_or_panic(state_machine, &self.mainnet_wasms.archive_wasm); } diff --git a/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs b/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs index db8191ea320..d44725258db 100644 --- a/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs +++ b/rs/ledger_suite/icrc1/tests/upgrade_downgrade.rs @@ -77,12 +77,20 @@ fn should_upgrade_and_downgrade_ledger_canister_suite() { ) .unwrap(); - env.upgrade_canister( + match env.upgrade_canister( ledger_id, ledger_mainnet_wasm(), Encode!(&ledger_upgrade_arg).unwrap(), - ) - .unwrap(); + ) { + Ok(_) => { + panic!("Upgrade to mainnet should fail!") + } + Err(e) => { + assert!(e + .description() + .contains("Trying to downgrade from incompatible version")) + } + }; } fn default_archive_options() -> ArchiveOptions { diff --git a/rs/ledger_suite/tests/sm-tests/src/lib.rs b/rs/ledger_suite/tests/sm-tests/src/lib.rs index 466270172c6..c38b394251a 100644 --- a/rs/ledger_suite/tests/sm-tests/src/lib.rs +++ b/rs/ledger_suite/tests/sm-tests/src/lib.rs @@ -2413,6 +2413,7 @@ pub fn test_upgrade_serialization( upgrade_args: Vec, minter: Arc, verify_blocks: bool, + mainnet_on_prev_version: bool, ) where Tokens: TokensType + Default + std::fmt::Display + From, { @@ -2470,20 +2471,33 @@ pub fn test_upgrade_serialization( }; // Test if the old serialized approvals and balances are correctly deserialized - // TODO: Expected migration steps should be 1 again in FI-1440. - // test_upgrade(ledger_wasm_current.clone(), 1); + let expected_steps = if mainnet_on_prev_version { 1 } else { 0 }; + test_upgrade(ledger_wasm_current.clone(), expected_steps); // Test the new wasm serialization test_upgrade(ledger_wasm_current.clone(), 0); // Test deserializing from memory manager test_upgrade(ledger_wasm_current.clone(), 0); - // Downgrade to mainnet should succeed since they are both the same version wrt. - // migration to stable structures. - env.upgrade_canister( + // Downgrade to mainnet if possible. + match env.upgrade_canister( ledger_id, ledger_wasm_mainnet.clone(), Encode!(&LedgerArgument::Upgrade(None)).unwrap(), - ) - .expect("Downgrading to mainnet should succeed"); + ) { + Ok(_) => { + if mainnet_on_prev_version { + panic!("Upgrade from future ledger version should fail!") + } + } + Err(e) => { + if mainnet_on_prev_version { + assert!(e + .description() + .contains("Trying to downgrade from incompatible version")) + } else { + panic!("Upgrade to mainnet should succeed!") + } + } + }; if verify_blocks { // This will also verify the ledger blocks. // The current implementation of the InMemoryLedger cannot get blocks @@ -2524,7 +2538,7 @@ pub fn icrc1_test_multi_step_migration( }, ]; let mut initial_balances = vec![]; - let all_accounts = [accounts.clone(), additional_accounts.clone()].concat(); + let mut all_accounts = [accounts.clone(), additional_accounts.clone()].concat(); for (index, account) in all_accounts.iter().enumerate() { initial_balances.push((*account, 10_000_000u64 + index as u64)); } @@ -2564,6 +2578,11 @@ pub fn icrc1_test_multi_step_migration( )); } } + for i in 7..7 + 30 { + let to = Account::from(PrincipalId::new_user_test_id(i).0); + transfer(&env, canister_id, accounts[0], to, 100).expect("failed to transfer funds"); + all_accounts.push(to); + } let mut balances = BTreeMap::new(); for account in &all_accounts { balances.insert(account, Nat::from(balance_of(&env, canister_id, *account))); @@ -2579,7 +2598,7 @@ pub fn icrc1_test_multi_step_migration( ) .unwrap(); - wait_ledger_ready(&env, canister_id, 10); + wait_ledger_ready(&env, canister_id, 20); let stable_upgrade_migration_steps = parse_metric(&env, canister_id, "ledger_stable_upgrade_migration_steps"); @@ -2748,6 +2767,11 @@ pub fn icrc1_test_stable_migration_endpoints_disabled( send_approval(&env, canister_id, account.owner, &approve_args).expect("approval failed"); } + for i in 2..30 { + let to = Account::from(PrincipalId::new_user_test_id(i).0); + transfer(&env, canister_id, account, to, 100).expect("failed to transfer funds"); + } + env.upgrade_canister( canister_id, ledger_wasm_current_lowinstructionlimits, @@ -2807,7 +2831,7 @@ pub fn icrc1_test_stable_migration_endpoints_disabled( test_endpoint(endpoint_name, args, true); } - wait_ledger_ready(&env, canister_id, 10); + wait_ledger_ready(&env, canister_id, 20); test_endpoint("icrc1_transfer", Encode!(&transfer_args).unwrap(), false); test_endpoint("icrc2_approve", Encode!(&approve_args).unwrap(), false); @@ -2842,8 +2866,10 @@ pub fn test_incomplete_migration( ); const APPROVE_AMOUNT: u64 = 150_000; + const TRANSFER_AMOUNT: u64 = 100; const NUM_APPROVALS: u64 = 20; + const NUM_TRANSFERS: u64 = 30; let send_approvals = || { for i in 2..2 + NUM_APPROVALS { @@ -2856,6 +2882,12 @@ pub fn test_incomplete_migration( send_approvals(); + for i in 2..2 + NUM_TRANSFERS { + let to = Account::from(PrincipalId::new_user_test_id(i).0); + transfer(&env, canister_id, account, to, TRANSFER_AMOUNT + FEE) + .expect("failed to transfer funds"); + } + let check_approvals = |non_zero_from: u64| { for i in 2..2 + NUM_APPROVALS { let allowance = Account::get_allowance( @@ -2872,8 +2904,23 @@ pub fn test_incomplete_migration( assert_eq!(allowance.allowance, expected_allowance); } }; - + let check_balances = |non_zero_from: u64| { + for i in 2..2 + NUM_TRANSFERS { + let balance = balance_of( + &env, + canister_id, + Account::from(PrincipalId::new_user_test_id(i).0), + ); + let expected_balance = if i < non_zero_from { + Nat::from(0u64) + } else { + Nat::from(TRANSFER_AMOUNT + FEE) + }; + assert_eq!(balance, expected_balance); + } + }; check_approvals(2); + check_balances(2); env.upgrade_canister( canister_id, @@ -2906,9 +2953,12 @@ pub fn test_incomplete_migration( let spender = Account::from(PrincipalId::new_user_test_id(i).0); let approve_args = default_approve_args(spender, 0); send_approval(&env, canister_id, account.owner, &approve_args).expect("approval failed"); + transfer(&env, canister_id, spender, account, TRANSFER_AMOUNT) + .expect("failed to transfer funds"); } check_approvals(5); + check_balances(5); env.upgrade_canister( canister_id, @@ -2919,6 +2969,7 @@ pub fn test_incomplete_migration( wait_ledger_ready(&env, canister_id, 20); check_approvals(5); + check_balances(5); } pub fn test_incomplete_migration_to_current( @@ -2939,8 +2990,10 @@ pub fn test_incomplete_migration_to_current( ); const APPROVE_AMOUNT: u64 = 150_000; + const TRANSFER_AMOUNT: u64 = 100; const NUM_APPROVALS: u64 = 20; + const NUM_TRANSFERS: u64 = 30; let send_approvals = || { for i in 2..2 + NUM_APPROVALS { @@ -2953,7 +3006,13 @@ pub fn test_incomplete_migration_to_current( send_approvals(); - let check_approvals = |non_zero_from: u64| { + for i in 2..2 + NUM_TRANSFERS { + let to = Account::from(PrincipalId::new_user_test_id(i).0); + transfer(&env, canister_id, account, to, TRANSFER_AMOUNT + i) + .expect("failed to transfer funds"); + } + + let check_approvals = || { for i in 2..2 + NUM_APPROVALS { let allowance = Account::get_allowance( &env, @@ -2961,16 +3020,22 @@ pub fn test_incomplete_migration_to_current( account, Account::from(PrincipalId::new_user_test_id(i).0), ); - let expected_allowance = if i < non_zero_from { - Nat::from(0u64) - } else { - Nat::from(APPROVE_AMOUNT) - }; - assert_eq!(allowance.allowance, expected_allowance); + assert_eq!(allowance.allowance, Nat::from(APPROVE_AMOUNT)); + } + }; + let check_balances = || { + for i in 2..2 + NUM_TRANSFERS { + let balance = balance_of( + &env, + canister_id, + Account::from(PrincipalId::new_user_test_id(i).0), + ); + assert_eq!(balance, Nat::from(TRANSFER_AMOUNT + i)); } }; - check_approvals(2); + check_approvals(); + check_balances(); env.upgrade_canister( canister_id, @@ -2997,7 +3062,8 @@ pub fn test_incomplete_migration_to_current( .unwrap(); wait_ledger_ready(&env, canister_id, 20); - check_approvals(2); + check_approvals(); + check_balances(); } pub fn test_migration_resumes_from_frozen( @@ -3028,7 +3094,10 @@ pub fn test_migration_resumes_from_frozen( .unwrap(); const APPROVE_AMOUNT: u64 = 150_000; + const TRANSFER_AMOUNT: u64 = 100; + const NUM_APPROVALS: u64 = 20; + const NUM_TRANSFERS: u64 = 30; let send_approvals = || { for i in 2..2 + NUM_APPROVALS { @@ -3041,6 +3110,12 @@ pub fn test_migration_resumes_from_frozen( send_approvals(); + for i in 2..2 + NUM_TRANSFERS { + let to = Account::from(PrincipalId::new_user_test_id(i).0); + transfer(&env, canister_id, account, to, TRANSFER_AMOUNT + i) + .expect("failed to transfer funds"); + } + let check_approvals = || { for i in 2..2 + NUM_APPROVALS { let allowance = Account::get_allowance( @@ -3052,8 +3127,19 @@ pub fn test_migration_resumes_from_frozen( assert_eq!(allowance.allowance, Nat::from(APPROVE_AMOUNT)); } }; + let check_balances = || { + for i in 2..2 + NUM_TRANSFERS { + let balance = balance_of( + &env, + canister_id, + Account::from(PrincipalId::new_user_test_id(i).0), + ); + assert_eq!(balance, Nat::from(TRANSFER_AMOUNT + i)); + } + }; check_approvals(); + check_balances(); env.upgrade_canister( canister_id, @@ -3099,6 +3185,7 @@ pub fn test_migration_resumes_from_frozen( assert!(!is_ledger_ready()); wait_ledger_ready(&env, canister_id, 20); check_approvals(); + check_balances(); } pub fn test_metrics_while_migrating( @@ -3124,6 +3211,11 @@ pub fn test_metrics_while_migrating( send_approval(&env, canister_id, account.owner, &approve_args).expect("approval failed"); } + for i in 2..30 { + let to = Account::from(PrincipalId::new_user_test_id(i).0); + transfer(&env, canister_id, account, to, 100).expect("failed to transfer funds"); + } + env.upgrade_canister( canister_id, ledger_wasm_current_lowinstructionlimits, @@ -3154,7 +3246,7 @@ pub fn test_metrics_while_migrating( .expect("failed to decode is_ledger_ready response"); assert!(!is_ledger_ready); - wait_ledger_ready(&env, canister_id, 10); + wait_ledger_ready(&env, canister_id, 20); let metrics = retrieve_metrics(&env, canister_id); assert!( @@ -3171,6 +3263,32 @@ pub fn test_metrics_while_migrating( ); } +pub fn test_upgrade_from_v1_not_possible( + ledger_wasm_mainnet_v1: Vec, + ledger_wasm_current: Vec, + encode_init_args: fn(InitArgs) -> T, +) where + T: CandidType, +{ + // Setup ledger with v1 state that does not use UPGRADES_MEMORY. + let (env, canister_id) = setup(ledger_wasm_mainnet_v1, encode_init_args, vec![]); + + match env.upgrade_canister( + canister_id, + ledger_wasm_current, + Encode!(&LedgerArgument::Upgrade(None)).unwrap(), + ) { + Ok(_) => { + panic!("Upgrade from V1 should fail!") + } + Err(e) => { + assert!(e + .description() + .contains("Cannot upgrade from scratch stable memory, please upgrade to memory manager first.")); + } + }; +} + pub fn default_approve_args(spender: impl Into, amount: u64) -> ApproveArgs { ApproveArgs { from_subaccount: None,