Skip to content
13 changes: 13 additions & 0 deletions rs/ledger_suite/common/ledger_canister_core/src/archive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ use ic_ledger_core::block::EncodedBlock;
/// 10 trillion cycles.
pub const DEFAULT_CYCLES_FOR_ARCHIVE_CREATION: u64 = 10_000_000_000_000;

/// The default maximum number of transactions returned by an archive's
/// `get_transactions` endpoint, applied when `max_transactions_per_response` is
/// not set. Shared with the archive canister so that the effective value the
/// ledger reports cannot drift from the one the archive enforces.
pub const DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE: u64 = 2000;

fn default_cycles_for_archive_creation() -> u64 {
0
}
Expand Down Expand Up @@ -199,6 +205,13 @@ impl<Rt: Runtime, Wasm: ArchiveCanisterWasm> Archive<Rt, Wasm> {
pub fn nodes(&self) -> &[CanisterId] {
&self.nodes
}

/// The maximum number of transactions an archive of this ledger returns per
/// response, with the default applied when the option is unset.
pub fn effective_max_transactions_per_response(&self) -> u64 {
self.max_transactions_per_response
.unwrap_or(DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE)
}
}

/// Grabs a write lock on the archive and executes a synchronous function under the lock.
Expand Down
76 changes: 74 additions & 2 deletions rs/ledger_suite/icp/ledger/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use ic_cdk::{post_upgrade, pre_upgrade, query, update};
use ic_http_types::{HttpRequest, HttpResponse, HttpResponseBuilder};
use ic_icrc1::endpoints::{StandardRecord, convert_transfer_error};
use ic_ledger_canister_core::ledger::{LedgerContext, LedgerData};
use ic_ledger_canister_core::runtime::heap_memory_size_bytes;
use ic_ledger_canister_core::runtime::{Runtime, heap_memory_size_bytes};
use ic_ledger_canister_core::{
archive::{Archive, ArchiveOptions},
archive::{Archive, ArchiveCanisterWasm, ArchiveOptions},
ledger::{
LedgerAccess, TransferError as CoreTransferError, apply_transaction, archive_blocks,
block_locations, find_block_in_archive,
Expand Down Expand Up @@ -1130,6 +1130,74 @@ fn get_nodes_() {
})
}

/// Exposes the archiving configuration that is otherwise not observable from
/// outside the canister. All values are the effective ones in force, i.e. with
/// the defaults of the optional `ArchiveOptions` fields already applied.
fn encode_archive_config_metrics<Rt, Wasm>(
w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>,
archive: &Archive<Rt, Wasm>,
) -> std::io::Result<()>
where
Rt: Runtime,
Wasm: ArchiveCanisterWasm,
{
w.encode_gauge(
"ledger_archive_trigger_threshold",
archive.trigger_threshold as f64,
"The number of blocks which, when exceeded, triggers archiving.",
)?;
w.encode_gauge(
"ledger_archive_num_blocks_to_archive",
archive.num_blocks_to_archive as f64,
"The number of blocks archived when the trigger threshold is exceeded.",
)?;
w.encode_gauge(
"ledger_archive_node_max_memory_size_bytes",
archive.node_max_memory_size_bytes as f64,
"Maximum number of bytes an archive canister of this ledger may store.",
)?;
w.encode_gauge(
"ledger_archive_max_message_size_bytes",
archive.max_message_size_bytes as f64,
Comment thread
mbjorkqvist marked this conversation as resolved.
Outdated
"Maximum size in bytes of a message sent to an archive canister.",
)?;
w.encode_gauge(
"ledger_archive_cycles_for_archive_creation",
archive.cycles_for_archive_creation as f64,
"Cycles attached to the call creating a new archive canister.",
)?;
w.encode_gauge(
"ledger_archive_max_transactions_per_response",
archive.effective_max_transactions_per_response() as f64,
"Maximum number of transactions an archive returns per response.",
)?;
Ok(())
}

