From e8da3aa99c21e3e93672d31c4d1d934adbdd7819 Mon Sep 17 00:00:00 2001 From: Nicolas80 <nicolas.pmail@protonmail.com> Date: Tue, 11 Mar 2025 08:39:29 +0100 Subject: [PATCH 1/4] Added extra message when the DB parsing of DbAccountId fails, so we know for which string it failed. --- src/entities/vault_account.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/entities/vault_account.rs b/src/entities/vault_account.rs index d04b02a..f2db84c 100644 --- a/src/entities/vault_account.rs +++ b/src/entities/vault_account.rs @@ -134,7 +134,10 @@ impl sea_orm::TryGetable for DbAccountId { .try_get_by(idx) .map_err(|e| TryGetError::Null(e.to_string()))?; Ok(DbAccountId(AccountId::from_str(&value).map_err(|e| { - TryGetError::DbErr(DbErr::Custom(e.to_string())) + TryGetError::DbErr(DbErr::Custom(format!( + "Cannot parse DbAccountId for string '{}' - error: {}", + &value, e + ))) })?)) } } -- GitLab From 54e82c1b7215d4700cfa723c3431511ef9293d85 Mon Sep 17 00:00:00 2001 From: Nicolas80 <nicolas.pmail@protonmail.com> Date: Wed, 26 Mar 2025 10:31:16 +0100 Subject: [PATCH 2/4] * Adapted `impl FromStr for DbAccountId` so that it is the only one that does the conversion from &str to DbAccountId ** It provides error with the "raw" string that couldn't be parsed ** All other methods use that one now ** In order to be able to resolve the issue from !48; we now remove any surrounding double quotes from the string (if present) before parsing into a DbAccountId * Replaced `impl From<String> for DbAccountId` with `impl TryFrom<String> for DbAccountId` and adapted the code to use it. * Made sure `impl sea_orm::TryGetable for DbAccountId` also use the correct `DbAccountId::from_str` that will now provide the detailed error message --- src/commands/vault.rs | 7 ++--- src/entities/vault_account.rs | 48 ++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/commands/vault.rs b/src/commands/vault.rs index 8d6de10..2f53bbf 100644 --- a/src/commands/vault.rs +++ b/src/commands/vault.rs @@ -729,7 +729,7 @@ where println!(); let vault_account = if let Some(existing_vault_account) = - vault_account::find_by_id(db_tx, &DbAccountId::from(address_to_import.clone())).await? + vault_account::find_by_id(db_tx, &DbAccountId::try_from(address_to_import.clone())?).await? { if existing_vault_account.is_base_account() { println!("You are trying to add {address_to_import} as a <Base> account while it already exists as a <Base> account."); @@ -853,7 +853,8 @@ where println!("Trying to create derivation with address '{derivation_address}'"); println!(); let vault_account = if let Some(existing_vault_account) = - vault_account::find_by_id(db_tx, &DbAccountId::from(derivation_address.clone())).await? + vault_account::find_by_id(db_tx, &DbAccountId::try_from(derivation_address.clone())?) + .await? { // Existing account println!("You are trying to derive '{derivation_path}' from parent '{parent_address}'"); @@ -922,7 +923,7 @@ where // Since links are made based on address / parent(address) we can just edit the existing entry and it should be fine let mut vault_account: ActiveModel = existing_vault_account.into(); vault_account.path = Set(Some(derivation_path.clone())); - vault_account.parent = Set(Some(DbAccountId::from(parent_address.clone()))); + vault_account.parent = Set(Some(DbAccountId::try_from(parent_address.clone())?)); vault_account.crypto_scheme = Set(None); vault_account.encrypted_suri = Set(None); vault_account.name = Set(name.clone()); diff --git a/src/entities/vault_account.rs b/src/entities/vault_account.rs index f2db84c..4c219e6 100644 --- a/src/entities/vault_account.rs +++ b/src/entities/vault_account.rs @@ -133,21 +133,16 @@ impl sea_orm::TryGetable for DbAccountId { let value: String = res .try_get_by(idx) .map_err(|e| TryGetError::Null(e.to_string()))?; - Ok(DbAccountId(AccountId::from_str(&value).map_err(|e| { - TryGetError::DbErr(DbErr::Custom(format!( - "Cannot parse DbAccountId for string '{}' - error: {}", - &value, e - ))) - })?)) + DbAccountId::from_str(&value).map_err(|e| TryGetError::DbErr(DbErr::Custom(e.to_string()))) } } impl sea_orm::sea_query::ValueType for DbAccountId { fn try_from(v: Value) -> Result<Self, sea_orm::sea_query::ValueTypeErr> { match v { - Value::String(Some(value)) => Ok(DbAccountId( - AccountId::from_str(&value).map_err(|_| sea_orm::sea_query::ValueTypeErr)?, - )), + Value::String(Some(value)) => { + Ok(DbAccountId::from_str(&value).map_err(|_| sea_orm::sea_query::ValueTypeErr)?) + } _ => Err(sea_orm::sea_query::ValueTypeErr), } } @@ -186,19 +181,32 @@ impl Display for DbAccountId { } } -impl FromStr for DbAccountId { - type Err = GcliError; +impl TryFrom<String> for DbAccountId { + type Error = GcliError; - fn from_str(s: &str) -> Result<Self, Self::Err> { - Ok(DbAccountId( - AccountId::from_str(s).map_err(|e| GcliError::Input(e.to_string()))?, - )) + fn try_from(value: String) -> Result<Self, Self::Error> { + DbAccountId::from_str(&value) } } -impl From<String> for DbAccountId { - fn from(s: String) -> Self { - DbAccountId(AccountId::from_str(&s).expect("Invalid AccountId format")) +/// This one is used by the other methods converting from String and does potential extra cleanup of the string before parsing. +/// +/// Due to a hard to reproduce issue (!48) we added trim of double quotes (") at start and end of the string. +impl FromStr for DbAccountId { + type Err = GcliError; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + // Remove surrounding double quotes if present + let cleaned_value = s.trim_matches('"'); + + Ok(DbAccountId(AccountId::from_str(cleaned_value).map_err( + |e| { + GcliError::Input(format!( + "Cannot parse DbAccountId for raw string '{}' - error: {}", + s, e + )) + }, + )?)) } } @@ -924,12 +932,12 @@ where } None => { let vault_account = ActiveModel { - address: Set(address.to_string().into()), + address: Set(address.to_string().try_into()?), name: Set(name.cloned()), path: Set(Some(derivation_path.to_string())), crypto_scheme: Set(None), encrypted_suri: Set(None), - parent: Set(Some(parent_address.to_string().into())), + parent: Set(Some(parent_address.to_string().try_into()?)), }; vault_account.insert(db).await? } -- GitLab From 2cd0a861751fe9336ad03e847cc43dedf593e0eb Mon Sep 17 00:00:00 2001 From: Nicolas80 <nicolas.pmail@protonmail.com> Date: Wed, 26 Mar 2025 10:50:54 +0100 Subject: [PATCH 3/4] * Kind of ugly; but needed to adapt fn find_direct_children_accounts to also match lines that have extra double quotes inside the parent column ** For that; since it seems sea-orm does the `eq` part on string values; the only way I could find is to allow any of those checks *** original "parent" eq address as DbAccountId (sea-orm converts address' DbAccountId to a clean string that doesn't have the extra double quotes) *** extra "parent" eq `format!("\"{}\"",current_account.address.to_string())` for the cases where the DB got incorrect values --- src/entities/vault_account.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/entities/vault_account.rs b/src/entities/vault_account.rs index 4c219e6..80586cf 100644 --- a/src/entities/vault_account.rs +++ b/src/entities/vault_account.rs @@ -6,7 +6,6 @@ use sea_orm::entity::prelude::*; use sea_orm::prelude::async_trait::async_trait; use sea_orm::prelude::StringLen; use sea_orm::ActiveValue::Set; -use sea_orm::PaginatorTrait; use sea_orm::QueryFilter; use sea_orm::TryGetError; use sea_orm::{ @@ -14,6 +13,7 @@ use sea_orm::{ ModelTrait, QueryOrder, RelationDef, RelationTrait, TryFromU64, }; use sea_orm::{ActiveModelTrait, ConnectionTrait, PrimaryKeyTrait}; +use sea_orm::{Condition, PaginatorTrait}; use sea_orm::{DeriveActiveEnum, EntityTrait}; use std::cell::RefCell; use std::collections::HashMap; @@ -768,6 +768,9 @@ where Ok(Some(base_parent_account)) } +/// Fetches all the children accounts of current_account +/// +/// Due to a hard to reproduce issue (!48) had to adapt the filter on Parent to also match if the Parent value has those extra double quotes around the value async fn find_direct_children_accounts<C>( db: &C, current_account: &Model, @@ -776,7 +779,11 @@ where C: ConnectionTrait, { Entity::find() - .filter(Column::Parent.eq(current_account.address.clone())) + .filter( + Condition::any() + .add(Column::Parent.eq(current_account.address.clone())) + .add(Column::Parent.eq(format!("\"{}\"",current_account.address.to_string()))), + ) .order_by_asc(Column::Address) .all(db) .await -- GitLab From 5924a73e25a251385eeae430724ea13b7dd49b5c Mon Sep 17 00:00:00 2001 From: Nicolas80 <nicolas.pmail@protonmail.com> Date: Wed, 26 Mar 2025 13:26:31 +0100 Subject: [PATCH 4/4] * Added `debug!` log inside `impl From<DbAccountId> for Value` used to serialize DbAccountId into the String value for the db ** Can be seen when adding `RUST_LOG=debug ` before the `gcli` command * Small fix for clippy remark --- src/entities/vault_account.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/entities/vault_account.rs b/src/entities/vault_account.rs index 80586cf..ec81c65 100644 --- a/src/entities/vault_account.rs +++ b/src/entities/vault_account.rs @@ -2,6 +2,7 @@ use crate::commands::{cesium, vault}; use crate::runtime_config::AccountId; use crate::utils::GcliError; use anyhow::anyhow; +use log::debug; use sea_orm::entity::prelude::*; use sea_orm::prelude::async_trait::async_trait; use sea_orm::prelude::StringLen; @@ -162,7 +163,12 @@ impl sea_orm::sea_query::ValueType for DbAccountId { impl From<DbAccountId> for Value { fn from(account_id: DbAccountId) -> Self { - Value::String(Some(Box::new(account_id.0.to_string()))) + let account_id_string = account_id.0.to_string(); + debug!( + "DB converting DbAccountId to string:'{}'", + account_id_string + ); + Value::String(Some(Box::new(account_id_string))) } } @@ -782,7 +788,7 @@ where .filter( Condition::any() .add(Column::Parent.eq(current_account.address.clone())) - .add(Column::Parent.eq(format!("\"{}\"",current_account.address.to_string()))), + .add(Column::Parent.eq(format!("\"{}\"", current_account.address))), ) .order_by_asc(Column::Address) .all(db) -- GitLab