-
Notifications
You must be signed in to change notification settings - Fork 0
feat: metrics for real minter balance #185
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||
| use crate::{ | ||||||||||
| address::{lazy_get_schnorr_master_key, minter_address}, | ||||||||||
| guard::TimerGuard, | ||||||||||
| rpc::get_balance, | ||||||||||
| runtime::CanisterRuntime, | ||||||||||
| state::{TaskType, mutate_state}, | ||||||||||
| }; | ||||||||||
| use canlog::log; | ||||||||||
| use cksol_types_internal::log::Priority; | ||||||||||
| use std::time::Duration; | ||||||||||
|
|
||||||||||
| pub const REFRESH_REAL_BALANCE_DELAY: Duration = Duration::from_secs(24 * 60 * 60); | ||||||||||
|
|
||||||||||
| /// Refresh the cached on-chain balance of the minter's main account. | ||||||||||
| /// Each call makes an RPC request to the Solana network, so this runs at most | ||||||||||
| /// once per day (see [`REFRESH_REAL_BALANCE_DELAY`]). | ||||||||||
|
Comment on lines
+15
to
+16
|
||||||||||
| /// Each call makes an RPC request to the Solana network, so this runs at most | |
| /// once per day (see [`REFRESH_REAL_BALANCE_DELAY`]). | |
| /// Each call makes an RPC request to the Solana network, so periodic refreshes | |
| /// are scheduled with a 24-hour delay (see [`REFRESH_REAL_BALANCE_DELAY`]). |
Copilot
AI
Apr 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New behavior here (daily balance RPC + state update) is currently untested. Please add unit tests covering: (1) successful refresh records last_balance_discrepancy using the runtime timestamp, (2) RPC error leaves last_balance_discrepancy unchanged, and (3) TimerGuard prevents concurrent refreshes.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| pub mod address; | ||
| pub mod balance_check; | ||
| pub mod consolidate; | ||
| mod constants; | ||
| mod cycles; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -92,6 +92,23 @@ pub fn encode_metrics(w: &mut MetricsEncoder<Vec<u8>>, s: &State) -> std::io::Re | |||||
| s.balance().metric_value(), | ||||||
| "Minter balance in Lamports.", | ||||||
| )?; | ||||||
| let (discrepancy_lamports, discrepancy_refresh_seconds) = match s.last_balance_discrepancy() { | ||||||
| Some(observation) => ( | ||||||
| observation.diff_lamports as f64, | ||||||
| (observation.observed_at / 1_000_000_000) as f64, | ||||||
| ), | ||||||
| None => (0.0, 0.0), | ||||||
| }; | ||||||
| w.encode_gauge( | ||||||
| "minter_balance_discrepancy_lamports", | ||||||
| discrepancy_lamports, | ||||||
| "Difference (real on-chain balance minus tracked balance) of the minter's main account at the time of the last successful refresh, in lamports. Both sides are sampled together; refreshed once per day. Reports 0 until the first refresh succeeds.", | ||||||
|
||||||
| "Difference (real on-chain balance minus tracked balance) of the minter's main account at the time of the last successful refresh, in lamports. Both sides are sampled together; refreshed once per day. Reports 0 until the first refresh succeeds.", | |
| "Difference (real on-chain balance minus tracked balance) of the minter's main account at the time of the last successful refresh, in lamports. Both sides are sampled together; refreshed shortly after install/upgrade and then once per day. Reports 0 until the first refresh succeeds.", |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,9 +7,10 @@ use cksol_types::ProcessDepositError; | |
| use derive_more::From; | ||
| use ic_canister_runtime::IcError; | ||
| use sol_rpc_types::{ | ||
| CommitmentLevel, ConfirmedTransactionStatusWithSignature, GetSignaturesForAddressParams, | ||
| GetTransactionEncoding, MultiRpcResult, RpcError, Slot, | ||
| CommitmentLevel, ConfirmedTransactionStatusWithSignature, GetBalanceParams, | ||
| GetSignaturesForAddressParams, GetTransactionEncoding, Lamport, MultiRpcResult, RpcError, Slot, | ||
| }; | ||
| use solana_address::Address; | ||
| use solana_hash::Hash; | ||
| use solana_signature::Signature; | ||
| use solana_transaction::Transaction; | ||
|
|
@@ -162,3 +163,33 @@ pub enum GetSignaturesForAddressError { | |
| #[error("Inconsistent RPC results for getSignaturesForAddress")] | ||
| InconsistentRpcResults, | ||
| } | ||
|
|
||
| pub async fn get_balance<R: CanisterRuntime>( | ||
| runtime: &R, | ||
| address: Address, | ||
| ) -> Result<Lamport, GetBalanceError> { | ||
| let client = read_state(|state| state.sol_rpc_client(runtime.inter_canister_call_runtime())); | ||
| let result = client | ||
| .get_balance(GetBalanceParams { | ||
| pubkey: address.into(), | ||
| commitment: Some(CommitmentLevel::Finalized), | ||
|
Comment on lines
+167
to
+175
|
||
| min_context_slot: None, | ||
| }) | ||
| .try_send() | ||
| .await; | ||
| match result? { | ||
| MultiRpcResult::Consistent(Ok(balance)) => Ok(balance), | ||
| MultiRpcResult::Consistent(Err(e)) => Err(GetBalanceError::RpcError(e)), | ||
| MultiRpcResult::Inconsistent(_) => Err(GetBalanceError::InconsistentRpcResults), | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, PartialEq, Error)] | ||
| pub enum GetBalanceError { | ||
| #[error("Error while calling SOL RPC canister: {0}")] | ||
| IcError(#[from] IcError), | ||
| #[error("RPC error while fetching balance: {0}")] | ||
| RpcError(RpcError), | ||
| #[error("Inconsistent RPC results for getBalance")] | ||
| InconsistentRpcResults, | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -109,6 +109,22 @@ pub struct State { | |||||||||
| consolidation_transactions: InsertionOrderedMap<Signature, ConsolidationTransaction>, | ||||||||||
| active_tasks: BTreeSet<TaskType>, | ||||||||||
| balance: Lamport, | ||||||||||
| /// Last observed difference (`real_balance - tracked_balance`) between the | ||||||||||
| /// real on-chain balance of the minter's main account and the value | ||||||||||
| /// tracked by the minter's state, together with the timestamp | ||||||||||
| /// (nanoseconds since the Unix epoch) at which it was computed. | ||||||||||
| /// | ||||||||||
| /// Both sides of the difference are sampled at the same time so that the | ||||||||||
| /// stored value remains meaningful even though `tracked_balance` keeps | ||||||||||
| /// changing afterwards. Refreshed once per day by a timer; `None` until | ||||||||||
| /// the first refresh succeeds. | ||||||||||
|
Comment on lines
+119
to
+120
|
||||||||||
| /// changing afterwards. Refreshed once per day by a timer; `None` until | |
| /// the first refresh succeeds. | |
| /// changing afterwards. Refreshed shortly after install/upgrade, and then | |
| /// once per day by a timer; `None` until the first refresh succeeds. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refresh_real_balancedoes not actually cache/store the real on-chain balance; it only records the discrepancy (real_balance - tracked_balance) viarecord_balance_discrepancy. Either adjust this docstring to describe what is stored, or extend the state to also persist the observed real balance if that’s the intended behavior.