From 74e9fb43b9c61459103dedcc912cc06abfaae3c4 Mon Sep 17 00:00:00 2001 From: Antonio Ventilii <169057656+AntonioVentilii-DFINITY@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:58:28 +0200 Subject: [PATCH 1/2] feat(frontend): add BTC address calls from backend (#2013) # Motivation New functions to call the backend and generate Bitcoin addresses by network. --- src/frontend/src/lib/api/backend.api.ts | 12 ++++++++++++ src/frontend/src/lib/types/address.ts | 2 ++ 2 files changed, 14 insertions(+) diff --git a/src/frontend/src/lib/api/backend.api.ts b/src/frontend/src/lib/api/backend.api.ts index 85a8a6090f..196f3f869b 100644 --- a/src/frontend/src/lib/api/backend.api.ts +++ b/src/frontend/src/lib/api/backend.api.ts @@ -1,5 +1,6 @@ import type { AddUserCredentialError, + BitcoinNetwork, CredentialSpec, CustomToken, GetUserProfileError, @@ -14,6 +15,17 @@ import type { Identity } from '@dfinity/agent'; import type { Principal } from '@dfinity/principal'; import { toNullable, type QueryParams } from '@dfinity/utils'; +export const getBtcAddress = async ({ + identity, + network +}: { + identity: OptionIdentity; + network: BitcoinNetwork; +}): Promise => { + const { caller_btc_address } = await getBackendActor({ identity }); + return caller_btc_address(network); +}; + export const getEthAddress = async (identity: OptionIdentity): Promise => { const { caller_eth_address } = await getBackendActor({ identity }); return caller_eth_address(); diff --git a/src/frontend/src/lib/types/address.ts b/src/frontend/src/lib/types/address.ts index ce60497cb5..41b5feba7a 100644 --- a/src/frontend/src/lib/types/address.ts +++ b/src/frontend/src/lib/types/address.ts @@ -1,3 +1,5 @@ +export type BtcAddress = string; + export type EthAddress = string; export type OptionEthAddress = EthAddress | undefined | null; From 3cfd6ce0c1735a492f21a5a0be61f87c81fe724b Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 14 Aug 2024 18:20:54 +0200 Subject: [PATCH 2/2] feat(backend): Update migration states (#2021) # Motivation We need to small changes to the migration states. These states are not yet used in main, so this is just an update on a type that is unused. # Changes - Add some additional migration states. - Update the autogenerated bindings. - Add an iterator to the migration states. # Tests There is a unit test that verifies that the iterator behaves _almost_ like the `strum` iterator. --- Cargo.lock | 67 +++++++++++++------ Cargo.toml | 2 + src/backend/backend.did | 6 +- src/declarations/backend/backend.did | 6 +- src/declarations/backend/backend.did.d.ts | 8 ++- .../backend/backend.factory.certified.did.js | 6 +- .../backend/backend.factory.did.js | 6 +- src/shared/Cargo.toml | 2 + src/shared/src/impls.rs | 65 +++++++++++++++++- src/shared/src/types.rs | 17 +++-- 10 files changed, 148 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5dd5c3d8b2..330fea8af8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -551,7 +551,7 @@ version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -1092,7 +1092,7 @@ dependencies = [ "rlp", "serde", "serde_json", - "strum", + "strum 0.25.0", "tempfile", "thiserror", "tiny-keccak", @@ -1383,6 +1383,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -1555,8 +1561,8 @@ dependencies = [ "phantom_newtype", "prost", "serde", - "strum", - "strum_macros", + "strum 0.25.0", + "strum_macros 0.25.3", ] [[package]] @@ -1984,7 +1990,7 @@ dependencies = [ "serde", "serde_bytes", "serde_cbor", - "strum_macros", + "strum_macros 0.25.3", "subtle", "zeroize", ] @@ -2008,8 +2014,8 @@ dependencies = [ "phantom_newtype", "serde", "serde_cbor", - "strum", - "strum_macros", + "strum 0.25.0", + "strum_macros 0.25.3", "thiserror", "zeroize", ] @@ -2093,8 +2099,8 @@ source = "git+https://github.com/dfinity/ic?rev=e69bcc7b319cbb3ebc22ec55af352877 dependencies = [ "ic-utils", "serde", - "strum", - "strum_macros", + "strum 0.25.0", + "strum_macros 0.25.3", ] [[package]] @@ -2112,8 +2118,8 @@ dependencies = [ "serde", "serde_bytes", "serde_cbor", - "strum", - "strum_macros", + "strum 0.25.0", + "strum_macros 0.25.3", ] [[package]] @@ -2207,8 +2213,8 @@ dependencies = [ "serde_cbor", "serde_json", "serde_with", - "strum", - "strum_macros", + "strum 0.25.0", + "strum_macros 0.25.3", "thiserror", "thousands", ] @@ -2306,7 +2312,7 @@ dependencies = [ "multibase", "serde", "serde_json", - "strum", + "strum 0.25.0", "thiserror", "time", "url", @@ -2330,7 +2336,7 @@ dependencies = [ "serde-aux", "serde_json", "serde_repr", - "strum", + "strum 0.25.0", "thiserror", "url", ] @@ -2344,7 +2350,7 @@ dependencies = [ "form_urlencoded", "identity_core", "serde", - "strum", + "strum 0.25.0", "thiserror", ] @@ -2359,7 +2365,7 @@ dependencies = [ "identity_verification", "indexmap 2.2.6", "serde", - "strum", + "strum 0.25.0", "thiserror", ] @@ -2389,7 +2395,7 @@ dependencies = [ "identity_jose", "serde", "serde_json", - "strum", + "strum 0.25.0", "thiserror", ] @@ -3829,6 +3835,8 @@ dependencies = [ "ic-verifiable-credentials", "serde", "serde_bytes", + "strum 0.26.3", + "strum_macros 0.26.4", ] [[package]] @@ -3958,16 +3966,35 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" dependencies = [ - "strum_macros", + "strum_macros 0.25.3", ] +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" + [[package]] name = "strum_macros" version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ - "heck", + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", "proc-macro2", "quote", "rustversion", diff --git a/Cargo.toml b/Cargo.toml index a5d99a6314..c56e567103 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,5 @@ lazy_static = "1.4.0" pocket-ic = "2.2.0" pretty_assertions = "1.4.0" bitcoin = "0.32.2" +strum = "0.26.3" +strum_macros = "0.26.4" diff --git a/src/backend/backend.did b/src/backend/backend.did index 1e464836b5..972a784a6a 100644 --- a/src/backend/backend.did +++ b/src/backend/backend.did @@ -80,10 +80,12 @@ type ListUsersResponse = record { matches_max_length : nat64; }; type MigrationProgress = variant { - MigratedUserTokensUpTo : principal; + MigratedUserTokensUpTo : opt principal; + MigratedUserTimestampsUpTo : opt principal; TargetPreCheckOk; - MigratedCustomTokensUpTo : principal; + MigratedCustomTokensUpTo : opt principal; Locked; + MigratedUserProfilesUpTo : opt record { nat64; principal }; CheckingTargetCanister; TargetLocked; Completed; diff --git a/src/declarations/backend/backend.did b/src/declarations/backend/backend.did index 1e464836b5..972a784a6a 100644 --- a/src/declarations/backend/backend.did +++ b/src/declarations/backend/backend.did @@ -80,10 +80,12 @@ type ListUsersResponse = record { matches_max_length : nat64; }; type MigrationProgress = variant { - MigratedUserTokensUpTo : principal; + MigratedUserTokensUpTo : opt principal; + MigratedUserTimestampsUpTo : opt principal; TargetPreCheckOk; - MigratedCustomTokensUpTo : principal; + MigratedCustomTokensUpTo : opt principal; Locked; + MigratedUserProfilesUpTo : opt record { nat64; principal }; CheckingTargetCanister; TargetLocked; Completed; diff --git a/src/declarations/backend/backend.did.d.ts b/src/declarations/backend/backend.did.d.ts index 89e1790fc0..3acefe482e 100644 --- a/src/declarations/backend/backend.did.d.ts +++ b/src/declarations/backend/backend.did.d.ts @@ -89,10 +89,14 @@ export interface ListUsersResponse { matches_max_length: bigint; } export type MigrationProgress = - | { MigratedUserTokensUpTo: Principal } + | { + MigratedUserTokensUpTo: [] | [Principal]; + } + | { MigratedUserTimestampsUpTo: [] | [Principal] } | { TargetPreCheckOk: null } - | { MigratedCustomTokensUpTo: Principal } + | { MigratedCustomTokensUpTo: [] | [Principal] } | { Locked: null } + | { MigratedUserProfilesUpTo: [] | [[bigint, Principal]] } | { CheckingTargetCanister: null } | { TargetLocked: null } | { Completed: null } diff --git a/src/declarations/backend/backend.factory.certified.did.js b/src/declarations/backend/backend.factory.certified.did.js index 82eee2abe5..e9ba6e3215 100644 --- a/src/declarations/backend/backend.factory.certified.did.js +++ b/src/declarations/backend/backend.factory.certified.did.js @@ -140,10 +140,12 @@ export const idlFactory = ({ IDL }) => { matches_max_length: IDL.Nat64 }); const MigrationProgress = IDL.Variant({ - MigratedUserTokensUpTo: IDL.Principal, + MigratedUserTokensUpTo: IDL.Opt(IDL.Principal), + MigratedUserTimestampsUpTo: IDL.Opt(IDL.Principal), TargetPreCheckOk: IDL.Null, - MigratedCustomTokensUpTo: IDL.Principal, + MigratedCustomTokensUpTo: IDL.Opt(IDL.Principal), Locked: IDL.Null, + MigratedUserProfilesUpTo: IDL.Opt(IDL.Tuple(IDL.Nat64, IDL.Principal)), CheckingTargetCanister: IDL.Null, TargetLocked: IDL.Null, Completed: IDL.Null, diff --git a/src/declarations/backend/backend.factory.did.js b/src/declarations/backend/backend.factory.did.js index 31e50862d1..6a42a3e2c6 100644 --- a/src/declarations/backend/backend.factory.did.js +++ b/src/declarations/backend/backend.factory.did.js @@ -140,10 +140,12 @@ export const idlFactory = ({ IDL }) => { matches_max_length: IDL.Nat64 }); const MigrationProgress = IDL.Variant({ - MigratedUserTokensUpTo: IDL.Principal, + MigratedUserTokensUpTo: IDL.Opt(IDL.Principal), + MigratedUserTimestampsUpTo: IDL.Opt(IDL.Principal), TargetPreCheckOk: IDL.Null, - MigratedCustomTokensUpTo: IDL.Principal, + MigratedCustomTokensUpTo: IDL.Opt(IDL.Principal), Locked: IDL.Null, + MigratedUserProfilesUpTo: IDL.Opt(IDL.Tuple(IDL.Nat64, IDL.Principal)), CheckingTargetCanister: IDL.Null, TargetLocked: IDL.Null, Completed: IDL.Null, diff --git a/src/shared/Cargo.toml b/src/shared/Cargo.toml index 16ad703ce3..866a7e572c 100644 --- a/src/shared/Cargo.toml +++ b/src/shared/Cargo.toml @@ -13,4 +13,6 @@ ic-metrics-encoder = { workspace = true } ic-verifiable-credentials = { workspace = true } serde = { workspace = true } serde_bytes = { workspace = true } +strum = { workspace = true } +strum_macros = { workspace = true } diff --git a/src/shared/src/impls.rs b/src/shared/src/impls.rs index ce6fd12527..9513836160 100644 --- a/src/shared/src/impls.rs +++ b/src/shared/src/impls.rs @@ -4,13 +4,15 @@ use crate::types::user_profile::{ AddUserCredentialError, OisyUser, StoredUserProfile, UserCredential, UserProfile, }; use crate::types::{ - ApiEnabled, Config, CredentialType, InitArg, Migration, MigrationReport, Timestamp, - TokenVersion, Version, + ApiEnabled, Config, CredentialType, InitArg, Migration, MigrationProgress, MigrationReport, + Timestamp, TokenVersion, Version, }; use candid::Principal; use ic_canister_sig_creation::{extract_raw_root_pk_from_der, IC_ROOT_PK_DER}; use std::collections::BTreeMap; use std::fmt; +#[cfg(test)] +use strum::IntoEnumIterator; impl From<&Token> for CustomTokenId { fn from(token: &Token) -> Self { @@ -213,3 +215,62 @@ fn test_api_enabled() { assert_eq!(ApiEnabled::Disabled.readable(), false); assert_eq!(ApiEnabled::Disabled.writable(), false); } + +impl MigrationProgress { + /// The next phase in the migration process. + /// + /// Note: A given phase, such as migrating a `BTreeMap`, may need multiple steps. + /// The code for that phase will have to keep track of those steps by means of the data in the variant. + /// + /// Prior art: + /// - There is an `enum_iterator` crate, however it deals only with simple enums + /// without variant fields. In this implementation, `next()` always uses the default value for + /// the new field, which is always None. `next()` does NOT step through the values of the + /// variant field. + /// - `strum` has the `EnumIter` derive macro, but that implements `.next()` on an iterator, not on the + /// enum itself, so stepping from one variant to the next is not straightforward. + /// + /// Note: The next state after Completed is Completed, so the the iterator will run + /// indefinitely. In our case returning an option and ending with None would be fine but needs + /// additional code that we don't need. + #[must_use] + pub fn next(&self) -> Self { + match self { + MigrationProgress::Pending => MigrationProgress::Locked, + MigrationProgress::Locked => MigrationProgress::TargetLocked, + MigrationProgress::TargetLocked => MigrationProgress::TargetPreCheckOk, + MigrationProgress::TargetPreCheckOk => MigrationProgress::MigratedUserTokensUpTo(None), + MigrationProgress::MigratedUserTokensUpTo(_) => { + MigrationProgress::MigratedCustomTokensUpTo(None) + } + MigrationProgress::MigratedCustomTokensUpTo(_) => { + MigrationProgress::MigratedUserTimestampsUpTo(None) + } + MigrationProgress::MigratedUserTimestampsUpTo(_) => { + MigrationProgress::MigratedUserProfilesUpTo(None) + } + MigrationProgress::MigratedUserProfilesUpTo(_) => { + MigrationProgress::CheckingTargetCanister + } + MigrationProgress::CheckingTargetCanister | MigrationProgress::Completed => { + MigrationProgress::Completed + } + } + } +} + +// `MigrationProgress::next(&self)` should list all the elements in the enum in order, but stop at Completed. +#[test] +fn next_matches_strum_iter() { + let mut iter = MigrationProgress::iter(); + let mut next = MigrationProgress::Pending; + while next != MigrationProgress::Completed { + assert_eq!(iter.next(), Some(next), "iter.next() != Some(next)"); + next = next.next(); + } + assert_eq!( + next, + next.next(), + "Once completed, it should stay completed" + ); +} diff --git a/src/shared/src/types.rs b/src/shared/src/types.rs index be688cb976..2ed172f746 100644 --- a/src/shared/src/types.rs +++ b/src/shared/src/types.rs @@ -1,6 +1,7 @@ use candid::{CandidType, Deserialize, Principal}; use ic_cdk_timers::TimerId; use std::fmt::Debug; +use strum_macros::{EnumCount as EnumCountMacro, EnumIter}; pub type Timestamp = u64; @@ -240,7 +241,9 @@ pub mod user_profile { } /// The current state of progress of a user data migration. -#[derive(CandidType, Deserialize, Copy, Clone, Eq, PartialEq, Debug, Default)] +#[derive( + CandidType, Deserialize, Copy, Clone, Eq, PartialEq, Debug, Default, EnumCountMacro, EnumIter, +)] pub enum MigrationProgress { // WARNING: The following are subject to change. The migration has NOT been implemented yet. // TODO: Remove warning once the migration has been implemented. @@ -253,10 +256,14 @@ pub enum MigrationProgress { TargetLocked, /// Target canister was empty. TargetPreCheckOk, - /// Tokens have been migrated up to but excluding the given principal. - MigratedUserTokensUpTo(Principal), - /// Custom tokens have been migrated up to but excluding the given principal. - MigratedCustomTokensUpTo(Principal), + /// Tokens have been migrated up to (but excluding) the given principal. + MigratedUserTokensUpTo(Option), + /// Custom tokens have been migrated up to (but excluding) the given principal. + MigratedCustomTokensUpTo(Option), + /// Migrated user profile timestamps up to the given principal. + MigratedUserTimestampsUpTo(Option), + /// Migrated user profiles up to the given timestamp/user pair. + MigratedUserProfilesUpTo(Option<(Timestamp, Principal)>), /// Checking that the target canister has all the data. CheckingTargetCanister, /// Migration has been completed.