From c8b5326b7250e96386e1797b15b329e0b7771e5e Mon Sep 17 00:00:00 2001 From: Brian Corbin Date: Fri, 18 Feb 2022 11:40:27 -0800 Subject: [PATCH] Supporting FOG enabled accounts (#254) Co-authored-by: Colin Carey --- Cargo.lock | 1 + full-service/Cargo.toml | 1 + .../down.sql | 1 + .../up.sql | 1 + full-service/src/db/account.rs | 131 +++++++++--- full-service/src/db/assigned_subaddress.rs | 4 + full-service/src/db/models.rs | 3 + full-service/src/db/schema.rs | 1 + full-service/src/db/wallet_db_error.rs | 21 ++ full-service/src/json_rpc/account.rs | 7 + full-service/src/json_rpc/e2e.rs | 198 +++++++++++++----- full-service/src/json_rpc/json_rpc_request.rs | 3 + full-service/src/json_rpc/wallet.rs | 12 +- full-service/src/service/account.rs | 26 ++- full-service/src/service/gift_code.rs | 6 +- full-service/src/service/receipt.rs | 16 +- full-service/src/service/transaction.rs | 10 +- full-service/src/service/txo.rs | 4 +- 18 files changed, 343 insertions(+), 103 deletions(-) create mode 100644 full-service/migrations/2022-02-15-200456_fog_enabled_accounts/down.sql create mode 100644 full-service/migrations/2022-02-15-200456_fog_enabled_accounts/up.sql diff --git a/Cargo.lock b/Cargo.lock index 3cfe3bb9b..23264e255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2310,6 +2310,7 @@ dependencies = [ name = "mc-full-service" version = "1.4.0" dependencies = [ + "base64 0.13.0", "bs58", "chrono", "crossbeam-channel", diff --git a/full-service/Cargo.toml b/full-service/Cargo.toml index fde57a237..e2db3c5b4 100644 --- a/full-service/Cargo.toml +++ b/full-service/Cargo.toml @@ -38,6 +38,7 @@ mc-util-parse = { path = "../mobilecoin/util/parse" } mc-util-serial = { path = "../mobilecoin/util/serial", default-features = false } mc-util-uri = { path = "../mobilecoin/util/uri" } +base64 = "0.13.0" chrono = { version = "0.4", default-features = false, features = ["alloc"] } crossbeam-channel = "0.5" diesel = { version = "1.4.6", features = ["sqlcipher-bundled"] } diff --git a/full-service/migrations/2022-02-15-200456_fog_enabled_accounts/down.sql b/full-service/migrations/2022-02-15-200456_fog_enabled_accounts/down.sql new file mode 100644 index 000000000..10b52b017 --- /dev/null +++ b/full-service/migrations/2022-02-15-200456_fog_enabled_accounts/down.sql @@ -0,0 +1 @@ +ALTER TABLE accounts DROP COLUMN fog_enabled; \ No newline at end of file diff --git a/full-service/migrations/2022-02-15-200456_fog_enabled_accounts/up.sql b/full-service/migrations/2022-02-15-200456_fog_enabled_accounts/up.sql new file mode 100644 index 000000000..5bc1c3cf3 --- /dev/null +++ b/full-service/migrations/2022-02-15-200456_fog_enabled_accounts/up.sql @@ -0,0 +1 @@ +ALTER TABLE accounts ADD COLUMN fog_enabled BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index f9b81dfc1..4061069e4 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -94,6 +94,7 @@ pub trait AccountModel { import_block_index: Option, next_subaddress_index: Option, name: &str, + fog_enabled: bool, conn: &PooledConnection>, ) -> Result<(AccountID, String), WalletDbError>; @@ -183,14 +184,13 @@ impl AccountModel for Account { fog_authority_spki: Option, conn: &PooledConnection>, ) -> Result<(AccountID, String), WalletDbError> { - let account_key = Slip10Key::from(mnemonic.clone()) - .try_into_account_key( - &fog_report_url.unwrap_or_else(|| "".to_string()), - &fog_report_id.unwrap_or_else(|| "".to_string()), - &hex::decode(fog_authority_spki.unwrap_or_else(|| "".to_string())) - .expect("invalid spki"), - ) - .unwrap(); + let fog_enabled = fog_report_url.is_some(); + + let account_key = Slip10Key::from(mnemonic.clone()).try_into_account_key( + &fog_report_url.unwrap_or_else(|| "".to_string()), + &fog_report_id.unwrap_or_else(|| "".to_string()), + &base64::decode(fog_authority_spki.unwrap_or_else(|| "".to_string()))?, + )?; Account::create( mnemonic.entropy(), @@ -200,6 +200,7 @@ impl AccountModel for Account { import_block_index, next_subaddress_index, name, + fog_enabled, conn, ) } @@ -215,12 +216,16 @@ impl AccountModel for Account { fog_authority_spki: Option, conn: &PooledConnection>, ) -> Result<(AccountID, String), WalletDbError> { + let fog_enabled = fog_report_url.is_some(); + let root_id = RootIdentity { root_entropy: entropy.clone(), fog_report_url: fog_report_url.unwrap_or_else(|| "".to_string()), fog_report_id: fog_report_id.unwrap_or_else(|| "".to_string()), - fog_authority_spki: hex::decode(fog_authority_spki.unwrap_or_else(|| "".to_string())) - .expect("invalid spki"), + fog_authority_spki: base64::decode( + fog_authority_spki.unwrap_or_else(|| "".to_string()), + ) + .expect("invalid spki"), }; let account_key = AccountKey::from(&root_id); @@ -232,6 +237,7 @@ impl AccountModel for Account { import_block_index, next_subaddress_index, name, + fog_enabled, conn, ) } @@ -244,6 +250,7 @@ impl AccountModel for Account { import_block_index: Option, next_subaddress_index: Option, name: &str, + fog_enabled: bool, conn: &PooledConnection>, ) -> Result<(AccountID, String), WalletDbError> { use crate::db::schema::accounts; @@ -251,6 +258,18 @@ impl AccountModel for Account { let account_id = AccountID::from(account_key); let fb = first_block_index.unwrap_or(DEFAULT_FIRST_BLOCK_INDEX); + let change_subaddress_index = if fog_enabled { + DEFAULT_SUBADDRESS_INDEX as i64 + } else { + DEFAULT_CHANGE_SUBADDRESS_INDEX as i64 + }; + + let next_subaddress_index = if fog_enabled { + 1 + } else { + next_subaddress_index.unwrap_or(DEFAULT_NEXT_SUBADDRESS_INDEX) as i64 + }; + let new_account = NewAccount { account_id_hex: &account_id.to_string(), account_key: &mc_util_serial::encode(account_key), /* FIXME: WS-6 - add @@ -258,13 +277,13 @@ impl AccountModel for Account { entropy, key_derivation_version: key_derivation_version as i32, main_subaddress_index: DEFAULT_SUBADDRESS_INDEX as i64, - change_subaddress_index: DEFAULT_CHANGE_SUBADDRESS_INDEX as i64, - next_subaddress_index: next_subaddress_index.unwrap_or(DEFAULT_NEXT_SUBADDRESS_INDEX) - as i64, + change_subaddress_index, + next_subaddress_index, first_block_index: fb as i64, next_block_index: fb as i64, import_block_index: import_block_index.map(|i| i as i64), name, + fog_enabled, }; diesel::insert_into(accounts::table) @@ -279,18 +298,19 @@ impl AccountModel for Account { "Main", conn, )?; - - let _change_subaddress_b58 = AssignedSubaddress::create( - account_key, - None, /* FIXME: WS-8 - Address Book Entry if details provided, or None - * always for main? */ - DEFAULT_CHANGE_SUBADDRESS_INDEX, - "Change", - conn, - )?; - - for subaddress_index in 2..next_subaddress_index.unwrap_or(DEFAULT_NEXT_SUBADDRESS_INDEX) { - AssignedSubaddress::create(account_key, None, subaddress_index, "", conn)?; + if !fog_enabled { + AssignedSubaddress::create( + account_key, + None, /* FIXME: WS-8 - Address Book Entry if details provided, or None + * always for main? */ + DEFAULT_CHANGE_SUBADDRESS_INDEX, + "Change", + conn, + )?; + + for subaddress_index in 2..next_subaddress_index { + AssignedSubaddress::create(account_key, None, subaddress_index as u64, "", conn)?; + } } Ok((account_id, main_subaddress_b58)) @@ -501,6 +521,7 @@ mod tests { next_block_index: 0, import_block_index: None, name: "Alice's Main Account".to_string(), + fog_enabled: false, }; assert_eq!(expected_account, acc); @@ -563,6 +584,7 @@ mod tests { next_block_index: 51, import_block_index: Some(50), name: "".to_string(), + fog_enabled: false, }; assert_eq!(expected_account_secondary, acc_secondary); @@ -630,4 +652,63 @@ mod tests { let decoded_account_key: AccountKey = mc_util_serial::decode(&account.account_key).unwrap(); assert_eq!(decoded_account_key, account_key); } + + #[test_with_logger] + fn test_create_fog_account(logger: Logger) { + let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); + + let db_test_context = WalletDbTestContext::default(); + let wallet_db = db_test_context.get_db_instance(logger); + + let root_id = RootIdentity::from_random(&mut rng); + let account_id_hex = { + let conn = wallet_db.get_conn().unwrap(); + let (account_id_hex, _public_address_b58) = Account::create_from_root_entropy( + &root_id.root_entropy, + Some(0), + None, + None, + "Alice's FOG Account", + Some("fog//some.fog.url".to_string()), + Some("".to_string()), + Some("DefinitelyARealFOGAuthoritySPKI".to_string()), + &conn, + ) + .unwrap(); + account_id_hex + }; + + { + let conn = wallet_db.get_conn().unwrap(); + let res = Account::list_all(&conn).unwrap(); + assert_eq!(res.len(), 1); + } + + let acc = Account::get(&account_id_hex, &wallet_db.get_conn().unwrap()).unwrap(); + let expected_account = Account { + id: 1, + account_id_hex: account_id_hex.to_string(), + account_key: [ + 10, 34, 10, 32, 129, 223, 141, 215, 200, 104, 120, 117, 123, 154, 151, 210, 253, + 23, 148, 151, 2, 18, 182, 100, 83, 138, 144, 99, 225, 74, 214, 14, 175, 68, 167, 4, + 18, 34, 10, 32, 24, 98, 18, 92, 9, 50, 142, 184, 114, 99, 34, 125, 211, 54, 146, + 33, 98, 71, 179, 56, 136, 67, 98, 97, 230, 228, 31, 194, 119, 169, 189, 8, 26, 17, + 102, 111, 103, 47, 47, 115, 111, 109, 101, 46, 102, 111, 103, 46, 117, 114, 108, + 42, 23, 13, 231, 226, 158, 43, 94, 151, 32, 17, 121, 169, 69, 56, 96, 46, 182, 26, + 43, 138, 220, 146, 60, 162, + ] + .to_vec(), + entropy: root_id.root_entropy.bytes.to_vec(), + key_derivation_version: 1, + main_subaddress_index: 0, + change_subaddress_index: 0, + next_subaddress_index: 1, + first_block_index: 0, + next_block_index: 0, + import_block_index: None, + name: "Alice's FOG Account".to_string(), + fog_enabled: true, + }; + assert_eq!(expected_account, acc); + } } diff --git a/full-service/src/db/assigned_subaddress.rs b/full-service/src/db/assigned_subaddress.rs index 408499dec..a0edc2fa6 100644 --- a/full-service/src/db/assigned_subaddress.rs +++ b/full-service/src/db/assigned_subaddress.rs @@ -145,6 +145,10 @@ impl AssignedSubaddressModel for AssignedSubaddress { let account = Account::get(&AccountID(account_id_hex.to_string()), conn)?; + if account.fog_enabled { + return Err(WalletDbError::SubaddressesNotSupportedForFOGEnabledAccounts); + } + let account_key: AccountKey = mc_util_serial::decode(&account.account_key)?; let account_view_key = account_key.view_key(); let subaddress_index = account.next_subaddress_index; diff --git a/full-service/src/db/models.rs b/full-service/src/db/models.rs index 50bd2cfed..426a3583d 100644 --- a/full-service/src/db/models.rs +++ b/full-service/src/db/models.rs @@ -100,6 +100,8 @@ pub struct Account { pub import_block_index: Option, /// Name of this account. pub name: String, /* empty string for nullable */ + /// Fog enabled address + pub fog_enabled: bool, } /// A structure that can be inserted to create a new entity in the `accounts` @@ -118,6 +120,7 @@ pub struct NewAccount<'a> { pub next_block_index: i64, pub import_block_index: Option, pub name: &'a str, + pub fog_enabled: bool, } /// A transaction output entity that either was received to an Account in this diff --git a/full-service/src/db/schema.rs b/full-service/src/db/schema.rs index 7b6e93cab..460dd2aaf 100644 --- a/full-service/src/db/schema.rs +++ b/full-service/src/db/schema.rs @@ -12,6 +12,7 @@ table! { next_block_index -> BigInt, import_block_index -> Nullable, name -> Text, + fog_enabled -> Bool, } } diff --git a/full-service/src/db/wallet_db_error.rs b/full-service/src/db/wallet_db_error.rs index d18a7f6d4..c61ae5a7d 100644 --- a/full-service/src/db/wallet_db_error.rs +++ b/full-service/src/db/wallet_db_error.rs @@ -119,6 +119,15 @@ pub enum WalletDbError { /// Error converting to/from API protos: {0} ProtoConversion(mc_api::ConversionError), + + /// Error while generating a Slip10Key: {0} + Slip10Key(mc_account_keys_slip10::Error), + + /// Decode from Base64 error: {0} + Base64Decode(base64::DecodeError), + + /// Subaddresses are not supported for FOG enabled accounts + SubaddressesNotSupportedForFOGEnabledAccounts, } impl From for WalletDbError { @@ -168,3 +177,15 @@ impl From for WalletDbError { Self::LedgerDB(src) } } + +impl From for WalletDbError { + fn from(src: mc_account_keys_slip10::Error) -> Self { + Self::Slip10Key(src) + } +} + +impl From for WalletDbError { + fn from(src: base64::DecodeError) -> Self { + Self::Base64Decode(src) + } +} diff --git a/full-service/src/json_rpc/account.rs b/full-service/src/json_rpc/account.rs index 79921aca9..d918c6a09 100644 --- a/full-service/src/json_rpc/account.rs +++ b/full-service/src/json_rpc/account.rs @@ -46,6 +46,12 @@ pub struct Account { /// found TXOs. It is recommended to move all MOB to another account after /// recovery if the user is unsure of the assigned addresses. pub recovery_mode: bool, + + /// A flag that indicates if this account is FOG enabled, which means that + /// it will send any change to it's main subaddress (index 0) instead of + /// the default change subaddress (index 1). It also generates + /// PublicAddressB58's with fog credentials. + pub fog_enabled: bool, } impl TryFrom<&db::models::Account> for Account { @@ -68,6 +74,7 @@ impl TryFrom<&db::models::Account> for Account { first_block_index: (src.first_block_index as u64).to_string(), next_block_index: (src.next_block_index as u64).to_string(), recovery_mode: false, + fog_enabled: src.fog_enabled, }) } } diff --git a/full-service/src/json_rpc/e2e.rs b/full-service/src/json_rpc/e2e.rs index e0b5d9329..7477a56cc 100644 --- a/full-service/src/json_rpc/e2e.rs +++ b/full-service/src/json_rpc/e2e.rs @@ -50,6 +50,7 @@ mod e2e { assert!(account_obj.get("main_address").is_some()); assert_eq!(account_obj.get("next_subaddress_index").unwrap(), "2"); assert_eq!(account_obj.get("recovery_mode").unwrap(), false); + assert_eq!(account_obj.get("fog_enabled").unwrap(), false); let account_id = account_obj.get("account_id").unwrap(); @@ -142,6 +143,36 @@ mod e2e { assert_eq!(accounts.len(), 0); } + #[test_with_logger] + fn test_e2e_create_account_with_fog(logger: Logger) { + let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); + let (client, _ledger_db, _db_ctx, _network_state) = setup(&mut rng, logger.clone()); + // Create Account + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "create_account", + "params": { + "name": "Alice Main Account", + "fog_report_url": "fog://fog-report.example.com", + "fog_report_id": "", + "fog_authority_spki": "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvnB9wTbTOT5uoizRYaYbw7XIEkInl8E7MGOAQj+xnC+F1rIXiCnc/t1+5IIWjbRGhWzo7RAwI5sRajn2sT4rRn9NXbOzZMvIqE4hmhmEzy1YQNDnfALAWNQ+WBbYGW+Vqm3IlQvAFFjVN1YYIdYhbLjAPdkgeVsWfcLDforHn6rR3QBZYZIlSBQSKRMY/tywTxeTCvK2zWcS0kbbFPtBcVth7VFFVPAZXhPi9yy1AvnldO6n7KLiupVmojlEMtv4FQkk604nal+j/dOplTATV8a9AJBbPRBZ/yQg57EG2Y2MRiHOQifJx0S5VbNyMm9bkS8TD7Goi59aCW6OT1gyeotWwLg60JRZTfyJ7lYWBSOzh0OnaCytRpSWtNZ6barPUeOnftbnJtE8rFhF7M4F66et0LI/cuvXYecwVwykovEVBKRF4HOK9GgSm17mQMtzrD7c558TbaucOWabYR04uhdAc3s10MkuONWG0wIQhgIChYVAGnFLvSpp2/aQEq3xrRSETxsixUIjsZyWWROkuA0IFnc8d7AmcnUBvRW7FT/5thWyk5agdYUGZ+7C1o69ihR1YxmoGh69fLMPIEOhYh572+3ckgl2SaV4uo9Gvkz8MMGRBcMIMlRirSwhCfozV2RyT5Wn1NgPpyc8zJL7QdOhL7Qxb+5WjnCVrQYHI2cCAwEAAQ==" + }, + }); + + let res = dispatch(&client, body, &logger); + assert_eq!(res.get("jsonrpc").unwrap(), "2.0"); + + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + assert!(account_obj.get("account_id").is_some()); + assert_eq!(account_obj.get("name").unwrap(), "Alice Main Account"); + assert_eq!(account_obj.get("recovery_mode").unwrap(), false); + assert!(account_obj.get("main_address").is_some()); + assert_eq!(account_obj.get("next_subaddress_index").unwrap(), "1"); + assert_eq!(account_obj.get("fog_enabled").unwrap(), true); + } + #[test_with_logger] fn test_e2e_import_account(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); @@ -173,6 +204,8 @@ mod e2e { *account_obj.get("first_block_index").unwrap(), serde_json::json!("200") ); + assert_eq!(account_obj.get("next_subaddress_index").unwrap(), "2"); + assert_eq!(account_obj.get("fog_enabled").unwrap(), false); } #[test_with_logger] @@ -242,6 +275,77 @@ mod e2e { *account_obj.get("first_block_index").unwrap(), serde_json::json!("200") ); + assert_eq!(account_obj.get("next_subaddress_index").unwrap(), "2"); + assert_eq!(account_obj.get("fog_enabled").unwrap(), false); + } + + #[test_with_logger] + fn test_e2e_import_account_fog(logger: Logger) { + let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); + let (client, _ledger_db, _db_ctx, _network_state) = setup(&mut rng, logger.clone()); + + // Import an account with fog info. + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "import_account", + "params": { + "mnemonic": "sheriff odor square mistake huge skate mouse shoot purity weapon proof stuff correct concert blanket neck own shift clay mistake air viable stick group", + "key_derivation_version": "2", + "name": "Alice Main Account", + "first_block_index": "200", + "fog_report_url": "fog://fog-report.example.com", + "fog_report_id": "", + "fog_authority_spki": "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvnB9wTbTOT5uoizRYaYbw7XIEkInl8E7MGOAQj+xnC+F1rIXiCnc/t1+5IIWjbRGhWzo7RAwI5sRajn2sT4rRn9NXbOzZMvIqE4hmhmEzy1YQNDnfALAWNQ+WBbYGW+Vqm3IlQvAFFjVN1YYIdYhbLjAPdkgeVsWfcLDforHn6rR3QBZYZIlSBQSKRMY/tywTxeTCvK2zWcS0kbbFPtBcVth7VFFVPAZXhPi9yy1AvnldO6n7KLiupVmojlEMtv4FQkk604nal+j/dOplTATV8a9AJBbPRBZ/yQg57EG2Y2MRiHOQifJx0S5VbNyMm9bkS8TD7Goi59aCW6OT1gyeotWwLg60JRZTfyJ7lYWBSOzh0OnaCytRpSWtNZ6barPUeOnftbnJtE8rFhF7M4F66et0LI/cuvXYecwVwykovEVBKRF4HOK9GgSm17mQMtzrD7c558TbaucOWabYR04uhdAc3s10MkuONWG0wIQhgIChYVAGnFLvSpp2/aQEq3xrRSETxsixUIjsZyWWROkuA0IFnc8d7AmcnUBvRW7FT/5thWyk5agdYUGZ+7C1o69ihR1YxmoGh69fLMPIEOhYh572+3ckgl2SaV4uo9Gvkz8MMGRBcMIMlRirSwhCfozV2RyT5Wn1NgPpyc8zJL7QdOhL7Qxb+5WjnCVrQYHI2cCAwEAAQ==" + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + let public_address = account_obj.get("main_address").unwrap().as_str().unwrap(); + assert_eq!(public_address, "2kD4vRp3DaBdRrNLNhJ5BKf5FsZxcAijoMt5pxjJpbk5jQRubngUXnd92vuXWkFyezuLgjCiKu4JHjpjNCnmzf1gAdW6PbqXsecQtp8Qr8uoeeDKrd1a5PtA6apXuDVtnrKsDCcHiJqdeSt3bRsPBvkBP4JqpGyAeKFsC7s2LQwuZ88BxFe2kyeZp5G3zENfvLaMripxTKkWGDopok2LCyA9NiCDf1vwjA5opLU7eqaRfh9"); + let account_id = account_obj.get("account_id").unwrap().as_str().unwrap(); + assert_eq!( + account_id, + "0b8a95253a7d57faf8510d8092ab55fb8610a9d691a7fa3bfafbf49945b845a2" + ); + + assert_eq!(account_obj.get("next_subaddress_index").unwrap(), "1"); + assert_eq!(account_obj.get("fog_enabled").unwrap(), true); + } + + #[test_with_logger] + fn test_e2e_import_account_legacy_fog(logger: Logger) { + let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); + let (client, _ledger_db, _db_ctx, _network_state) = setup(&mut rng, logger.clone()); + + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "import_account_from_legacy_root_entropy", + "params": { + "entropy": "c593274dc6f6eb94242e34ae5f0ab16bc3085d45d49d9e18b8a8c6f057e6b56b", + "name": "Alice Main Account", + "first_block_index": "200", + "fog_report_url": "fog://fog-report.example.com", + "fog_report_id": "", + "fog_authority_spki": "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvnB9wTbTOT5uoizRYaYbw7XIEkInl8E7MGOAQj+xnC+F1rIXiCnc/t1+5IIWjbRGhWzo7RAwI5sRajn2sT4rRn9NXbOzZMvIqE4hmhmEzy1YQNDnfALAWNQ+WBbYGW+Vqm3IlQvAFFjVN1YYIdYhbLjAPdkgeVsWfcLDforHn6rR3QBZYZIlSBQSKRMY/tywTxeTCvK2zWcS0kbbFPtBcVth7VFFVPAZXhPi9yy1AvnldO6n7KLiupVmojlEMtv4FQkk604nal+j/dOplTATV8a9AJBbPRBZ/yQg57EG2Y2MRiHOQifJx0S5VbNyMm9bkS8TD7Goi59aCW6OT1gyeotWwLg60JRZTfyJ7lYWBSOzh0OnaCytRpSWtNZ6barPUeOnftbnJtE8rFhF7M4F66et0LI/cuvXYecwVwykovEVBKRF4HOK9GgSm17mQMtzrD7c558TbaucOWabYR04uhdAc3s10MkuONWG0wIQhgIChYVAGnFLvSpp2/aQEq3xrRSETxsixUIjsZyWWROkuA0IFnc8d7AmcnUBvRW7FT/5thWyk5agdYUGZ+7C1o69ihR1YxmoGh69fLMPIEOhYh572+3ckgl2SaV4uo9Gvkz8MMGRBcMIMlRirSwhCfozV2RyT5Wn1NgPpyc8zJL7QdOhL7Qxb+5WjnCVrQYHI2cCAwEAAQ==" + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + let public_address = account_obj.get("main_address").unwrap().as_str().unwrap(); + assert_eq!(public_address, "d3FhtyUQDYJFpEmzoXmRtF9VA5FTLycgQBKf1JEJJj8K6UXCuwzGD2uVYw1cxzZpbSivZLSxf9nZpMgUnuRxSpJA9qCDpDZd2qtc7j2N2x4758dQ91jrSCxzyuR1aJR7zgdcgdF2KwSShUhQ5n7M9uebf2HqiCWt8vttqESJ7aRNDwiW8TVmeKWviWunzYG46c8vo4DeZYK4wFfLNdwmeSn9HXKkQVpNgzsMz87cKpHRnzn"); + let account_id = account_obj.get("account_id").unwrap().as_str().unwrap(); + // Catches if a change results in changed accounts_ids, which should always be + // made to be backward compatible. + assert_eq!( + account_id, + "9111a17691a1eecb85bbeaa789c69471e7c8b9789e0068de02204f9d7264263d" + ); + assert_eq!(account_obj.get("next_subaddress_index").unwrap(), "1"); + assert_eq!(account_obj.get("fog_enabled").unwrap(), true); } #[test_with_logger] @@ -401,57 +505,6 @@ mod e2e { ); } - #[test_with_logger] - fn test_e2e_import_account_fog(logger: Logger) { - let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); - let (client, _ledger_db, _db_ctx, _network_state) = setup(&mut rng, logger.clone()); - - // Import an account with fog info. - let body = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "import_account", - "params": { - "mnemonic": "sheriff odor square mistake huge skate mouse shoot purity weapon proof stuff correct concert blanket neck own shift clay mistake air viable stick group", - "key_derivation_version": "2", - "name": "Alice Main Account", - "first_block_index": "200", - "fog_report_url": "fog://fog-report.example.com", - "fog_report_id": "", - "fog_authority_spki": "30820222300d06092a864886f70d01010105000382020f003082020a0282020100c853a8724bc211cf5370ed4dbec8947c5573bed0ec47ae14211454977b41336061f0a040f77dbf529f3a46d8095676ec971b940ab4c9642578760779840a3f9b3b893b2f65006c544e9c16586d33649769b7c1c94552d7efa081a56ad612dec932812676ebec091f2aed69123604f4888a125e04ff85f5a727c286664378581cf34c7ee13eb01cc4faf3308ed3c07a9415f98e5fbfe073e6c357967244e46ba6ebbe391d8154e6e4a1c80524b1a6733eca46e37bfdd62d75816988a79aac6bdb62a06b1237a8ff5e5c848d01bbff684248cf06d92f301623c893eb0fba0f3faee2d197ea57ac428f89d6c000f76d58d5aacc3d70204781aca45bc02b1456b454231d2f2ed4ca6614e5242c7d7af0fe61e9af6ecfa76674ffbc29b858091cbfb4011538f0e894ce45d21d7fac04ba2ff57e9ff6db21e2afd9468ad785c262ec59d4a1a801c5ec2f95fc107dc9cb5f7869d70aa84450b8c350c2fa48bddef20752a1e43676b246c7f59f8f1f4aee43c1a15f36f7a36a9ec708320ea42089991551f2656ec62ea38233946b85616ff182cf17cd227e596329b546ea04d13b053be4cf3338de777b50bc6eca7a6185cf7a5022bc9be3749b1bb43e10ecc88a0c580f2b7373138ee49c7bafd8be6a64048887230480b0c85a045255494e04a9a81646369ce7a10e08da6fae27333ec0c16c8a74d93779a9e055395078d0b07286f9930203010001" - } - }); - let res = dispatch(&client, body, &logger); - let result = res.get("result").unwrap(); - let account_obj = result.get("account").unwrap(); - let public_address = account_obj.get("main_address").unwrap().as_str().unwrap(); - assert_eq!(public_address, "mpcKQqPcgbB2oPneTAuLiZu9ZHp9qNkQo9k6949dupe89HruwmEgvcyVRFFNQccsurgMaZBykWAR1tGwbZqw4FGckqJsAcs2Fc1912Bf84S2am1kLKiRdQWfWUm6rQ8LCw75k14htjiD4u1PfYxwEvXWHXPK2R7PpzfWv5xc8129J5DykCC6wRDUZiqDcesjf7zi91frhfWvX3E6QPnc6kKZj4mfZQPjFVkHdcXWAuQoaJc"); - let account_id = account_obj.get("account_id").unwrap().as_str().unwrap(); - assert_eq!( - account_id, - "e260179ba2bed78ed47266a55106a7365f96329203cd95edfc0915f08b7947ce" - ); - - // Export account secrets and check fog info. - let body = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "export_account_secrets", - "params": { - "account_id": account_id, - } - }); - let res = dispatch(&client, body, &logger); - let result = res.get("result").unwrap(); - let secrets = result.get("account_secrets").unwrap(); - let account_key = secrets.get("account_key").unwrap(); - - assert_eq!( - *account_key.get("fog_report_url").unwrap(), - serde_json::json!("fog://fog-report.example.com") - ); - } - #[test_with_logger] fn test_e2e_get_balance(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); @@ -1867,6 +1920,49 @@ mod e2e { assert_eq!(addresses_page[..], addresses_all[1..5]); } + #[test_with_logger] + fn test_next_subaddress_fails_with_fog(logger: Logger) { + use crate::db::WalletDbError::SubaddressesNotSupportedForFOGEnabledAccounts as subaddress_error; + + let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); + let (client, mut _ledger_db, _db_ctx, _network_state) = setup(&mut rng, logger.clone()); + + // Create Account + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "create_account", + "params": { + "name": "Alice Main Account", + "fog_report_url": "fog://fog-report.example.com", + "fog_report_id": "", + "fog_authority_spki": "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvnB9wTbTOT5uoizRYaYbw7XIEkInl8E7MGOAQj+xnC+F1rIXiCnc/t1+5IIWjbRGhWzo7RAwI5sRajn2sT4rRn9NXbOzZMvIqE4hmhmEzy1YQNDnfALAWNQ+WBbYGW+Vqm3IlQvAFFjVN1YYIdYhbLjAPdkgeVsWfcLDforHn6rR3QBZYZIlSBQSKRMY/tywTxeTCvK2zWcS0kbbFPtBcVth7VFFVPAZXhPi9yy1AvnldO6n7KLiupVmojlEMtv4FQkk604nal+j/dOplTATV8a9AJBbPRBZ/yQg57EG2Y2MRiHOQifJx0S5VbNyMm9bkS8TD7Goi59aCW6OT1gyeotWwLg60JRZTfyJ7lYWBSOzh0OnaCytRpSWtNZ6barPUeOnftbnJtE8rFhF7M4F66et0LI/cuvXYecwVwykovEVBKRF4HOK9GgSm17mQMtzrD7c558TbaucOWabYR04uhdAc3s10MkuONWG0wIQhgIChYVAGnFLvSpp2/aQEq3xrRSETxsixUIjsZyWWROkuA0IFnc8d7AmcnUBvRW7FT/5thWyk5agdYUGZ+7C1o69ihR1YxmoGh69fLMPIEOhYh572+3ckgl2SaV4uo9Gvkz8MMGRBcMIMlRirSwhCfozV2RyT5Wn1NgPpyc8zJL7QdOhL7Qxb+5WjnCVrQYHI2cCAwEAAQ==" + }, + }); + + let creation_res = dispatch(&client, body, &logger); + let creation_result = creation_res.get("result").unwrap(); + let account_obj = creation_result.get("account").unwrap(); + let account_id = account_obj.get("account_id").unwrap().as_str().unwrap(); + assert_eq!(creation_res.get("jsonrpc").unwrap(), "2.0"); + + // assign next subaddress for account + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "assign_address_for_account", + "params": { + "account_id": account_id, + "metadata": "subaddress_index_2", + } + }); + let res = dispatch(&client, body, &logger); + let error = res.get("error").unwrap(); + let data = error.get("data").unwrap(); + let details = data.get("details").unwrap(); + assert!(details.to_string().contains(&subaddress_error.to_string())); + } + #[test_with_logger] fn test_import_account_with_next_subaddress_index(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); diff --git a/full-service/src/json_rpc/json_rpc_request.rs b/full-service/src/json_rpc/json_rpc_request.rs index 62628d005..43d43c366 100644 --- a/full-service/src/json_rpc/json_rpc_request.rs +++ b/full-service/src/json_rpc/json_rpc_request.rs @@ -114,6 +114,9 @@ pub enum JsonCommandRequest { }, create_account { name: Option, + fog_report_url: Option, + fog_report_id: Option, + fog_authority_spki: Option, }, create_payment_request { account_id: String, diff --git a/full-service/src/json_rpc/wallet.rs b/full-service/src/json_rpc/wallet.rs index 15c8ef7e1..d6b1392b0 100644 --- a/full-service/src/json_rpc/wallet.rs +++ b/full-service/src/json_rpc/wallet.rs @@ -336,9 +336,15 @@ where txo_id: TxoID::from(&tx.prefix.outputs[0]).to_string(), } } - JsonCommandRequest::create_account { name } => { - let account: db::models::Account = - service.create_account(name).map_err(format_error)?; + JsonCommandRequest::create_account { + name, + fog_report_url, + fog_report_id, + fog_authority_spki, + } => { + let account: db::models::Account = service + .create_account(name, fog_report_url, fog_report_id, fog_authority_spki) + .map_err(format_error)?; JsonCommandResponse::create_account { account: json_rpc::account::Account::try_from(&account).map_err(|e| { diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index d3045c771..631a06006 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -79,7 +79,13 @@ impl From for AccountServiceError { /// accounts. pub trait AccountService { /// Creates a new account with default values. - fn create_account(&self, name: Option) -> Result; + fn create_account( + &self, + name: Option, + fog_report_url: Option, + fog_report_id: Option, + fog_authority_spki: Option, + ) -> Result; /// Import an existing account to the wallet using the entropy. #[allow(clippy::too_many_arguments)] @@ -130,7 +136,13 @@ where T: BlockchainConnection + UserTxConnection + 'static, FPR: FogPubkeyResolver + Send + Sync + 'static, { - fn create_account(&self, name: Option) -> Result { + fn create_account( + &self, + name: Option, + fog_report_url: Option, + fog_report_id: Option, + fog_authority_spki: Option, + ) -> Result { log::info!(self.logger, "Creating account {:?}", name,); // Generate entropy for the account @@ -153,9 +165,9 @@ where Some(import_block_index), None, &name.unwrap_or_else(|| "".to_string()), - None, - None, - None, + fog_report_url, + fog_report_id, + fog_authority_spki, &conn, )?; @@ -315,7 +327,9 @@ mod tests { let wallet_db = &service.wallet_db; // Create an account. - let account = service.create_account(Some("A".to_string())).unwrap(); + let account = service + .create_account(Some("A".to_string()), None, None, None) + .unwrap(); // Add a transaction, with transaction status. let account_key: AccountKey = mc_util_serial::decode(&account.account_key).unwrap(); diff --git a/full-service/src/service/gift_code.rs b/full-service/src/service/gift_code.rs index 5b612aad9..925c4edcd 100644 --- a/full-service/src/service/gift_code.rs +++ b/full-service/src/service/gift_code.rs @@ -734,7 +734,7 @@ mod tests { // Create our main account for the wallet let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Add a block with a transaction for Alice @@ -837,7 +837,7 @@ mod tests { // Claim the gift code to another account log::info!(logger, "Creating new account to receive gift code"); let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); manually_sync_account( &ledger_db, @@ -899,7 +899,7 @@ mod tests { // Create our main account for the wallet let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Add a block with a transaction for Alice diff --git a/full-service/src/service/receipt.rs b/full-service/src/service/receipt.rs index 50c4cea1c..913c28481 100644 --- a/full-service/src/service/receipt.rs +++ b/full-service/src/service/receipt.rs @@ -341,7 +341,7 @@ mod tests { let service = setup_wallet_service(ledger_db.clone(), logger.clone()); let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Fund Alice @@ -362,7 +362,7 @@ mod tests { ); let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); let bob_addresses = service .get_addresses_for_account(&AccountID(bob.account_id_hex.clone()), None, None) @@ -463,7 +463,7 @@ mod tests { let service = setup_wallet_service(ledger_db.clone(), logger.clone()); let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Fund Alice @@ -484,7 +484,7 @@ mod tests { ); let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); let bob_addresses = service .get_addresses_for_account(&AccountID(bob.account_id_hex.clone()), None, None) @@ -574,7 +574,7 @@ mod tests { let service = setup_wallet_service(ledger_db.clone(), logger.clone()); let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Fund Alice @@ -595,7 +595,7 @@ mod tests { ); let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); let bob_addresses = service .get_addresses_for_account(&AccountID(bob.account_id_hex.clone()), None, None) @@ -690,7 +690,7 @@ mod tests { let service = setup_wallet_service(ledger_db.clone(), logger.clone()); let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Fund Alice @@ -711,7 +711,7 @@ mod tests { ); let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); let bob_addresses = service .get_addresses_for_account(&AccountID(bob.account_id_hex.clone()), None, None) diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index af5b82486..b3828ccbe 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -371,7 +371,7 @@ mod tests { // Create our main account for the wallet let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Add a block with a transaction for Alice @@ -409,7 +409,7 @@ mod tests { // Add an account for Bob let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); let bob_account_key: AccountKey = mc_util_serial::decode(&bob.account_key).expect("Could not decode account key"); @@ -509,7 +509,7 @@ mod tests { // Create our main account for the wallet let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Add a block with a transaction for Alice @@ -534,7 +534,7 @@ mod tests { // Add an account for Bob let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); let bob_account_key: AccountKey = mc_util_serial::decode(&bob.account_key).expect("Could not decode account key"); @@ -676,7 +676,7 @@ mod tests { // Create our main account for the wallet let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Add a block with a transaction for Alice diff --git a/full-service/src/service/txo.rs b/full-service/src/service/txo.rs index 7bbefb50f..b1f680347 100644 --- a/full-service/src/service/txo.rs +++ b/full-service/src/service/txo.rs @@ -198,7 +198,7 @@ mod tests { let service = setup_wallet_service(ledger_db.clone(), logger.clone()); let alice = service - .create_account(Some("Alice's Main Account".to_string())) + .create_account(Some("Alice's Main Account".to_string()), None, None, None) .unwrap(); // Add a block with a transaction for this recipient @@ -227,7 +227,7 @@ mod tests { // Add another account let bob = service - .create_account(Some("Bob's Main Account".to_string())) + .create_account(Some("Bob's Main Account".to_string()), None, None, None) .unwrap(); // Construct a new transaction to Bob