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

test(ICP_Ledger): FI-1531: Use InMemoryLedger in ICP golden state test #2768

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions rs/ledger_suite/icp/index/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ fn assert_ledger_index_parity(
ledger_id: CanisterId,
index_id: CanisterId,
) -> usize {
let ledger_blocks = icp_get_blocks(env, ledger_id);
let ledger_blocks = icp_get_blocks(env, ledger_id, None, None);
let index_blocks = index_get_blocks(env, index_id);
assert_eq!(ledger_blocks, index_blocks);
ledger_blocks.len()
Expand All @@ -496,7 +496,7 @@ fn assert_ledger_index_parity_query_blocks_and_query_encoded_blocks(
ledger_id: CanisterId,
index_id: CanisterId,
) {
let ledger_blocks = icp_get_blocks(env, ledger_id);
let ledger_blocks = icp_get_blocks(env, ledger_id, None, None);
let index_blocks = index_get_blocks(env, index_id);
let ledger_unencoded_blocks = icp_query_blocks(env, ledger_id);
assert_eq!(ledger_blocks, index_blocks);
Expand Down Expand Up @@ -914,7 +914,7 @@ fn assert_ledger_index_block_transaction_parity(
);

// verify that the ledger block is as expected
let ledger_blocks = icp_get_blocks(&setup.env, setup.ledger_id);
let ledger_blocks = icp_get_blocks(&setup.env, setup.ledger_id, None, None);
assert_eq!(ledger_blocks.len(), expected_ledger_block_index + 1);
let ledger_parent_block = ledger_blocks
.get(expected_ledger_block_index - 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ use icp_ledger::{
};
use on_wire::FromWire;

pub fn icp_get_blocks(env: &StateMachine, ledger_id: CanisterId) -> Vec<icp_ledger::Block> {
pub fn icp_get_blocks(
env: &StateMachine,
ledger_id: CanisterId,
start_index: Option<u64>,
num_blocks: Option<u64>,
) -> Vec<icp_ledger::Block> {
let req = GetBlocksArgs {
start: 0u64,
length: u32::MAX as usize,
start: start_index.unwrap_or(0u64),
length: num_blocks.unwrap_or(u32::MAX as u64) as usize,
};
let req = Encode!(&req).expect("Failed to encode GetBlocksArgs");
let res = env
Expand Down
186 changes: 181 additions & 5 deletions rs/ledger_suite/icp/tests/golden_nns_state.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
use candid::{Decode, Encode};
use candid::{Decode, Encode, Principal};
use canister_test::Wasm;
use ic_base_types::CanisterId;
use ic_ledger_core::block::BlockType;
use ic_ledger_suite_state_machine_tests::{generate_transactions, TransactionGenerationParameters};
use ic_ledger_core::Tokens;
use ic_ledger_suite_state_machine_tests::in_memory_ledger::{
BlockConsumer, BurnsWithoutSpender, InMemoryLedger,
};
use ic_ledger_suite_state_machine_tests::{
generate_transactions, get_blocks, TransactionGenerationParameters,
};
use ic_ledger_test_utils::state_machine_helpers::index::{
get_all_blocks, wait_until_sync_is_completed,
};
Expand All @@ -16,7 +22,9 @@ use ic_nns_constants::{
};
use ic_nns_test_utils_golden_nns_state::new_state_machine_with_golden_nns_state_or_panic;
use ic_state_machine_tests::StateMachine;
use icp_ledger::{Archives, FeatureFlags, LedgerCanisterPayload, UpgradeArgs};
use icp_ledger::{
AccountIdentifier, Archives, Block, FeatureFlags, LedgerCanisterPayload, UpgradeArgs,
};
use std::time::Instant;

const INDEX_CANISTER_ID: CanisterId =
Expand All @@ -29,12 +37,157 @@ const APPROVE_MULTIPLIER: u64 = 100;
const TRANSFER_FROM_MULTIPLIER: u64 = 10;
const BURN_MULTIPLIER: u64 = 1;

struct FetchedBlocks {
#[allow(dead_code)]
blocks: Vec<Block>,
#[allow(dead_code)]
start_index: u64,
}

struct LedgerState {
in_memory_ledger: InMemoryLedger<AccountIdentifier, Tokens>,
num_blocks: u64,
}

impl LedgerState {
fn assert_eq(&self, other: &Self) {
assert_eq!(
other.num_blocks, self.num_blocks,
"Number of blocks ({}) does not match number of blocks in previous state ({})",
self.num_blocks, other.num_blocks,
);
assert!(
other.in_memory_ledger == self.in_memory_ledger,
"In-memory ledger state does not match previous state"
);
}

/// Fetch the next blocks from the ledger canister and ingest them into the in-memory ledger.
/// If `total_num_blocks` is `None`, fetch all blocks from the ledger canister, otherwise fetch
/// `total_num_blocks - self.num_blocks` blocks (some amount of latest blocks that the in-memory
/// ledger does not hold yet).
fn fetch_and_ingest_next_ledger_and_archive_blocks(
&mut self,
state_machine: &StateMachine,
canister_id: CanisterId,
total_num_blocks: Option<u64>,
) -> FetchedBlocks {
let num_blocks = total_num_blocks
.unwrap_or(u64::MAX)
.saturating_sub(self.num_blocks);
let start_index = self.num_blocks;
let blocks = icp_get_blocks(
state_machine,
canister_id,
Some(start_index),
Some(num_blocks),
);
self.num_blocks = self
.num_blocks
.checked_add(blocks.len() as u64)
.expect("number of blocks should fit in u64");
self.in_memory_ledger.consume_blocks(&blocks);
FetchedBlocks {
blocks,
start_index,
}
}

fn new(burns_without_spender: Option<BurnsWithoutSpender<AccountIdentifier>>) -> Self {
let in_memory_ledger = InMemoryLedger::new(burns_without_spender);
Self {
in_memory_ledger,
num_blocks: 0,
}
}

fn verify_balances_and_allowances(
&self,
state_machine: &StateMachine,
canister_id: CanisterId,
) {
let num_ledger_blocks =
get_blocks(state_machine, Principal::from(canister_id), 0, 0).chain_length;
self.in_memory_ledger.verify_balances_and_allowances(
state_machine,
canister_id,
num_ledger_blocks,
);
}

/// Verify the ledger state and generate new transactions. In particular:
/// - Create a new instance of an in-memory ledger by fetching blocks from the ledger
/// - If a previous ledger state is provided, only fetch the blocks that were present when
/// the previous state was generated.
/// - Verify that the balances and allowances in the in-memory ledger match the ledger
/// canister state
/// - If a previous ledger state is provided, assert that the state of the newly-generated
/// in-memory ledger state matches that of the previous state
/// - Generate transactions on the ledger canister
/// - Fetch all blocks from the ledger canister into the new `ledger_state`
/// - Return the new `ledger_state`
fn verify_state_and_generate_transactions(
state_machine: &StateMachine,
ledger_id: CanisterId,
_index_id: CanisterId,
burns_without_spender: Option<BurnsWithoutSpender<AccountIdentifier>>,
previous_ledger_state: Option<LedgerState>,
) -> Self {
let num_blocks_to_fetch = previous_ledger_state
.as_ref()
.map(|previous_ledger_state| previous_ledger_state.num_blocks);

let mut ledger_state = LedgerState::new(burns_without_spender);
// Only fetch the blocks that were present when the previous state was generated. This is
// necessary since there may have been in-transit messages for the ledger in the backup,
// or new transactions triggered e.g., by timers running in other canisters on the subnet,
// that get applied after the `StateMachine` is initialized, and are not part of the
// transactions in `generate_transactions`.
// FIXME: The return value should be used as an optimization when fetching blocks from the
// index to compare ledger, archive, and index block parity
let _ledger_and_archive_blocks = ledger_state
.fetch_and_ingest_next_ledger_and_archive_blocks(
state_machine,
ledger_id,
num_blocks_to_fetch,
);
ledger_state.verify_balances_and_allowances(state_machine, ledger_id);
// Parity between the blocks in the ledger+archive, and those in the index, is verified separately
// TODO: Refactor?
// Verify the reconstructed ledger state matches the previous state
if let Some(previous_ledger_state) = &previous_ledger_state {
ledger_state.assert_eq(previous_ledger_state);
}
generate_transactions(
state_machine,
ledger_id,
TransactionGenerationParameters {
mint_multiplier: MINT_MULTIPLIER,
transfer_multiplier: TRANSFER_MULTIPLIER,
approve_multiplier: APPROVE_MULTIPLIER,
transfer_from_multiplier: TRANSFER_FROM_MULTIPLIER,
burn_multiplier: BURN_MULTIPLIER,
num_transactions_per_type: NUM_TRANSACTIONS_PER_TYPE,
},
);
// Fetch all blocks into the new `ledger_state`. This call only retrieves blocks that were
// not fetched in the previous call to `fetch_next_blocks`.
let _ledger_and_archive_blocks = ledger_state
.fetch_and_ingest_next_ledger_and_archive_blocks(state_machine, ledger_id, None);
// Parity between the blocks in the ledger+archive, and those in the index, is verified separately
// TODO: Refactor?
ledger_state
}
}

/// Create a state machine with the golden NNS state, then upgrade and downgrade the ICP
/// ledger canister suite.
#[test]
fn should_create_state_machine_with_golden_nns_state() {
let setup = Setup::new();
let mut setup = Setup::new();

// Verify ledger balance and allowance state
setup.verify_state();
// Verify ledger, archives, and index block parity
setup.verify_ledger_archive_index_block_parity();

Expand All @@ -43,19 +196,29 @@ fn should_create_state_machine_with_golden_nns_state() {
// Upgrade again to test the pre-upgrade
setup.upgrade_to_master();

// Verify ledger balance and allowance state
setup.verify_state();
// Verify ledger, archives, and index block parity
setup.verify_ledger_archive_index_block_parity();

setup.perform_transactions();

// Verify ledger balance and allowance state
setup.verify_state();

// Downgrade all the canisters to the mainnet version
setup.downgrade_to_mainnet();

// Verify ledger balance and allowance state
setup.verify_state();
// Verify ledger, archives, and index block parity
setup.verify_ledger_archive_index_block_parity();

setup.perform_transactions();

// Verify ledger balance and allowance state
setup.verify_state();
// Verify ledger, archives, and index block parity
setup.verify_ledger_archive_index_block_parity();
}

Expand All @@ -69,6 +232,7 @@ struct Setup {
state_machine: StateMachine,
master_wasms: Wasms,
mainnet_wasms: Wasms,
previous_ledger_state: Option<LedgerState>,
}

impl Setup {
Expand All @@ -91,6 +255,7 @@ impl Setup {
state_machine,
master_wasms,
mainnet_wasms,
previous_ledger_state: None,
}
}

Expand Down Expand Up @@ -126,10 +291,21 @@ impl Setup {
println!("Time taken for index to sync: {:?}", start.elapsed());
}

pub fn verify_state(&mut self) {
self.previous_ledger_state = Some(LedgerState::verify_state_and_generate_transactions(
&self.state_machine,
LEDGER_CANISTER_ID,
INDEX_CANISTER_ID,
None,
self.previous_ledger_state.take(),
));
}

pub fn verify_ledger_archive_index_block_parity(&self) {
println!("Verifying ledger, archive, and index block parity");
println!("Retrieving blocks from the ledger and archives");
let ledger_blocks = icp_get_blocks(&self.state_machine, LEDGER_CANISTER_ID);
// FIXME: Improve this - do not fetch all blocks every time the index parity is verified
let ledger_blocks = icp_get_blocks(&self.state_machine, LEDGER_CANISTER_ID, None, None);
// Wait for the index to sync with the ledger and archives
wait_until_sync_is_completed(&self.state_machine, INDEX_CANISTER_ID, LEDGER_CANISTER_ID);
println!("Retrieving {} blocks from the index", ledger_blocks.len());
Expand Down
Loading