From 97056c2548654b293f40bb66f8815242516fcd57 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 21 Jan 2025 19:06:13 +0500 Subject: [PATCH 1/9] new wallets --- stats/stats/src/charts/lines/user_ops/mod.rs | 1 + .../charts/lines/user_ops/new_aa_wallets.rs | 229 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs diff --git a/stats/stats/src/charts/lines/user_ops/mod.rs b/stats/stats/src/charts/lines/user_ops/mod.rs index f95f724cb..72a115761 100644 --- a/stats/stats/src/charts/lines/user_ops/mod.rs +++ b/stats/stats/src/charts/lines/user_ops/mod.rs @@ -1,5 +1,6 @@ pub mod active_bundlers; pub mod active_paymasters; +pub mod new_aa_wallets; pub mod new_user_ops; pub mod user_ops_growth; diff --git a/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs new file mode 100644 index 000000000..dac39918c --- /dev/null +++ b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs @@ -0,0 +1,229 @@ +use std::ops::Range; + +use crate::{ + charts::{ + db_interaction::{read::QueryAllBlockTimestampRange, utils::datetime_range_filter}, + types::timespans::DateValue, + }, + data_source::{ + kinds::{ + data_manipulation::{ + map::{MapParseTo, MapToString, StripExt}, + resolutions::sum::SumLowerResolution, + }, + local_db::{ + parameters::update::batching::parameters::{ + Batch30Weeks, Batch30Years, Batch36Months, BatchMaxDays, + }, + DirectVecLocalDbChartSource, + }, + remote_db::{RemoteDatabaseSource, RemoteQueryBehaviour, StatementFromRange}, + }, + types::BlockscoutMigrations, + UpdateContext, + }, + define_and_impl_resolution_properties, + missing_date::trim_out_of_range_sorted, + range::{data_source_query_range_to_db_statement_range, UniversalRange}, + types::timespans::{Month, Week, Year}, + ChartError, ChartProperties, Named, +}; + +use blockscout_db::entity::{blocks, user_operations}; +use chrono::{DateTime, NaiveDate, Utc}; +use entity::sea_orm_active_enums::ChartType; +use migration::{Alias, Asterisk, Func, IntoColumnRef, Query, SelectStatement, SimpleExpr}; +use sea_orm::{ + ColumnTrait, DatabaseBackend, EntityTrait, FromQueryResult, IntoIdentity, IntoSimpleExpr, + Order, QueryFilter, QueryOrder, QuerySelect, QueryTrait, Statement, StatementBuilder, +}; + +pub struct NewAccountAbstractionWalletsStatement; + +impl StatementFromRange for NewAccountAbstractionWalletsStatement { + fn get_statement( + range: Option>>, + _completed_migrations: &BlockscoutMigrations, + ) -> Statement { + // `MIN_UTC` does not fit into postgres' timestamp. Unix epoch start should be enough + let min_timestamp = DateTime::::UNIX_EPOCH; + // All transactions from the beginning must be considered to calculate new wallets correctly. + // E.g. if a wallet was first active both before `range.start()` and within the range, + // we don't want to count it within the range (as it's not a *new* wallet). + let range = range.map(|r| (min_timestamp..r.end)); + + // same as `new_accounts` but in sea-query/sea-orm form + let date_intermediate_col = "date".into_identity(); + let mut first_user_op = user_operations::Entity::find() + .select_only() + .join( + sea_orm::JoinType::InnerJoin, + user_operations::Entity::belongs_to(blocks::Entity) + .from(user_operations::Column::BlockHash) + .to(blocks::Column::Hash) + .into(), + ) + .distinct_on([user_operations::Column::Sender]) + .expr_as( + blocks::Column::Timestamp + .into_simple_expr() + .cast_as(Alias::new("date")), + date_intermediate_col, + ) + .filter(blocks::Column::Consensus.eq(true)) + .filter(blocks::Column::Timestamp.ne(DateTime::UNIX_EPOCH)) + .order_by(user_operations::Column::Sender, Order::Asc) + .order_by(blocks::Column::Timestamp, Order::Asc); + if let Some(range) = range { + first_user_op = datetime_range_filter(first_user_op, blocks::Column::Timestamp, &range); + } + let first_user_op = first_user_op.into_query(); + let first_user_op_alias = Alias::new("first_user_op"); + let date_intermediate_col = (first_user_op_alias.clone(), Alias::new("date")); + + let mut query = Query::select(); + query + .expr_as( + date_intermediate_col.clone().into_column_ref(), + Alias::new("date"), + ) + .expr_as( + SimpleExpr::from(Func::count(Asterisk.into_column_ref())) + .cast_as(Alias::new("text")), + Alias::new("value"), + ) + .from_subquery(first_user_op, first_user_op_alias.clone()) + .add_group_by([date_intermediate_col.into_column_ref().into()]); + ::build(&query, &DatabaseBackend::Postgres) + } +} + +pub struct NewAccountAbstractionWalletsQueryBehaviour; + +impl RemoteQueryBehaviour for NewAccountAbstractionWalletsQueryBehaviour { + type Output = Vec>; + + async fn query_data( + cx: &UpdateContext<'_>, + range: UniversalRange>, + ) -> Result>, ChartError> { + let statement_range = + data_source_query_range_to_db_statement_range::(cx, range) + .await?; + let query = NewAccountAbstractionWalletsStatement::get_statement( + statement_range.clone(), + &cx.blockscout_applied_migrations, + ); + let mut data = DateValue::::find_by_statement(query) + .all(cx.blockscout) + .await + .map_err(ChartError::BlockscoutDB)?; + // make sure that it's sorted + data.sort_by_key(|d| d.timespan); + if let Some(range) = statement_range { + let range = range.start.date_naive()..=range.end.date_naive(); + trim_out_of_range_sorted(&mut data, range); + } + Ok(data) + } +} + +/// Note: The intended strategy is to update whole range at once, even +/// though the implementation allows batching. The batching was done +/// to simplify interface of the data source. +/// +/// Thus, use max batch size in the dependant data sources. +pub type NewAccountAbstractionWalletsRemote = + RemoteDatabaseSource; + +pub struct Properties; + +impl Named for Properties { + fn name() -> String { + "newAccountAbstractionWallets".into() + } +} + +impl ChartProperties for Properties { + type Resolution = NaiveDate; + + fn chart_type() -> ChartType { + ChartType::Line + } +} + +define_and_impl_resolution_properties!( + define_and_impl: { + WeeklyProperties: Week, + MonthlyProperties: Month, + YearlyProperties: Year, + }, + base_impl: Properties +); + +pub type NewAccountAbstractionWallets = + DirectVecLocalDbChartSource; +pub type NewAccountAbstractionWalletsInt = MapParseTo, i64>; +pub type NewAccountAbstractionWalletsWeekly = DirectVecLocalDbChartSource< + MapToString>, + Batch30Weeks, + WeeklyProperties, +>; +pub type NewAccountAbstractionWalletsMonthly = DirectVecLocalDbChartSource< + MapToString>, + Batch36Months, + MonthlyProperties, +>; +pub type NewAccountAbstractionWalletsMonthlyInt = + MapParseTo, i64>; +pub type NewAccountAbstractionWalletsYearly = DirectVecLocalDbChartSource< + MapToString>, + Batch30Years, + YearlyProperties, +>; + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::simple_test::simple_test_chart; + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_new_account_abstraction_wallets() { + simple_test_chart::( + "update_new_account_abstraction_wallets", + vec![("2022-11-09", "1")], + ) + .await; + } + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_new_account_abstraction_wallets_weekly() { + simple_test_chart::( + "update_new_account_abstraction_wallets_weekly", + vec![("2022-11-07", "1")], + ) + .await; + } + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_new_account_abstraction_wallets_monthly() { + simple_test_chart::( + "update_new_account_abstraction_wallets_monthly", + vec![("2022-11-01", "1")], + ) + .await; + } + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_new_account_abstraction_wallets_yearly() { + simple_test_chart::( + "update_new_account_abstraction_wallets_yearly", + vec![("2022-01-01", "1")], + ) + .await; + } +} From 3ff612dfd4cb9693d9b55c2f58261ccc926da78f Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 21 Jan 2025 19:16:21 +0500 Subject: [PATCH 2/9] other aa charts --- stats/stats/src/charts/counters/mod.rs | 1 + .../src/charts/counters/total_aa_wallets.rs | 50 ++++++++ stats/stats/src/charts/lines/mod.rs | 1 + .../lines/user_ops/aa_wallets_growth.rs | 112 ++++++++++++++++++ .../lines/user_ops/active_aa_wallets.rs | 88 ++++++++++++++ stats/stats/src/charts/lines/user_ops/mod.rs | 5 + 6 files changed, 257 insertions(+) create mode 100644 stats/stats/src/charts/counters/total_aa_wallets.rs create mode 100644 stats/stats/src/charts/lines/user_ops/aa_wallets_growth.rs create mode 100644 stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs diff --git a/stats/stats/src/charts/counters/mod.rs b/stats/stats/src/charts/counters/mod.rs index 04173c35c..40ce939e8 100644 --- a/stats/stats/src/charts/counters/mod.rs +++ b/stats/stats/src/charts/counters/mod.rs @@ -5,6 +5,7 @@ mod last_new_verified_contracts; mod new_contracts_24h; mod new_verified_contracts_24h; mod pending_txns; +mod total_aa_wallets; mod total_accounts; mod total_addresses; mod total_blocks; diff --git a/stats/stats/src/charts/counters/total_aa_wallets.rs b/stats/stats/src/charts/counters/total_aa_wallets.rs new file mode 100644 index 000000000..7dc5a637f --- /dev/null +++ b/stats/stats/src/charts/counters/total_aa_wallets.rs @@ -0,0 +1,50 @@ +use crate::{ + data_source::kinds::{ + data_manipulation::{last_point::LastPoint, map::StripExt}, + local_db::DirectPointLocalDbChartSource, + }, + lines::AccountAbstractionWalletsGrowth, + ChartProperties, MissingDatePolicy, Named, +}; + +use chrono::NaiveDate; +use entity::sea_orm_active_enums::ChartType; + +pub struct Properties; + +impl Named for Properties { + fn name() -> String { + "totalAccountAbstractionWallets".into() + } +} + +impl ChartProperties for Properties { + type Resolution = NaiveDate; + + fn chart_type() -> ChartType { + ChartType::Counter + } + fn missing_date_policy() -> MissingDatePolicy { + MissingDatePolicy::FillPrevious + } +} + +pub type TotalAccountAbstractionWallets = + DirectPointLocalDbChartSource>, Properties>; + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::simple_test::simple_test_counter; + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_total_account_abstraction_wallets() { + simple_test_counter::( + "update_total_account_abstraction_wallets", + "1", + None, + ) + .await; + } +} diff --git a/stats/stats/src/charts/lines/mod.rs b/stats/stats/src/charts/lines/mod.rs index 3efa9f475..cfb57d293 100644 --- a/stats/stats/src/charts/lines/mod.rs +++ b/stats/stats/src/charts/lines/mod.rs @@ -19,6 +19,7 @@ mod mock; pub use new_txns_window::WINDOW as NEW_TXNS_WINDOW_RANGE; +pub use aa_wallets_growth::AccountAbstractionWalletsGrowth; pub use accounts_growth::{ AccountsGrowth, AccountsGrowthMonthly, AccountsGrowthWeekly, AccountsGrowthYearly, }; diff --git a/stats/stats/src/charts/lines/user_ops/aa_wallets_growth.rs b/stats/stats/src/charts/lines/user_ops/aa_wallets_growth.rs new file mode 100644 index 000000000..e55fa0df6 --- /dev/null +++ b/stats/stats/src/charts/lines/user_ops/aa_wallets_growth.rs @@ -0,0 +1,112 @@ +use crate::{ + charts::chart::ChartProperties, + data_source::kinds::{ + data_manipulation::{map::StripExt, resolutions::last_value::LastValueLowerResolution}, + local_db::{ + parameters::update::batching::parameters::{Batch30Weeks, Batch30Years, Batch36Months}, + DailyCumulativeLocalDbChartSource, DirectVecLocalDbChartSource, + }, + }, + define_and_impl_resolution_properties, + types::timespans::{Month, Week, Year}, + MissingDatePolicy, Named, +}; + +use chrono::NaiveDate; +use entity::sea_orm_active_enums::ChartType; + +use super::new_aa_wallets::NewAccountAbstractionWalletsInt; + +pub struct Properties; + +impl Named for Properties { + fn name() -> String { + "accountAbstractionWalletsGrowth".into() + } +} + +impl ChartProperties for Properties { + type Resolution = NaiveDate; + + fn chart_type() -> ChartType { + ChartType::Line + } + fn missing_date_policy() -> MissingDatePolicy { + MissingDatePolicy::FillPrevious + } +} + +define_and_impl_resolution_properties!( + define_and_impl: { + WeeklyProperties: Week, + MonthlyProperties: Month, + YearlyProperties: Year, + }, + base_impl: Properties +); + +pub type AccountAbstractionWalletsGrowth = + DailyCumulativeLocalDbChartSource; +type AccountAbstractionWalletsGrowthS = StripExt; +pub type AccountAbstractionWalletsGrowthWeekly = DirectVecLocalDbChartSource< + LastValueLowerResolution, + Batch30Weeks, + WeeklyProperties, +>; +pub type AccountAbstractionWalletsGrowthMonthly = DirectVecLocalDbChartSource< + LastValueLowerResolution, + Batch36Months, + MonthlyProperties, +>; +type AccountAbstractionWalletsGrowthMonthlyS = StripExt; +pub type AccountAbstractionWalletsGrowthYearly = DirectVecLocalDbChartSource< + LastValueLowerResolution, + Batch30Years, + YearlyProperties, +>; + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::simple_test::simple_test_chart; + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_account_abstraction_wallets_growth() { + simple_test_chart::( + "update_account_abstraction_wallets_growth", + vec![("2022-11-09", "1")], + ) + .await; + } + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_account_abstraction_wallets_growth_weekly() { + simple_test_chart::( + "update_account_abstraction_wallets_growth_weekly", + vec![("2022-11-07", "1")], + ) + .await; + } + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_account_abstraction_wallets_growth_monthly() { + simple_test_chart::( + "update_account_abstraction_wallets_growth_monthly", + vec![("2022-11-01", "1")], + ) + .await; + } + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_account_abstraction_wallets_growth_yearly() { + simple_test_chart::( + "update_account_abstraction_wallets_growth_yearly", + vec![("2022-01-01", "1")], + ) + .await; + } +} diff --git a/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs b/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs new file mode 100644 index 000000000..d840c2844 --- /dev/null +++ b/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs @@ -0,0 +1,88 @@ +//! Active account_abstraction_wallets on each day. + +use std::ops::Range; + +use crate::{ + charts::db_interaction::read::QueryAllBlockTimestampRange, + data_source::{ + kinds::{ + local_db::{ + parameters::update::batching::parameters::Batch30Days, DirectVecLocalDbChartSource, + }, + remote_db::{PullAllWithAndSort, RemoteDatabaseSource, StatementFromRange}, + }, + types::BlockscoutMigrations, + }, + ChartProperties, Named, +}; + +use blockscout_db::entity::user_operations; +use chrono::{DateTime, NaiveDate, Utc}; +use entity::sea_orm_active_enums::ChartType; +use migration::IntoColumnRef; +use sea_orm::Statement; + +use super::active_bundlers::count_distinct_in_user_ops; + +pub struct ActiveAccountAbstractionWalletsStatement; + +impl StatementFromRange for ActiveAccountAbstractionWalletsStatement { + fn get_statement( + range: Option>>, + _completed_migrations: &BlockscoutMigrations, + ) -> Statement { + count_distinct_in_user_ops(user_operations::Column::Sender.into_column_ref(), range) + } +} + +pub type ActiveAccountAbstractionWalletsRemote = RemoteDatabaseSource< + PullAllWithAndSort< + ActiveAccountAbstractionWalletsStatement, + NaiveDate, + String, + QueryAllBlockTimestampRange, + >, +>; + +pub struct Properties; + +impl Named for Properties { + fn name() -> String { + "activeAccountAbstractionWallets".into() + } +} + +impl ChartProperties for Properties { + type Resolution = NaiveDate; + + fn chart_type() -> ChartType { + ChartType::Line + } +} + +pub type ActiveAccountAbstractionWallets = + DirectVecLocalDbChartSource; + +#[cfg(test)] +mod tests { + use crate::tests::simple_test::simple_test_chart; + + use super::ActiveAccountAbstractionWallets; + + #[tokio::test] + #[ignore = "needs database to run"] + async fn update_active_account_abstraction_wallets() { + simple_test_chart::( + "update_active_account_abstraction_wallets", + vec![ + ("2022-11-09", "1"), + ("2022-11-10", "1"), + ("2022-11-11", "1"), + ("2022-11-12", "1"), + ("2022-12-01", "1"), + ("2023-02-01", "1"), + ], + ) + .await; + } +} diff --git a/stats/stats/src/charts/lines/user_ops/mod.rs b/stats/stats/src/charts/lines/user_ops/mod.rs index 72a115761..2e61f468d 100644 --- a/stats/stats/src/charts/lines/user_ops/mod.rs +++ b/stats/stats/src/charts/lines/user_ops/mod.rs @@ -1,3 +1,8 @@ +//! Stats for user-ops-indexer. +//! In other words, about account abstraction as per ERC 4337. + +pub mod aa_wallets_growth; +pub mod active_aa_wallets; pub mod active_bundlers; pub mod active_paymasters; pub mod new_aa_wallets; From 58387a16b361eac2bb18e5d4b30a28eb573c21f6 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 21 Jan 2025 19:30:56 +0500 Subject: [PATCH 3/9] add new charts to configs & endpoint & etc. --- stats/config/charts.json | 16 ++++++++++++++++ stats/config/layout.json | 4 ++++ stats/config/update_groups.json | 2 ++ stats/stats-server/src/runtime_setup.rs | 2 ++ .../tests/it/chart_endpoints/counters.rs | 3 ++- .../tests/it/chart_endpoints/lines.rs | 3 +++ stats/stats/src/charts/counters/mod.rs | 1 + stats/stats/src/charts/lines/mod.rs | 10 +++++++++- stats/stats/src/update_groups.rs | 17 ++++++++++++++++- 9 files changed, 55 insertions(+), 3 deletions(-) diff --git a/stats/config/charts.json b/stats/config/charts.json index 61913f45b..5dda6f987 100644 --- a/stats/config/charts.json +++ b/stats/config/charts.json @@ -69,6 +69,10 @@ "title": "Total user operations", "description": "Number of user operations as per the ERC-4337 standard" }, + "total_account_abstraction_wallets": { + "title": "Total AA wallets", + "description": "Number of account abstraction wallets (ERC-4337) that sent at least 1 user operation" + }, "last_new_contracts": { "title": "Number of contracts today", "description": "Number of deployed contracts today (UTC)" @@ -171,6 +175,18 @@ "title": "Number of user operations", "description": "Cumulative user operation (ERC-4337) growth over time" }, + "new_account_abstraction_wallets": { + "title": "New AA wallets", + "description": "Number of newly added account abstraction wallets (ERC-4337)" + }, + "account_abstraction_wallets_growth": { + "title": "Number of AA wallets", + "description": "Cumulative account abstraction wallets (ERC-4337) growth over time" + }, + "active_account_abstraction_wallets": { + "title": "Active AA wallets", + "description": "Active account abstraction wallets (ERC-4337) number per period" + }, "active_bundlers": { "title": "Active bundlers", "description": "Active user ops bundlers number per period" diff --git a/stats/config/layout.json b/stats/config/layout.json index ebafeb72c..73aa432ec 100644 --- a/stats/config/layout.json +++ b/stats/config/layout.json @@ -14,6 +14,7 @@ "total_txns", "total_operational_txns", "total_user_ops", + "total_account_abstraction_wallets", "total_verified_contracts", "new_txns_24h", "pending_txns_30m", @@ -91,6 +92,9 @@ "charts_order": [ "user_ops_growth", "new_user_ops", + "new_account_abstraction_wallets", + "account_abstraction_wallets_growth", + "active_account_abstraction_wallets", "active_bundlers", "active_paymasters" ] diff --git a/stats/config/update_groups.json b/stats/config/update_groups.json index 5316dbe05..58173be5f 100644 --- a/stats/config/update_groups.json +++ b/stats/config/update_groups.json @@ -37,8 +37,10 @@ "new_contracts_group": "0 20 */3 * * * *", "new_txns_group": "0 10 */3 * * * *", "new_user_ops_group": "0 5 7 * * * *", + "new_account_abstraction_wallets_group": "0 35 12 * * * *", "active_bundlers_group": "0 7 7 * * * *", "active_paymasters_group": "0 8 7 * * * *", + "active_account_abstraction_wallets_group": "0 30 13 * * * *", "new_verified_contracts_group": "0 30 */3 * * * *", "native_coin_holders_growth_group": "0 0 7,17,22 * * * *", "new_native_coin_transfers_group": "0 0 3,13 * * * *", diff --git a/stats/stats-server/src/runtime_setup.rs b/stats/stats-server/src/runtime_setup.rs index ee2031489..d99d32796 100644 --- a/stats/stats-server/src/runtime_setup.rs +++ b/stats/stats-server/src/runtime_setup.rs @@ -303,6 +303,7 @@ impl RuntimeSetup { Arc::new(ActiveAccountsGroup), Arc::new(ActiveBundlersGroup), Arc::new(ActivePaymastersGroup), + Arc::new(ActiveAccountAbstractionWalletsGroup), Arc::new(AverageBlockTimeGroup), Arc::new(CompletedTxnsGroup), Arc::new(PendingTxns30mGroup), @@ -338,6 +339,7 @@ impl RuntimeSetup { Arc::new(TxnsSuccessRateGroup), // complex groups Arc::new(NewAccountsGroup), + Arc::new(NewAccountAbstractionWalletsGroup), Arc::new(NewContractsGroup), Arc::new(NewTxnsGroup), Arc::new(NewUserOpsGroup), diff --git a/stats/stats-server/tests/it/chart_endpoints/counters.rs b/stats/stats-server/tests/it/chart_endpoints/counters.rs index d4fdb611f..0a0ddd7d6 100644 --- a/stats/stats-server/tests/it/chart_endpoints/counters.rs +++ b/stats/stats-server/tests/it/chart_endpoints/counters.rs @@ -26,13 +26,14 @@ pub async fn test_counters_ok(base: Url) { "totalTokens", "totalTxns", "totalUserOps", + "totalAccountAbstractionWallets", "totalVerifiedContracts", "newTxns24h", "pendingTxns30m", "txnsFee24h", "averageTxnFee24h", // on a different page; they are checked by other endpoint tests and - // `check_all_enabled_charts_have_endpoints` + // `check_all_enabled_charts_have_endpoints`. // "newContracts24h", // "newVerifiedContracts24h", diff --git a/stats/stats-server/tests/it/chart_endpoints/lines.rs b/stats/stats-server/tests/it/chart_endpoints/lines.rs index 7262af5ea..375a6ce3e 100644 --- a/stats/stats-server/tests/it/chart_endpoints/lines.rs +++ b/stats/stats-server/tests/it/chart_endpoints/lines.rs @@ -58,6 +58,9 @@ pub async fn test_lines_ok(base: Url) { "txnsSuccessRate", "newUserOps", "userOpsGrowth", + "newAccountAbstractionWallets", + "accountAbstractionWalletsGrowth", + "activeAccountAbstractionWallets", "activeBundlers", "activePaymasters", "newVerifiedContracts", diff --git a/stats/stats/src/charts/counters/mod.rs b/stats/stats/src/charts/counters/mod.rs index 40ce939e8..eb6b5877c 100644 --- a/stats/stats/src/charts/counters/mod.rs +++ b/stats/stats/src/charts/counters/mod.rs @@ -30,6 +30,7 @@ pub use last_new_verified_contracts::LastNewVerifiedContracts; pub use new_contracts_24h::NewContracts24h; pub use new_verified_contracts_24h::NewVerifiedContracts24h; pub use pending_txns::PendingTxns30m; +pub use total_aa_wallets::TotalAccountAbstractionWallets; pub use total_accounts::TotalAccounts; pub use total_addresses::TotalAddresses; pub use total_blocks::{TotalBlocks, TotalBlocksInt}; diff --git a/stats/stats/src/charts/lines/mod.rs b/stats/stats/src/charts/lines/mod.rs index cfb57d293..c73d1efd1 100644 --- a/stats/stats/src/charts/lines/mod.rs +++ b/stats/stats/src/charts/lines/mod.rs @@ -19,13 +19,21 @@ mod mock; pub use new_txns_window::WINDOW as NEW_TXNS_WINDOW_RANGE; -pub use aa_wallets_growth::AccountAbstractionWalletsGrowth; +pub use aa_wallets_growth::{ + AccountAbstractionWalletsGrowth, AccountAbstractionWalletsGrowthMonthly, + AccountAbstractionWalletsGrowthWeekly, AccountAbstractionWalletsGrowthYearly, +}; pub use accounts_growth::{ AccountsGrowth, AccountsGrowthMonthly, AccountsGrowthWeekly, AccountsGrowthYearly, }; +pub use active_aa_wallets::ActiveAccountAbstractionWallets; pub use active_accounts::ActiveAccounts; pub use active_bundlers::ActiveBundlers; pub use active_paymasters::ActivePaymasters; +pub use new_aa_wallets::{ + NewAccountAbstractionWallets, NewAccountAbstractionWalletsMonthly, + NewAccountAbstractionWalletsWeekly, NewAccountAbstractionWalletsYearly, +}; #[rustfmt::skip] pub use active_recurring_accounts::{ ActiveRecurringAccountsDailyRecurrence120Days, ActiveRecurringAccountsMonthlyRecurrence120Days, diff --git a/stats/stats/src/update_groups.rs b/stats/stats/src/update_groups.rs index f8ea01f42..ec084788f 100644 --- a/stats/stats/src/update_groups.rs +++ b/stats/stats/src/update_groups.rs @@ -18,9 +18,10 @@ singleton_groups!( // Active accounts is left without resolutions because the chart is non-trivial // to calculate somewhat-optimally ActiveAccounts, - // Same^ for bundlers & paymasters + // Same ^ for bundlers, paymasters, and aa wallets ActiveBundlers, ActivePaymasters, + ActiveAccountAbstractionWallets, AverageBlockTime, CompletedTxns, PendingTxns30m, @@ -163,6 +164,20 @@ construct_update_group!(NewAccountsGroup { ] }); +construct_update_group!(NewAccountAbstractionWalletsGroup { + charts: [ + NewAccountAbstractionWallets, + NewAccountAbstractionWalletsWeekly, + NewAccountAbstractionWalletsMonthly, + NewAccountAbstractionWalletsYearly, + AccountAbstractionWalletsGrowth, + AccountAbstractionWalletsGrowthWeekly, + AccountAbstractionWalletsGrowthMonthly, + AccountAbstractionWalletsGrowthYearly, + TotalAccountAbstractionWallets, + ] +}); + construct_update_group!(NewContractsGroup { charts: [ NewContracts, From 1c03d37dd092acb69c82028aad3148137747abe5 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 21 Jan 2025 19:36:01 +0500 Subject: [PATCH 4/9] small comment --- stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs index dac39918c..1141fa237 100644 --- a/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs +++ b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs @@ -1,3 +1,5 @@ +//! Essentially the same logic as with `NewAccounts` +//! but for account abstraction wallets. use std::ops::Range; use crate::{ From 90cc20ac4694849652da86a9c216a52df880d418 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 21 Jan 2025 19:39:00 +0500 Subject: [PATCH 5/9] comment fix --- stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs b/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs index d840c2844..525b885b2 100644 --- a/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs +++ b/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs @@ -1,4 +1,4 @@ -//! Active account_abstraction_wallets on each day. +//! Active account abstraction wallets on each day. use std::ops::Range; From ad5e3fc40202152a6c09ab363f1eb0b141eb8e19 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Wed, 22 Jan 2025 18:18:04 +0500 Subject: [PATCH 6/9] fix import --- stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs b/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs index 525b885b2..df084e721 100644 --- a/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs +++ b/stats/stats/src/charts/lines/user_ops/active_aa_wallets.rs @@ -22,7 +22,7 @@ use entity::sea_orm_active_enums::ChartType; use migration::IntoColumnRef; use sea_orm::Statement; -use super::active_bundlers::count_distinct_in_user_ops; +use super::count_distinct_in_user_ops; pub struct ActiveAccountAbstractionWalletsStatement; From 1f96b424772f99ff3479b017ee40c6e81f41ab46 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 28 Jan 2025 15:46:03 +0500 Subject: [PATCH 7/9] random minor non-functional tweaks --- stats/stats/src/charts/counters/pending_txns.rs | 9 ++------- stats/stats/src/charts/counters/total_txns.rs | 9 ++------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/stats/stats/src/charts/counters/pending_txns.rs b/stats/stats/src/charts/counters/pending_txns.rs index 2e521d955..8b5b6e4de 100644 --- a/stats/stats/src/charts/counters/pending_txns.rs +++ b/stats/stats/src/charts/counters/pending_txns.rs @@ -93,16 +93,11 @@ pub type PendingTxns30m = DirectPointLocalDbChartSource( - "update_pending_txns_30m", - "0", - None, - ) - .await; + simple_test_counter::("update_pending_txns_30m", "0", None).await; } } diff --git a/stats/stats/src/charts/counters/total_txns.rs b/stats/stats/src/charts/counters/total_txns.rs index a9e4a93e5..1caad930f 100644 --- a/stats/stats/src/charts/counters/total_txns.rs +++ b/stats/stats/src/charts/counters/total_txns.rs @@ -98,13 +98,8 @@ impl ValueEstimation for TotalTxnsEstimation { } } -// We will need it to update on not fully indexed data soon, therefore this counter is -// separated from `NewTxns`. -// -// Separate query not reliant on previous computation helps this counter to work in such -// environments. -// -// todo: make it dependant again if #845 is resolved +// Independent from `NewTxns` because this needs to work on not-fully-indexed +// just as well. pub type TotalTxns = DirectPointLocalDbChartSourceWithEstimate; pub type TotalTxnsInt = MapParseTo; From 0773b71e1a6274bf89b421c27536fc7f473c07fb Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 28 Jan 2025 16:12:28 +0500 Subject: [PATCH 8/9] rewrite new_aa_wallets query to plain sql for readability --- .../charts/lines/user_ops/new_aa_wallets.rs | 89 ++++++++----------- 1 file changed, 35 insertions(+), 54 deletions(-) diff --git a/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs index 1141fa237..4ab5c1149 100644 --- a/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs +++ b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs @@ -3,10 +3,7 @@ use std::ops::Range; use crate::{ - charts::{ - db_interaction::{read::QueryAllBlockTimestampRange, utils::datetime_range_filter}, - types::timespans::DateValue, - }, + charts::{db_interaction::read::QueryAllBlockTimestampRange, types::timespans::DateValue}, data_source::{ kinds::{ data_manipulation::{ @@ -28,17 +25,13 @@ use crate::{ missing_date::trim_out_of_range_sorted, range::{data_source_query_range_to_db_statement_range, UniversalRange}, types::timespans::{Month, Week, Year}, + utils::sql_with_range_filter_opt, ChartError, ChartProperties, Named, }; -use blockscout_db::entity::{blocks, user_operations}; use chrono::{DateTime, NaiveDate, Utc}; use entity::sea_orm_active_enums::ChartType; -use migration::{Alias, Asterisk, Func, IntoColumnRef, Query, SelectStatement, SimpleExpr}; -use sea_orm::{ - ColumnTrait, DatabaseBackend, EntityTrait, FromQueryResult, IntoIdentity, IntoSimpleExpr, - Order, QueryFilter, QueryOrder, QuerySelect, QueryTrait, Statement, StatementBuilder, -}; +use sea_orm::{DbBackend, FromQueryResult, Statement}; pub struct NewAccountAbstractionWalletsStatement; @@ -54,49 +47,26 @@ impl StatementFromRange for NewAccountAbstractionWalletsStatement { // we don't want to count it within the range (as it's not a *new* wallet). let range = range.map(|r| (min_timestamp..r.end)); - // same as `new_accounts` but in sea-query/sea-orm form - let date_intermediate_col = "date".into_identity(); - let mut first_user_op = user_operations::Entity::find() - .select_only() - .join( - sea_orm::JoinType::InnerJoin, - user_operations::Entity::belongs_to(blocks::Entity) - .from(user_operations::Column::BlockHash) - .to(blocks::Column::Hash) - .into(), - ) - .distinct_on([user_operations::Column::Sender]) - .expr_as( - blocks::Column::Timestamp - .into_simple_expr() - .cast_as(Alias::new("date")), - date_intermediate_col, - ) - .filter(blocks::Column::Consensus.eq(true)) - .filter(blocks::Column::Timestamp.ne(DateTime::UNIX_EPOCH)) - .order_by(user_operations::Column::Sender, Order::Asc) - .order_by(blocks::Column::Timestamp, Order::Asc); - if let Some(range) = range { - first_user_op = datetime_range_filter(first_user_op, blocks::Column::Timestamp, &range); - } - let first_user_op = first_user_op.into_query(); - let first_user_op_alias = Alias::new("first_user_op"); - let date_intermediate_col = (first_user_op_alias.clone(), Alias::new("date")); - - let mut query = Query::select(); - query - .expr_as( - date_intermediate_col.clone().into_column_ref(), - Alias::new("date"), - ) - .expr_as( - SimpleExpr::from(Func::count(Asterisk.into_column_ref())) - .cast_as(Alias::new("text")), - Alias::new("value"), - ) - .from_subquery(first_user_op, first_user_op_alias.clone()) - .add_group_by([date_intermediate_col.into_column_ref().into()]); - ::build(&query, &DatabaseBackend::Postgres) + sql_with_range_filter_opt!( + DbBackend::Postgres, + r#" + SELECT "first_user_op"."date" AS "date", + COUNT(*)::TEXT AS "value" + FROM + (SELECT DISTINCT ON ("sender") CAST("blocks"."timestamp" AS date) AS "date" + FROM "user_operations" + INNER JOIN "blocks" ON "user_operations"."block_hash" = "blocks"."hash" + WHERE "blocks"."consensus" = TRUE + AND "blocks"."timestamp" != to_timestamp(0) {filter} + ORDER BY "user_operations"."sender" ASC, + "blocks"."timestamp" ASC + ) AS "first_user_op" + GROUP BY "first_user_op"."date" + "#, + [], + "\"blocks\".\"timestamp\"", + range + ) } } @@ -187,7 +157,18 @@ pub type NewAccountAbstractionWalletsYearly = DirectVecLocalDbChartSource< #[cfg(test)] mod tests { use super::*; - use crate::tests::simple_test::simple_test_chart; + use crate::tests::{point_construction::dt, simple_test::simple_test_chart}; + + #[test] + fn print_stmt() { + println!( + "{}", + NewAccountAbstractionWalletsStatement::get_statement( + Some(dt("2023-01-01T00:00:00").and_utc()..dt("2024-01-01T12:00:00").and_utc()), + &BlockscoutMigrations::latest(), + ) + ) + } #[tokio::test] #[ignore = "needs database to run"] From ae3fb20eef85e5d742bff616792852e2fc4e0794 Mon Sep 17 00:00:00 2001 From: Kirill Ivanov Date: Tue, 28 Jan 2025 16:13:38 +0500 Subject: [PATCH 9/9] fix comments --- stats/stats/src/charts/lines/accounts/new_accounts.rs | 2 +- stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stats/stats/src/charts/lines/accounts/new_accounts.rs b/stats/stats/src/charts/lines/accounts/new_accounts.rs index 6a956a97c..627d04269 100644 --- a/stats/stats/src/charts/lines/accounts/new_accounts.rs +++ b/stats/stats/src/charts/lines/accounts/new_accounts.rs @@ -127,7 +127,7 @@ impl RemoteQueryBehaviour for NewAccountsQueryBehaviour { /// though the implementation allows batching. The batching was done /// to simplify interface of the data source. /// -/// Thus, use max batch size in the dependant data sources. +/// Thus, use max batch size in the `DirectVecLocalDbChartSource` for it. pub type NewAccountsRemote = RemoteDatabaseSource; pub struct Properties; diff --git a/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs index 4ab5c1149..b52fa02a2 100644 --- a/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs +++ b/stats/stats/src/charts/lines/user_ops/new_aa_wallets.rs @@ -104,7 +104,7 @@ impl RemoteQueryBehaviour for NewAccountAbstractionWalletsQueryBehaviour { /// though the implementation allows batching. The batching was done /// to simplify interface of the data source. /// -/// Thus, use max batch size in the dependant data sources. +/// Thus, use max batch size in the `DirectVecLocalDbChartSource` for it. pub type NewAccountAbstractionWalletsRemote = RemoteDatabaseSource;