/// Exposes the transaction-deduplication configuration, which is not observable
/// from outside the canister.
fn encode_dedup_config_metrics<LD: LedgerData>(
w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>,
ledger: &LD,
) -> std::io::Result<()> {
w.encode_gauge(
"ledger_transaction_window_seconds",
ledger.transaction_window().as_secs() as f64,
"Length of the transaction deduplication window in seconds.",
)?;
w.encode_gauge(
"ledger_max_transactions_in_window",
ledger.max_transactions_in_window() as f64,
"Maximum number of transactions retained in the deduplication window.",
)?;
w.encode_gauge(
"ledger_max_transactions_to_purge",
ledger.max_transactions_to_purge() as f64,
"Maximum number of transactions purged from the deduplication window per operation.",
)?;
Ok(())
}

fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::io::Result<()> {
let ledger = LEDGER
.try_read()
Expand All @@ -1144,6 +1212,10 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::i
*MAX_MESSAGE_SIZE_BYTES.read().unwrap() as f64,
"Maximum inter-canister message size in bytes.",
)?;
if let Some(archive) = archive_guard.as_ref() {
encode_archive_config_metrics(w, archive)?;
}
encode_dedup_config_metrics(w, &*ledger)?;
w.encode_gauge(
"ledger_stable_memory_pages",
ic_cdk::stable::stable_size() as f64,
Expand Down
73 changes: 71 additions & 2 deletions rs/ledger_suite/icp/ledger/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use ic_icrc1_test_utils::minter_identity;
use ic_ledger_core::block::BlockIndex;
use ic_ledger_core::{Tokens, block::BlockType};
use ic_ledger_suite_state_machine_helpers::{
AllowanceProvider, balance_of, icrc21_consent_message, send_approval, send_transfer,
send_transfer_from, supported_standards, total_supply, transfer,
AllowanceProvider, balance_of, icrc21_consent_message, parse_metric, send_approval,
send_transfer, send_transfer_from, supported_standards, total_supply, transfer,
};
use ic_ledger_suite_state_machine_tests::archiving::icp_archives;
use ic_ledger_suite_state_machine_tests::{
Expand Down Expand Up @@ -2624,6 +2624,75 @@ fn test_burn_whole_balance() {
assert_eq!(balance_of(&env, canister_id, p1.0), 0);
}

#[test]
fn test_archive_and_dedup_config_metrics() {
const TRIGGER_THRESHOLD: usize = 17;
const NUM_BLOCKS_TO_ARCHIVE: usize = 5;
const NODE_MAX_MEMORY_SIZE_BYTES: u64 = 123_456;
const MAX_MESSAGE_SIZE_BYTES: u64 = 64 * 1024;
const CYCLES_FOR_ARCHIVE_CREATION: u64 = 7_000_000_000;
const MAX_TRANSACTIONS_PER_RESPONSE: u64 = 99;
const TRANSACTION_WINDOW: Duration = Duration::from_secs(3600);

let env = StateMachine::new();
let payload = LedgerCanisterInitPayload::builder()
.minting_account(MINTER.into())
.icrc1_minting_account(MINTER)
.transfer_fee(Tokens::from_e8s(10_000))
.token_symbol_and_name("ICP", "Internet Computer")
.transaction_window(TRANSACTION_WINDOW)
.archive_options(ArchiveOptions {
trigger_threshold: TRIGGER_THRESHOLD,
num_blocks_to_archive: NUM_BLOCKS_TO_ARCHIVE,
node_max_memory_size_bytes: Some(NODE_MAX_MEMORY_SIZE_BYTES),
max_message_size_bytes: Some(MAX_MESSAGE_SIZE_BYTES),
controller_id: PrincipalId::new_user_test_id(100),
more_controller_ids: None,
cycles_for_archive_creation: Some(CYCLES_FOR_ARCHIVE_CREATION),
max_transactions_per_response: Some(MAX_TRANSACTIONS_PER_RESPONSE),
})
.build()
.unwrap();
let ledger_id = env
.install_canister(ledger_wasm(), Encode!(&payload).unwrap(), None)
.expect("Unable to install the Ledger canister");

let metric = |name: &str| parse_metric(&env, ledger_id, name);

assert_eq!(
metric("ledger_archive_trigger_threshold"),
TRIGGER_THRESHOLD as u64
);
assert_eq!(
metric("ledger_archive_num_blocks_to_archive"),
NUM_BLOCKS_TO_ARCHIVE as u64
);
assert_eq!(
metric("ledger_archive_node_max_memory_size_bytes"),
NODE_MAX_MEMORY_SIZE_BYTES
);
assert_eq!(
metric("ledger_archive_max_message_size_bytes"),
MAX_MESSAGE_SIZE_BYTES
);
assert_eq!(
metric("ledger_archive_cycles_for_archive_creation"),
CYCLES_FOR_ARCHIVE_CREATION
);
assert_eq!(
metric("ledger_archive_max_transactions_per_response"),
MAX_TRANSACTIONS_PER_RESPONSE
);

assert_eq!(
metric("ledger_transaction_window_seconds"),
TRANSACTION_WINDOW.as_secs(),
"the ICP ledger's transaction window is configurable, so the metric must reflect the configured value"
);
assert!(metric("ledger_max_transactions_in_window") > 0);
assert!(metric("ledger_max_transactions_to_purge") > 0);
}

#[test]
fn test_change_initially_set_archive_options() {
const ARCHIVE_TRIGGER_THRESHOLD: usize = 10;
Expand Down
8 changes: 4 additions & 4 deletions rs/ledger_suite/icrc1/archive/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const GIB: u64 = 1024 * 1024 * 1024;
const DEFAULT_MEMORY_LIMIT: u64 = 3 * GIB;

/// The maximum number of blocks to return in a single get_transactions request.
const DEFAULT_MAX_TRANSACTIONS_PER_GET_TRANSACTION_RESPONSE: u64 = 2000;
use ic_ledger_canister_core::archive::DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE;

/// The maximum number of Wasm pages that we allow to use for the stable storage.
const NUM_WASM_PAGES: u64 = 4 * GIB / WASM_PAGE_SIZE;
Expand Down Expand Up @@ -108,7 +108,7 @@ impl Default for ArchiveConfig {
max_memory_size_bytes: 0,
block_index_offset: 0,
ledger_id: Principal::management_canister(),
max_transactions_per_response: DEFAULT_MAX_TRANSACTIONS_PER_GET_TRANSACTION_RESPONSE,
max_transactions_per_response: DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE,
token_type: wasm_token_type(),
}
}
Expand Down Expand Up @@ -165,8 +165,8 @@ fn init(
let max_memory_size_bytes = max_memory_size_bytes
.unwrap_or(DEFAULT_MEMORY_LIMIT)
.min(DEFAULT_MEMORY_LIMIT);
let max_transactions_per_response = max_transactions_per_response
.unwrap_or(DEFAULT_MAX_TRANSACTIONS_PER_GET_TRANSACTION_RESPONSE);
let max_transactions_per_response =
max_transactions_per_response.unwrap_or(DEFAULT_MAX_TRANSACTIONS_PER_RESPONSE);
cell.borrow_mut()
.set(ArchiveConfig {
max_memory_size_bytes,
Expand Down
76 changes: 76 additions & 0 deletions rs/ledger_suite/icrc1/ledger/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ use ic_icrc1_ledger::{
InitArgs, LEDGER_VERSION, Ledger, LedgerArgument, UPGRADES_MEMORY, balances_len,
get_allowances, read_first_balance, wasm_token_type,
};
use ic_ledger_canister_core::archive::{Archive, ArchiveCanisterWasm};
use ic_ledger_canister_core::ledger::{
LedgerAccess, LedgerContext, LedgerData, TransferError as CoreTransferError, apply_transaction,
archive_blocks,
};
use ic_ledger_canister_core::runtime::Runtime;
use ic_ledger_canister_core::runtime::heap_memory_size_bytes;
use ic_ledger_core::block::BlockIndex;
use ic_ledger_core::timestamp::TimeStamp;
Expand Down Expand Up @@ -284,6 +286,76 @@ fn log_message(msg: &str) {
log!(&LOG, "{msg}");
}

/// Exposes the transaction-deduplication configuration, which is not observable
/// from outside the canister. The window is a constant on the ICRC ledger and a
/// configured value on the ICP ledger; both are reported here as the effective
/// value in force.
fn encode_dedup_config_metrics<LD: LedgerData>(
w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>,
ledger: &LD,
) -> std::io::Result<()> {
w.encode_gauge(
"ledger_transaction_window_seconds",
ledger.transaction_window().as_secs() as f64,
"Length of the transaction deduplication window in seconds.",
)?;
w.encode_gauge(
"ledger_max_transactions_in_window",
ledger.max_transactions_in_window() as f64,
"Maximum number of transactions retained in the deduplication window.",
)?;
w.encode_gauge(
"ledger_max_transactions_to_purge",
ledger.max_transactions_to_purge() as f64,
"Maximum number of transactions purged from the deduplication window per operation.",
)?;
Ok(())
}

/// Exposes the archiving configuration that is otherwise not observable from
/// outside the canister. All values are the effective ones in force, i.e. with
/// the defaults of the optional `ArchiveOptions` fields already applied.
fn encode_archive_config_metrics<Rt, Wasm>(
w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>,
archive: &Archive<Rt, Wasm>,
) -> std::io::Result<()>
where
Rt: Runtime,
Wasm: ArchiveCanisterWasm,
{
w.encode_gauge(
"ledger_archive_trigger_threshold",
archive.trigger_threshold as f64,
"The number of blocks which, when exceeded, triggers archiving.",
)?;
w.encode_gauge(
"ledger_archive_num_blocks_to_archive",
archive.num_blocks_to_archive as f64,
"The number of blocks archived when the trigger threshold is exceeded.",
)?;
w.encode_gauge(
"ledger_archive_node_max_memory_size_bytes",
archive.node_max_memory_size_bytes as f64,
"Maximum number of bytes an archive canister of this ledger may store.",
Comment thread
mbjorkqvist marked this conversation as resolved.
Outdated
)?;
w.encode_gauge(
"ledger_archive_max_message_size_bytes",
archive.max_message_size_bytes as f64,
"Maximum size in bytes of a message sent to an archive canister.",
)?;
w.encode_gauge(
"ledger_archive_cycles_for_archive_creation",
archive.cycles_for_archive_creation as f64,
"Cycles attached to the call creating a new archive canister.",
)?;
w.encode_gauge(
"ledger_archive_max_transactions_per_response",
archive.effective_max_transactions_per_response() as f64,
"Maximum number of transactions an archive returns per response.",
)?;
Ok(())
}

fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::io::Result<()> {
w.encode_gauge(
"ledger_stable_memory_pages",
Expand Down Expand Up @@ -402,6 +474,9 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::i
num_archives as f64,
"Total number of archives.",
)?;
if let Some(archive) = archive_guard.as_ref() {
encode_archive_config_metrics(w, archive)?;
}
}
Err(err) => Err(std::io::Error::other(format!(
"Failed to read number of archives: {err}"
Expand All @@ -412,6 +487,7 @@ fn encode_metrics(w: &mut ic_metrics_encoder::MetricsEncoder<Vec<u8>>) -> std::i
ledger.approvals().get_num_approvals() as f64,
"Total number of approvals.",
)?;
encode_dedup_config_metrics(w, &*ledger)?;
Ok(())
})
}
Expand Down
8 changes: 8 additions & 0 deletions rs/ledger_suite/icrc1/ledger/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ fn test_change_trigger_threshold_before_archive_spawned() {
);
}

#[test]
fn test_archive_and_dedup_config_metrics() {
ic_ledger_suite_state_machine_tests::test_archive_and_dedup_config_metrics(
ledger_wasm(),
encode_init_args,
);
}

#[test]
fn test_upgrade_archive_options() {
ic_ledger_suite_state_machine_tests::test_upgrade_archive_options(
Expand Down
Loading
Loading