From a139a93e2ba564b58b4023d42ac19a883cc213e9 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Mon, 20 May 2024 22:17:34 -0700 Subject: [PATCH 01/21] Initial pass plumbing API Signed-off-by: sugargoat --- .../down.sql | 2 ++ .../up.sql | 3 ++ full-service/src/db/account.rs | 31 +++++++++++++++++++ full-service/src/db/models.rs | 3 ++ full-service/src/db/schema.rs | 4 +-- full-service/src/db/txo.rs | 22 +++++++++++++ full-service/src/json_rpc/v1/api/request.rs | 1 + full-service/src/json_rpc/v1/api/wallet.rs | 4 +++ full-service/src/json_rpc/v1/models/txo.rs | 1 + full-service/src/json_rpc/v2/api/request.rs | 3 ++ full-service/src/json_rpc/v2/api/wallet.rs | 17 ++++++++-- full-service/src/json_rpc/v2/models/txo.rs | 1 + full-service/src/service/account.rs | 23 +++++++++++--- full-service/src/service/address.rs | 2 +- full-service/src/service/balance.rs | 1 + full-service/src/service/gift_code.rs | 3 ++ .../src/service/models/tx_proposal.rs | 2 ++ full-service/src/service/receipt.rs | 8 +++++ full-service/src/service/sync.rs | 1 + full-service/src/service/transaction.rs | 11 +++++++ full-service/src/service/transaction_log.rs | 1 + full-service/src/service/txo.rs | 2 ++ full-service/src/test_utils.rs | 1 + 23 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql create mode 100644 full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql diff --git a/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql b/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql new file mode 100644 index 000000000..e739fde5b --- /dev/null +++ b/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE accounts DROP COLUMN spend_only_from_subaddress; \ No newline at end of file diff --git a/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql b/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql new file mode 100644 index 000000000..edf6e0851 --- /dev/null +++ b/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE accounts + ADD COLUMN spend_only_from_subaddress BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index 54db84161..e1b75a1ff 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -82,6 +82,7 @@ pub trait AccountModel { ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | + ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | /// /// # Returns: /// * (account_id, main_subaddress_b58) @@ -94,6 +95,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -110,6 +112,7 @@ pub trait AccountModel { ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | + ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -123,6 +126,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -140,6 +144,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_enabled` | Indicate if fog server is enabled or disabled | | + ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -154,6 +159,7 @@ pub trait AccountModel { next_subaddress_index: Option, name: &str, fog_enabled: bool, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -170,6 +176,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | + ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -183,6 +190,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result; @@ -199,6 +207,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | + ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -212,6 +221,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result; @@ -453,6 +463,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -474,6 +485,7 @@ impl AccountModel for Account { next_subaddress_index, name, fog_enabled, + spend_only_from_subaddress_mode, conn, ) } @@ -486,6 +498,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -507,6 +520,7 @@ impl AccountModel for Account { next_subaddress_index, name, fog_enabled, + spend_only_from_subaddress_mode, conn, ) } @@ -520,6 +534,7 @@ impl AccountModel for Account { next_subaddress_index: Option, name: &str, fog_enabled: bool, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { use crate::db::schema::accounts; @@ -548,6 +563,7 @@ impl AccountModel for Account { fog_enabled, view_only: false, managed_by_hardware_wallet: false, + spend_only_from_subaddress: spend_only_from_subaddress_mode.unwrap_or(false), }; diesel::insert_into(accounts::table) @@ -583,6 +599,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_mnemonic( @@ -593,6 +610,7 @@ impl AccountModel for Account { &name.unwrap_or_default(), fog_report_url, fog_authority_spki, + spend_only_from_subaddress_mode, conn, )?; Account::get(&account_id, conn) @@ -606,6 +624,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_root_entropy( @@ -616,6 +635,7 @@ impl AccountModel for Account { &name.unwrap_or_default(), fog_report_url, fog_authority_spki, + spend_only_from_subaddress_mode, conn, )?; Account::get(&account_id, conn) @@ -656,6 +676,7 @@ impl AccountModel for Account { fog_enabled: false, view_only: true, managed_by_hardware_wallet, + spend_only_from_subaddress: false, }; diesel::insert_into(accounts::table) @@ -726,6 +747,7 @@ impl AccountModel for Account { fog_enabled: true, view_only: true, managed_by_hardware_wallet: true, + spend_only_from_subaddress: false, }; diesel::insert_into(accounts::table) @@ -964,6 +986,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -995,6 +1018,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, + spend_only_from_subaddress: false, }; assert_eq!(expected_account, acc); @@ -1037,6 +1061,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, wallet_db.get_pooled_conn().unwrap().deref_mut(), ) .unwrap(); @@ -1062,6 +1087,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, + spend_only_from_subaddress: false, }; assert_eq!(expected_account_secondary, acc_secondary); @@ -1125,6 +1151,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -1160,6 +1187,7 @@ mod tests { "Alice's FOG Account", "fog//some.fog.url".to_string(), "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvnB9wTbTOT5uoizRYaYbw7XIEkInl8E7MGOAQj+xnC+F1rIXiCnc/t1+5IIWjbRGhWzo7RAwI5sRajn2sT4rRn9NXbOzZMvIqE4hmhmEzy1YQNDnfALAWNQ+WBbYGW+Vqm3IlQvAFFjVN1YYIdYhbLjAPdkgeVsWfcLDforHn6rR3QBZYZIlSBQSKRMY/tywTxeTCvK2zWcS0kbbFPtBcVth7VFFVPAZXhPi9yy1AvnldO6n7KLiupVmojlEMtv4FQkk604nal+j/dOplTATV8a9AJBbPRBZ/yQg57EG2Y2MRiHOQifJx0S5VbNyMm9bkS8TD7Goi59aCW6OT1gyeotWwLg60JRZTfyJ7lYWBSOzh0OnaCytRpSWtNZ6barPUeOnftbnJtE8rFhF7M4F66et0LI/cuvXYecwVwykovEVBKRF4HOK9GgSm17mQMtzrD7c558TbaucOWabYR04uhdAc3s10MkuONWG0wIQhgIChYVAGnFLvSpp2/aQEq3xrRSETxsixUIjsZyWWROkuA0IFnc8d7AmcnUBvRW7FT/5thWyk5agdYUGZ+7C1o69ihR1YxmoGh69fLMPIEOhYh572+3ckgl2SaV4uo9Gvkz8MMGRBcMIMlRirSwhCfozV2RyT5Wn1NgPpyc8zJL7QdOhL7Qxb+5WjnCVrQYHI2cCAwEAAQ==".to_string(), + None, conn, ) .unwrap(); @@ -1229,6 +1257,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, + spend_only_from_subaddress: false, }; assert_eq!(expected_account, acc); } @@ -1287,6 +1316,7 @@ mod tests { view_only: true, managed_by_hardware_wallet: false, resyncing: false, + spend_only_from_subaddress: false, }; assert_eq!(expected_account, account); } @@ -1347,6 +1377,7 @@ mod tests { view_only: true, managed_by_hardware_wallet: true, resyncing: false, + spend_only_from_subaddress: false, }; // Check to make sure the account in the database is correct diff --git a/full-service/src/db/models.rs b/full-service/src/db/models.rs index f790fd0d8..3e23cb0f3 100644 --- a/full-service/src/db/models.rs +++ b/full-service/src/db/models.rs @@ -40,6 +40,8 @@ pub struct Account { /// account. pub managed_by_hardware_wallet: bool, pub resyncing: bool, + /// If true, this account is only allowed to spend from subaddresses. + pub spend_only_from_subaddress: bool, } /// A structure that can be inserted to create a new entity in the `accounts` @@ -58,6 +60,7 @@ pub struct NewAccount<'a> { pub fog_enabled: bool, pub view_only: bool, pub managed_by_hardware_wallet: bool, + pub spend_only_from_subaddress: 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 29a7243ca..acc1f8aca 100644 --- a/full-service/src/db/schema.rs +++ b/full-service/src/db/schema.rs @@ -1,5 +1,3 @@ -// @generated automatically by Diesel CLI. - diesel::table! { accounts (id) { id -> Text, @@ -14,6 +12,7 @@ diesel::table! { view_only -> Bool, managed_by_hardware_wallet -> Bool, resyncing -> Bool, + spend_only_from_subaddress -> Bool, } } @@ -116,6 +115,7 @@ diesel::table! { diesel::joinable!(assigned_subaddresses -> accounts (account_id)); diesel::joinable!(authenticated_sender_memos -> txos (txo_id)); +diesel::joinable!(destination_memos -> txos (txo_id)); diesel::joinable!(transaction_input_txos -> transaction_logs (transaction_log_id)); diesel::joinable!(transaction_input_txos -> txos (txo_id)); diesel::joinable!(transaction_logs -> accounts (account_id)); diff --git a/full-service/src/db/txo.rs b/full-service/src/db/txo.rs index 08a95a378..71d6fc19d 100644 --- a/full-service/src/db/txo.rs +++ b/full-service/src/db/txo.rs @@ -2481,6 +2481,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -2825,6 +2826,7 @@ mod tests { "Bob's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -2930,6 +2932,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -3012,6 +3015,7 @@ mod tests { "Exchange Account", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -3120,6 +3124,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3248,6 +3253,7 @@ mod tests { "Exchange Account", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -3357,6 +3363,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3424,6 +3431,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3482,6 +3490,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3557,6 +3566,7 @@ mod tests { "Alice", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3719,6 +3729,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3770,6 +3781,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3861,6 +3873,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -3917,6 +3930,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -3970,6 +3984,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -4059,6 +4074,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4159,6 +4175,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4346,6 +4363,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4498,6 +4516,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4601,6 +4620,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -4688,6 +4708,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); @@ -4816,6 +4837,7 @@ mod tests { "", "".to_string(), "".to_string(), + None, conn, ) .unwrap(); diff --git a/full-service/src/json_rpc/v1/api/request.rs b/full-service/src/json_rpc/v1/api/request.rs index dce75b2e9..de7bdac0a 100644 --- a/full-service/src/json_rpc/v1/api/request.rs +++ b/full-service/src/json_rpc/v1/api/request.rs @@ -102,6 +102,7 @@ pub enum JsonCommandRequest { fog_report_url: Option, fog_report_id: Option, // Deprecated fog_authority_spki: Option, + spend_only_from_subaddress_mode: Option, }, create_payment_request { account_id: String, diff --git a/full-service/src/json_rpc/v1/api/wallet.rs b/full-service/src/json_rpc/v1/api/wallet.rs index 6472a3275..aa9d7a3a1 100644 --- a/full-service/src/json_rpc/v1/api/wallet.rs +++ b/full-service/src/json_rpc/v1/api/wallet.rs @@ -377,12 +377,14 @@ where fog_report_url, fog_report_id: _, // Deprecated fog_authority_spki, + spend_only_from_subaddress_mode, } => { let account = service .create_account( name, fog_report_url.unwrap_or_default(), fog_authority_spki.unwrap_or_default(), + spend_only_from_subaddress_mode, ) .map_err(format_error)?; let next_subaddress_index = service @@ -1005,6 +1007,7 @@ where ns, fog_report_url.unwrap_or_default(), fog_authority_spki.unwrap_or_default(), + None, ) .map_err(format_error)?; @@ -1045,6 +1048,7 @@ where ns, fog_report_url.unwrap_or_default(), fog_authority_spki.unwrap_or_default(), + None, ) .map_err(format_error)?; diff --git a/full-service/src/json_rpc/v1/models/txo.rs b/full-service/src/json_rpc/v1/models/txo.rs index e4addc8b0..e39db7d3e 100644 --- a/full-service/src/json_rpc/v1/models/txo.rs +++ b/full-service/src/json_rpc/v1/models/txo.rs @@ -237,6 +237,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); diff --git a/full-service/src/json_rpc/v2/api/request.rs b/full-service/src/json_rpc/v2/api/request.rs index 885a1492d..dbd6cf60e 100644 --- a/full-service/src/json_rpc/v2/api/request.rs +++ b/full-service/src/json_rpc/v2/api/request.rs @@ -121,6 +121,7 @@ pub enum JsonCommandRequest { create_account { name: Option, fog_info: Option, + spend_only_from_subaddress_mode: Option, }, create_payment_request { account_id: String, @@ -226,6 +227,7 @@ pub enum JsonCommandRequest { first_block_index: Option, next_subaddress_index: Option, fog_info: Option, + spend_only_from_subaddress_mode: Option, }, import_account { mnemonic: String, @@ -233,6 +235,7 @@ pub enum JsonCommandRequest { first_block_index: Option, next_subaddress_index: Option, fog_info: Option, + spend_only_from_subaddress_mode: Option, }, import_view_only_account { view_private_key: String, diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index d348194d7..4eb699cf8 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -504,11 +504,20 @@ where txo: txo_status_and_memo.map(|txo_info| (&txo_info).into()), } } - JsonCommandRequest::create_account { name, fog_info } => { + JsonCommandRequest::create_account { + name, + fog_info, + spend_only_from_subaddress_mode, + } => { let fog_info = fog_info.unwrap_or_default(); let account = service - .create_account(name, fog_info.report_url, fog_info.authority_spki) + .create_account( + name, + fog_info.report_url, + fog_info.authority_spki, + spend_only_from_subaddress_mode, + ) .map_err(format_error)?; let next_subaddress_index = service @@ -1109,6 +1118,7 @@ where first_block_index, next_subaddress_index, fog_info, + spend_only_from_subaddress_mode, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1129,6 +1139,7 @@ where ns, fog_info.report_url, fog_info.authority_spki, + spend_only_from_subaddress_mode, ) .map_err(format_error)?; @@ -1156,6 +1167,7 @@ where first_block_index, next_subaddress_index, fog_info, + spend_only_from_subaddress_mode, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1176,6 +1188,7 @@ where ns, fog_info.report_url, fog_info.authority_spki, + spend_only_from_subaddress_mode, ) .map_err(format_error)?; diff --git a/full-service/src/json_rpc/v2/models/txo.rs b/full-service/src/json_rpc/v2/models/txo.rs index 353e32905..34d568224 100644 --- a/full-service/src/json_rpc/v2/models/txo.rs +++ b/full-service/src/json_rpc/v2/models/txo.rs @@ -133,6 +133,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), + None, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index 8f6c0ec4c..aa7e31ccc 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -162,12 +162,14 @@ pub trait AccountService { ///| `name` | A label for this account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | + ///| `spend_only_from_subaddress_mode` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// fn create_account( &self, name: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -182,6 +184,7 @@ pub trait AccountService { ///| `next_subaddress_index` | The next known unused subaddress index for the account. | | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | + ///| `spend_only_from_subaddress_mode` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// #[allow(clippy::too_many_arguments)] fn import_account( @@ -192,6 +195,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, ) -> Result; /// Import an existing account to the wallet using the entropy. @@ -206,6 +210,7 @@ pub trait AccountService { ///| `next_subaddress_index` | The next known unused subaddress index for the account. | | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | + ///| `spend_only_from_subaddress_mode` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// #[allow(clippy::too_many_arguments)] fn import_account_from_legacy_root_entropy( @@ -216,6 +221,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -371,6 +377,7 @@ where name: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, ) -> Result { log::info!(self.logger, "Creating account {:?}", name,); @@ -408,6 +415,7 @@ where &name.unwrap_or_default(), fog_report_url, fog_authority_spki, + spend_only_from_subaddress_mode, conn, )?; let account = Account::get(&account_id, conn)?; @@ -423,6 +431,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, ) -> Result { log::info!( self.logger, @@ -456,6 +465,7 @@ where next_subaddress_index, fog_report_url, fog_authority_spki, + spend_only_from_subaddress_mode, conn, )?) }) @@ -469,6 +479,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, + spend_only_from_subaddress_mode: Option, ) -> Result { log::info!( self.logger, @@ -495,6 +506,7 @@ where next_subaddress_index, fog_report_url, fog_authority_spki, + spend_only_from_subaddress_mode, conn, )?) }) @@ -847,6 +859,7 @@ mod tests { None, "".to_string(), "".to_string(), + None, ) .unwrap(); let account_id = AccountID(account.id); @@ -874,7 +887,7 @@ mod tests { // create an account that has its first_block_index set to later in the ledger let account2 = service - .create_account(None, "".to_string(), "".to_string()) + .create_account(None, "".to_string(), "".to_string(), None) .unwrap(); assert_eq!( account2.first_block_index as u64, @@ -945,6 +958,7 @@ mod tests { None, "".to_string(), "".to_string(), + None, ) .unwrap(); let account_a_id = AccountID(account_a.id.clone()); @@ -957,6 +971,7 @@ mod tests { None, "".to_string(), "".to_string(), + None, ) .unwrap(); let account_b_id = AccountID(account_b.id.clone()); @@ -1095,7 +1110,7 @@ mod tests { // Create an account. let account = service - .create_account(Some("A".to_string()), "".to_string(), "".to_string()) + .create_account(Some("A".to_string()), "".to_string(), "".to_string(), None) .unwrap(); // Add a transaction, with transaction status. @@ -1151,7 +1166,7 @@ mod tests { // Create an account. let account = service - .create_account(Some("A".to_string()), "".to_string(), "".to_string()) + .create_account(Some("A".to_string()), "".to_string(), "".to_string(), None) .unwrap(); // Even though we don't have a network connection, it sets the block indices @@ -1183,7 +1198,7 @@ mod tests { // Create an account. let account = service - .create_account(Some("A".to_string()), "".to_string(), "".to_string()) + .create_account(Some("A".to_string()), "".to_string(), "".to_string(), None) .unwrap(); // The block indices are set to zero because we have no ledger information diff --git a/full-service/src/service/address.rs b/full-service/src/service/address.rs index 525b59b4e..ba0afe5a9 100644 --- a/full-service/src/service/address.rs +++ b/full-service/src/service/address.rs @@ -220,7 +220,7 @@ mod tests { // Create an account. let account = service - .create_account(None, "".to_string(), "".to_string()) + .create_account(None, "".to_string(), "".to_string(), None) .unwrap(); assert_eq!(account.clone().next_subaddress_index(conn).unwrap(), 2); diff --git a/full-service/src/service/balance.rs b/full-service/src/service/balance.rs index ec51a4960..cc5442b2e 100644 --- a/full-service/src/service/balance.rs +++ b/full-service/src/service/balance.rs @@ -493,6 +493,7 @@ mod tests { None, "".to_string(), "".to_string(), + None, ) .expect("Could not import account entropy"); diff --git a/full-service/src/service/gift_code.rs b/full-service/src/service/gift_code.rs index 5bbd2bd65..e1bf832be 100644 --- a/full-service/src/service/gift_code.rs +++ b/full-service/src/service/gift_code.rs @@ -890,6 +890,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -1006,6 +1007,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); manually_sync_account( @@ -1071,6 +1073,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); diff --git a/full-service/src/service/models/tx_proposal.rs b/full-service/src/service/models/tx_proposal.rs index 16318870a..299c3f710 100644 --- a/full-service/src/service/models/tx_proposal.rs +++ b/full-service/src/service/models/tx_proposal.rs @@ -489,6 +489,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -517,6 +518,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); diff --git a/full-service/src/service/receipt.rs b/full-service/src/service/receipt.rs index da66ad421..311fc72ea 100644 --- a/full-service/src/service/receipt.rs +++ b/full-service/src/service/receipt.rs @@ -387,6 +387,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -412,6 +413,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_addresses = service @@ -520,6 +522,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -545,6 +548,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_addresses = service @@ -645,6 +649,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -670,6 +675,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_addresses = service @@ -791,6 +797,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -816,6 +823,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_addresses = service diff --git a/full-service/src/service/sync.rs b/full-service/src/service/sync.rs index 0cad193e9..1e0c6e519 100644 --- a/full-service/src/service/sync.rs +++ b/full-service/src/service/sync.rs @@ -501,6 +501,7 @@ mod tests { None, "".to_string(), "".to_string(), + None, ) .expect("Could not import account entropy"); diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index 9184e302a..d26b75437 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -754,6 +754,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -802,6 +803,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_account_key: AccountKey = @@ -924,6 +926,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -959,6 +962,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_account_key: AccountKey = @@ -1166,6 +1170,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -1228,6 +1233,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -1339,6 +1345,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -1374,6 +1381,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_account_key: AccountKey = @@ -1527,6 +1535,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -1562,6 +1571,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let bob_account_key: AccountKey = @@ -1747,6 +1757,7 @@ mod tests { Some("Exchange's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); let exchange_account_key: AccountKey = diff --git a/full-service/src/service/transaction_log.rs b/full-service/src/service/transaction_log.rs index d66531f74..38029b1ce 100644 --- a/full-service/src/service/transaction_log.rs +++ b/full-service/src/service/transaction_log.rs @@ -157,6 +157,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); diff --git a/full-service/src/service/txo.rs b/full-service/src/service/txo.rs index cb26784cf..20a77f6c8 100644 --- a/full-service/src/service/txo.rs +++ b/full-service/src/service/txo.rs @@ -381,6 +381,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); @@ -431,6 +432,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), + None, ) .unwrap(); diff --git a/full-service/src/test_utils.rs b/full-service/src/test_utils.rs index 50eaab464..507170475 100644 --- a/full-service/src/test_utils.rs +++ b/full-service/src/test_utils.rs @@ -565,6 +565,7 @@ pub fn random_account_with_seed_values( &format!("SeedAccount{}", rng.next_u32()), "".to_string(), "".to_string(), + None, wallet_db.get_pooled_conn().unwrap().deref_mut(), ) .unwrap(); From 24978ee1a91b57b9046ce59baf0e29eaac00f9ab Mon Sep 17 00:00:00 2001 From: sugargoat Date: Tue, 21 May 2024 14:19:32 -0700 Subject: [PATCH 02/21] Logic is in place Signed-off-by: sugargoat --- ...nd_submit_with_subaddress_to_spend_from.rs | 1 + full-service/src/service/transaction.rs | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_subaddress_to_spend_from.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_subaddress_to_spend_from.rs index 1d34d18ad..2d0d2811c 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_subaddress_to_spend_from.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_subaddress_to_spend_from.rs @@ -42,6 +42,7 @@ mod e2e_transaction { "method": "create_account", "params": { "name": "Exchange Main Account", + "spend_only_from_subaddress_mode": true, } }); let res = dispatch(&client, body, &logger); diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index d26b75437..1f699d7ce 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -468,6 +468,22 @@ where let conn = pooled_conn.deref_mut(); exclusive_transaction(conn, |conn| { + if Account::get(&AccountID(account_id_hex.to_string()), conn)? + .spend_only_from_subaddress + { + if subaddress_to_spend_from.is_none() { + return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( + "This account is configured to spend only from a specific subaddress. Please provide a subaddress to spend from.".to_string() + ))); + } + } else { + if subaddress_to_spend_from.is_some() { + return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( + "This account is not configured to spend only from a specific subaddress. Please do not provide a subaddress to spend from.".to_string() + ))); + } + } + let mut builder = WalletTransactionBuilder::new( account_id_hex.to_string(), self.ledger_db.clone(), @@ -1723,7 +1739,7 @@ mod tests { // transaction change arrives back to that subaddress. // This is a long, complicated test, so I'll list out the steps here for // readability: - // 1. Create exchange account + // 1. Create exchange account with subaddress_to_spend_from_mode // 2. Create subaddresses for Alice and Bob // 3. Add a block with a transaction for 100 MOB from some external source for // Alice. Balances [Alice: 100, Bob: 0] @@ -1757,7 +1773,7 @@ mod tests { Some("Exchange's Main Account".to_string()), "".to_string(), "".to_string(), - None, + Some(true), ) .unwrap(); let exchange_account_key: AccountKey = From 46ffc5affddaaad15d8598bd7d0471d6a9e0b718 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Tue, 21 May 2024 15:29:45 -0700 Subject: [PATCH 03/21] Update to default bool Signed-off-by: sugargoat --- full-service/src/db/account.rs | 30 ++++++------- full-service/src/db/txo.rs | 44 +++++++++---------- full-service/src/json_rpc/v1/api/request.rs | 1 - full-service/src/json_rpc/v1/api/wallet.rs | 7 ++- full-service/src/json_rpc/v1/models/txo.rs | 2 +- full-service/src/json_rpc/v2/api/request.rs | 9 ++-- full-service/src/json_rpc/v2/models/txo.rs | 2 +- full-service/src/service/account.rs | 26 +++++------ full-service/src/service/address.rs | 2 +- full-service/src/service/balance.rs | 2 +- full-service/src/service/gift_code.rs | 6 +-- .../src/service/models/tx_proposal.rs | 4 +- full-service/src/service/receipt.rs | 16 +++---- full-service/src/service/sync.rs | 2 +- full-service/src/service/transaction.rs | 22 +++++----- full-service/src/service/transaction_log.rs | 2 +- full-service/src/service/txo.rs | 4 +- full-service/src/test_utils.rs | 2 +- 18 files changed, 92 insertions(+), 91 deletions(-) diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index e1b75a1ff..70c7b8574 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -95,7 +95,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -126,7 +126,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -159,7 +159,7 @@ pub trait AccountModel { next_subaddress_index: Option, name: &str, fog_enabled: bool, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -190,7 +190,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result; @@ -221,7 +221,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result; @@ -463,7 +463,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -498,7 +498,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -534,7 +534,7 @@ impl AccountModel for Account { next_subaddress_index: Option, name: &str, fog_enabled: bool, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { use crate::db::schema::accounts; @@ -563,7 +563,7 @@ impl AccountModel for Account { fog_enabled, view_only: false, managed_by_hardware_wallet: false, - spend_only_from_subaddress: spend_only_from_subaddress_mode.unwrap_or(false), + spend_only_from_subaddress: spend_only_from_subaddress_mode, }; diesel::insert_into(accounts::table) @@ -599,7 +599,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_mnemonic( @@ -624,7 +624,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_root_entropy( @@ -986,7 +986,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -1061,7 +1061,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, wallet_db.get_pooled_conn().unwrap().deref_mut(), ) .unwrap(); @@ -1151,7 +1151,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -1187,7 +1187,7 @@ mod tests { "Alice's FOG Account", "fog//some.fog.url".to_string(), "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvnB9wTbTOT5uoizRYaYbw7XIEkInl8E7MGOAQj+xnC+F1rIXiCnc/t1+5IIWjbRGhWzo7RAwI5sRajn2sT4rRn9NXbOzZMvIqE4hmhmEzy1YQNDnfALAWNQ+WBbYGW+Vqm3IlQvAFFjVN1YYIdYhbLjAPdkgeVsWfcLDforHn6rR3QBZYZIlSBQSKRMY/tywTxeTCvK2zWcS0kbbFPtBcVth7VFFVPAZXhPi9yy1AvnldO6n7KLiupVmojlEMtv4FQkk604nal+j/dOplTATV8a9AJBbPRBZ/yQg57EG2Y2MRiHOQifJx0S5VbNyMm9bkS8TD7Goi59aCW6OT1gyeotWwLg60JRZTfyJ7lYWBSOzh0OnaCytRpSWtNZ6barPUeOnftbnJtE8rFhF7M4F66et0LI/cuvXYecwVwykovEVBKRF4HOK9GgSm17mQMtzrD7c558TbaucOWabYR04uhdAc3s10MkuONWG0wIQhgIChYVAGnFLvSpp2/aQEq3xrRSETxsixUIjsZyWWROkuA0IFnc8d7AmcnUBvRW7FT/5thWyk5agdYUGZ+7C1o69ihR1YxmoGh69fLMPIEOhYh572+3ckgl2SaV4uo9Gvkz8MMGRBcMIMlRirSwhCfozV2RyT5Wn1NgPpyc8zJL7QdOhL7Qxb+5WjnCVrQYHI2cCAwEAAQ==".to_string(), - None, + false, conn, ) .unwrap(); diff --git a/full-service/src/db/txo.rs b/full-service/src/db/txo.rs index 71d6fc19d..70294ad74 100644 --- a/full-service/src/db/txo.rs +++ b/full-service/src/db/txo.rs @@ -2481,7 +2481,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -2826,7 +2826,7 @@ mod tests { "Bob's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -2932,7 +2932,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -3015,7 +3015,7 @@ mod tests { "Exchange Account", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -3124,7 +3124,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3253,7 +3253,7 @@ mod tests { "Exchange Account", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -3363,7 +3363,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3431,7 +3431,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3490,7 +3490,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3566,7 +3566,7 @@ mod tests { "Alice", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3729,7 +3729,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3781,7 +3781,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -3873,7 +3873,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -3930,7 +3930,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -3984,7 +3984,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -4074,7 +4074,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4175,7 +4175,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4363,7 +4363,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4516,7 +4516,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); @@ -4620,7 +4620,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -4708,7 +4708,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); @@ -4837,7 +4837,7 @@ mod tests { "", "".to_string(), "".to_string(), - None, + false, conn, ) .unwrap(); diff --git a/full-service/src/json_rpc/v1/api/request.rs b/full-service/src/json_rpc/v1/api/request.rs index de7bdac0a..dce75b2e9 100644 --- a/full-service/src/json_rpc/v1/api/request.rs +++ b/full-service/src/json_rpc/v1/api/request.rs @@ -102,7 +102,6 @@ pub enum JsonCommandRequest { fog_report_url: Option, fog_report_id: Option, // Deprecated fog_authority_spki: Option, - spend_only_from_subaddress_mode: Option, }, create_payment_request { account_id: String, diff --git a/full-service/src/json_rpc/v1/api/wallet.rs b/full-service/src/json_rpc/v1/api/wallet.rs index aa9d7a3a1..f0c3e64b1 100644 --- a/full-service/src/json_rpc/v1/api/wallet.rs +++ b/full-service/src/json_rpc/v1/api/wallet.rs @@ -377,14 +377,13 @@ where fog_report_url, fog_report_id: _, // Deprecated fog_authority_spki, - spend_only_from_subaddress_mode, } => { let account = service .create_account( name, fog_report_url.unwrap_or_default(), fog_authority_spki.unwrap_or_default(), - spend_only_from_subaddress_mode, + false, // not exposed in V1 API ) .map_err(format_error)?; let next_subaddress_index = service @@ -1007,7 +1006,7 @@ where ns, fog_report_url.unwrap_or_default(), fog_authority_spki.unwrap_or_default(), - None, + false, ) .map_err(format_error)?; @@ -1048,7 +1047,7 @@ where ns, fog_report_url.unwrap_or_default(), fog_authority_spki.unwrap_or_default(), - None, + false, ) .map_err(format_error)?; diff --git a/full-service/src/json_rpc/v1/models/txo.rs b/full-service/src/json_rpc/v1/models/txo.rs index e39db7d3e..762248ddd 100644 --- a/full-service/src/json_rpc/v1/models/txo.rs +++ b/full-service/src/json_rpc/v1/models/txo.rs @@ -237,7 +237,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); diff --git a/full-service/src/json_rpc/v2/api/request.rs b/full-service/src/json_rpc/v2/api/request.rs index dbd6cf60e..620cada93 100644 --- a/full-service/src/json_rpc/v2/api/request.rs +++ b/full-service/src/json_rpc/v2/api/request.rs @@ -121,7 +121,8 @@ pub enum JsonCommandRequest { create_account { name: Option, fog_info: Option, - spend_only_from_subaddress_mode: Option, + #[serde(default = "bool::default")] // default is false + spend_only_from_subaddress_mode: bool, }, create_payment_request { account_id: String, @@ -227,7 +228,8 @@ pub enum JsonCommandRequest { first_block_index: Option, next_subaddress_index: Option, fog_info: Option, - spend_only_from_subaddress_mode: Option, + #[serde(default = "bool::default")] // default is false + spend_only_from_subaddress_mode: bool, }, import_account { mnemonic: String, @@ -235,7 +237,8 @@ pub enum JsonCommandRequest { first_block_index: Option, next_subaddress_index: Option, fog_info: Option, - spend_only_from_subaddress_mode: Option, + #[serde(default = "bool::default")] // default is false + spend_only_from_subaddress_mode: bool, }, import_view_only_account { view_private_key: String, diff --git a/full-service/src/json_rpc/v2/models/txo.rs b/full-service/src/json_rpc/v2/models/txo.rs index 34d568224..af954c095 100644 --- a/full-service/src/json_rpc/v2/models/txo.rs +++ b/full-service/src/json_rpc/v2/models/txo.rs @@ -133,7 +133,7 @@ mod tests { "Alice's Main Account", "".to_string(), "".to_string(), - None, + false, &mut wallet_db.get_pooled_conn().unwrap(), ) .unwrap(); diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index aa7e31ccc..1c39a2d05 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -169,7 +169,7 @@ pub trait AccountService { name: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -195,7 +195,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, ) -> Result; /// Import an existing account to the wallet using the entropy. @@ -221,7 +221,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -377,7 +377,7 @@ where name: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, ) -> Result { log::info!(self.logger, "Creating account {:?}", name,); @@ -431,7 +431,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, ) -> Result { log::info!( self.logger, @@ -479,7 +479,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: Option, + spend_only_from_subaddress_mode: bool, ) -> Result { log::info!( self.logger, @@ -859,7 +859,7 @@ mod tests { None, "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let account_id = AccountID(account.id); @@ -887,7 +887,7 @@ mod tests { // create an account that has its first_block_index set to later in the ledger let account2 = service - .create_account(None, "".to_string(), "".to_string(), None) + .create_account(None, "".to_string(), "".to_string(), false) .unwrap(); assert_eq!( account2.first_block_index as u64, @@ -958,7 +958,7 @@ mod tests { None, "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let account_a_id = AccountID(account_a.id.clone()); @@ -971,7 +971,7 @@ mod tests { None, "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let account_b_id = AccountID(account_b.id.clone()); @@ -1110,7 +1110,7 @@ mod tests { // Create an account. let account = service - .create_account(Some("A".to_string()), "".to_string(), "".to_string(), None) + .create_account(Some("A".to_string()), "".to_string(), "".to_string(), false) .unwrap(); // Add a transaction, with transaction status. @@ -1166,7 +1166,7 @@ mod tests { // Create an account. let account = service - .create_account(Some("A".to_string()), "".to_string(), "".to_string(), None) + .create_account(Some("A".to_string()), "".to_string(), "".to_string(), false) .unwrap(); // Even though we don't have a network connection, it sets the block indices @@ -1198,7 +1198,7 @@ mod tests { // Create an account. let account = service - .create_account(Some("A".to_string()), "".to_string(), "".to_string(), None) + .create_account(Some("A".to_string()), "".to_string(), "".to_string(), false) .unwrap(); // The block indices are set to zero because we have no ledger information diff --git a/full-service/src/service/address.rs b/full-service/src/service/address.rs index ba0afe5a9..6fd0b08a5 100644 --- a/full-service/src/service/address.rs +++ b/full-service/src/service/address.rs @@ -220,7 +220,7 @@ mod tests { // Create an account. let account = service - .create_account(None, "".to_string(), "".to_string(), None) + .create_account(None, "".to_string(), "".to_string(), false) .unwrap(); assert_eq!(account.clone().next_subaddress_index(conn).unwrap(), 2); diff --git a/full-service/src/service/balance.rs b/full-service/src/service/balance.rs index cc5442b2e..ac8c860fc 100644 --- a/full-service/src/service/balance.rs +++ b/full-service/src/service/balance.rs @@ -493,7 +493,7 @@ mod tests { None, "".to_string(), "".to_string(), - None, + false, ) .expect("Could not import account entropy"); diff --git a/full-service/src/service/gift_code.rs b/full-service/src/service/gift_code.rs index e1bf832be..2eea41a06 100644 --- a/full-service/src/service/gift_code.rs +++ b/full-service/src/service/gift_code.rs @@ -890,7 +890,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -1007,7 +1007,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); manually_sync_account( @@ -1073,7 +1073,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); diff --git a/full-service/src/service/models/tx_proposal.rs b/full-service/src/service/models/tx_proposal.rs index 299c3f710..d2c0fdea1 100644 --- a/full-service/src/service/models/tx_proposal.rs +++ b/full-service/src/service/models/tx_proposal.rs @@ -489,7 +489,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -518,7 +518,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); diff --git a/full-service/src/service/receipt.rs b/full-service/src/service/receipt.rs index 311fc72ea..90524ab8f 100644 --- a/full-service/src/service/receipt.rs +++ b/full-service/src/service/receipt.rs @@ -387,7 +387,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -413,7 +413,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_addresses = service @@ -522,7 +522,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -548,7 +548,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_addresses = service @@ -649,7 +649,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -675,7 +675,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_addresses = service @@ -797,7 +797,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -823,7 +823,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_addresses = service diff --git a/full-service/src/service/sync.rs b/full-service/src/service/sync.rs index 1e0c6e519..5c0c24289 100644 --- a/full-service/src/service/sync.rs +++ b/full-service/src/service/sync.rs @@ -501,7 +501,7 @@ mod tests { None, "".to_string(), "".to_string(), - None, + false, ) .expect("Could not import account entropy"); diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index 1f699d7ce..f02a3516c 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -770,7 +770,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -819,7 +819,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_account_key: AccountKey = @@ -942,7 +942,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -978,7 +978,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_account_key: AccountKey = @@ -1186,7 +1186,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -1249,7 +1249,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -1361,7 +1361,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -1397,7 +1397,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_account_key: AccountKey = @@ -1551,7 +1551,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -1587,7 +1587,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); let bob_account_key: AccountKey = @@ -1773,7 +1773,7 @@ mod tests { Some("Exchange's Main Account".to_string()), "".to_string(), "".to_string(), - Some(true), + true, ) .unwrap(); let exchange_account_key: AccountKey = diff --git a/full-service/src/service/transaction_log.rs b/full-service/src/service/transaction_log.rs index 38029b1ce..5f5c54a1f 100644 --- a/full-service/src/service/transaction_log.rs +++ b/full-service/src/service/transaction_log.rs @@ -157,7 +157,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); diff --git a/full-service/src/service/txo.rs b/full-service/src/service/txo.rs index 20a77f6c8..abe2364f4 100644 --- a/full-service/src/service/txo.rs +++ b/full-service/src/service/txo.rs @@ -381,7 +381,7 @@ mod tests { Some("Alice's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); @@ -432,7 +432,7 @@ mod tests { Some("Bob's Main Account".to_string()), "".to_string(), "".to_string(), - None, + false, ) .unwrap(); diff --git a/full-service/src/test_utils.rs b/full-service/src/test_utils.rs index 507170475..9b15f7579 100644 --- a/full-service/src/test_utils.rs +++ b/full-service/src/test_utils.rs @@ -565,7 +565,7 @@ pub fn random_account_with_seed_values( &format!("SeedAccount{}", rng.next_u32()), "".to_string(), "".to_string(), - None, + false, wallet_db.get_pooled_conn().unwrap().deref_mut(), ) .unwrap(); From 761e76495a5cc1e99ebee95b169d69d33a10161f Mon Sep 17 00:00:00 2001 From: sugargoat Date: Tue, 21 May 2024 16:18:42 -0700 Subject: [PATCH 04/21] Rename for brevity & clarity Signed-off-by: sugargoat --- full-service/src/db/account.rs | 50 +++++++++---------- full-service/src/db/models.rs | 2 +- full-service/src/json_rpc/v1/api/wallet.rs | 4 +- full-service/src/json_rpc/v2/api/request.rs | 16 +++--- full-service/src/json_rpc/v2/api/wallet.rs | 32 ++++++------ ..._and_submit_with_spend_from_subaddress.rs} | 6 +-- .../e2e_tests/transaction/build_submit/mod.rs | 2 +- full-service/src/service/account.rs | 24 ++++----- full-service/src/service/gift_code.rs | 2 +- full-service/src/service/transaction.rs | 39 +++++++-------- .../src/service/transaction_builder.rs | 6 +-- 11 files changed, 91 insertions(+), 92 deletions(-) rename full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/{build_and_submit_with_subaddress_to_spend_from.rs => build_and_submit_with_spend_from_subaddress.rs} (98%) diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index 70c7b8574..e4fa33443 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -82,7 +82,7 @@ pub trait AccountModel { ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | /// /// # Returns: /// * (account_id, main_subaddress_b58) @@ -95,7 +95,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -112,7 +112,7 @@ pub trait AccountModel { ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -126,7 +126,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -144,7 +144,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_enabled` | Indicate if fog server is enabled or disabled | | - ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -159,7 +159,7 @@ pub trait AccountModel { next_subaddress_index: Option, name: &str, fog_enabled: bool, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -176,7 +176,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -190,7 +190,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result; @@ -207,7 +207,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `spend_only_from_subaddress_mode` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -221,7 +221,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result; @@ -463,7 +463,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -485,7 +485,7 @@ impl AccountModel for Account { next_subaddress_index, name, fog_enabled, - spend_only_from_subaddress_mode, + require_spend_subaddresses, conn, ) } @@ -498,7 +498,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -520,7 +520,7 @@ impl AccountModel for Account { next_subaddress_index, name, fog_enabled, - spend_only_from_subaddress_mode, + require_spend_subaddresses, conn, ) } @@ -534,7 +534,7 @@ impl AccountModel for Account { next_subaddress_index: Option, name: &str, fog_enabled: bool, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { use crate::db::schema::accounts; @@ -563,7 +563,7 @@ impl AccountModel for Account { fog_enabled, view_only: false, managed_by_hardware_wallet: false, - spend_only_from_subaddress: spend_only_from_subaddress_mode, + spend_only_from_subaddress: require_spend_subaddresses, }; diesel::insert_into(accounts::table) @@ -599,7 +599,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_mnemonic( @@ -610,7 +610,7 @@ impl AccountModel for Account { &name.unwrap_or_default(), fog_report_url, fog_authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, conn, )?; Account::get(&account_id, conn) @@ -624,7 +624,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_root_entropy( @@ -635,7 +635,7 @@ impl AccountModel for Account { &name.unwrap_or_default(), fog_report_url, fog_authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, conn, )?; Account::get(&account_id, conn) @@ -1018,7 +1018,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, - spend_only_from_subaddress: false, + require_spend_subaddresses: false, }; assert_eq!(expected_account, acc); @@ -1087,7 +1087,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, - spend_only_from_subaddress: false, + require_spend_subaddresses: false, }; assert_eq!(expected_account_secondary, acc_secondary); @@ -1257,7 +1257,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, - spend_only_from_subaddress: false, + require_spend_subaddresses: false, }; assert_eq!(expected_account, acc); } @@ -1316,7 +1316,7 @@ mod tests { view_only: true, managed_by_hardware_wallet: false, resyncing: false, - spend_only_from_subaddress: false, + require_spend_subaddresses: false, }; assert_eq!(expected_account, account); } @@ -1377,7 +1377,7 @@ mod tests { view_only: true, managed_by_hardware_wallet: true, resyncing: false, - spend_only_from_subaddress: false, + require_spend_subaddresses: false, }; // Check to make sure the account in the database is correct diff --git a/full-service/src/db/models.rs b/full-service/src/db/models.rs index 3e23cb0f3..18a7e35ac 100644 --- a/full-service/src/db/models.rs +++ b/full-service/src/db/models.rs @@ -41,7 +41,7 @@ pub struct Account { pub managed_by_hardware_wallet: bool, pub resyncing: bool, /// If true, this account is only allowed to spend from subaddresses. - pub spend_only_from_subaddress: bool, + pub require_spend_subaddresses: bool, } /// A structure that can be inserted to create a new entity in the `accounts` diff --git a/full-service/src/json_rpc/v1/api/wallet.rs b/full-service/src/json_rpc/v1/api/wallet.rs index f0c3e64b1..97cbce34e 100644 --- a/full-service/src/json_rpc/v1/api/wallet.rs +++ b/full-service/src/json_rpc/v1/api/wallet.rs @@ -181,7 +181,7 @@ where subaddress_index: None, }, None, - None, // Note: Not including subaddress_to_spend_from in V1 API + None, // Note: Not including spend_from_subaddress in V1 API ) .await .map_err(format_error)?; @@ -298,7 +298,7 @@ where subaddress_index: None, }, None, - None, // Note: not including subaddress_to_spend_from in V1 API + None, // Note: not including spend_from_subaddress in V1 API ) .await .map_err(format_error)?; diff --git a/full-service/src/json_rpc/v2/api/request.rs b/full-service/src/json_rpc/v2/api/request.rs index 620cada93..236768610 100644 --- a/full-service/src/json_rpc/v2/api/request.rs +++ b/full-service/src/json_rpc/v2/api/request.rs @@ -57,7 +57,7 @@ pub enum JsonCommandRequest { block_version: Option, sender_memo_credential_subaddress_index: Option, payment_request_id: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, }, build_burn_transaction { account_id: String, @@ -69,7 +69,7 @@ pub enum JsonCommandRequest { tombstone_block: Option, max_spendable_value: Option, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, }, build_transaction { account_id: String, @@ -84,7 +84,7 @@ pub enum JsonCommandRequest { block_version: Option, sender_memo_credential_subaddress_index: Option, payment_request_id: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, }, build_unsigned_burn_transaction { account_id: String, @@ -96,7 +96,7 @@ pub enum JsonCommandRequest { tombstone_block: Option, max_spendable_value: Option, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, }, build_unsigned_transaction { account_id: String, @@ -109,7 +109,7 @@ pub enum JsonCommandRequest { tombstone_block: Option, max_spendable_value: Option, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, }, check_b58_type { b58_code: String, @@ -122,7 +122,7 @@ pub enum JsonCommandRequest { name: Option, fog_info: Option, #[serde(default = "bool::default")] // default is false - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, }, create_payment_request { account_id: String, @@ -229,7 +229,7 @@ pub enum JsonCommandRequest { next_subaddress_index: Option, fog_info: Option, #[serde(default = "bool::default")] // default is false - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, }, import_account { mnemonic: String, @@ -238,7 +238,7 @@ pub enum JsonCommandRequest { next_subaddress_index: Option, fog_info: Option, #[serde(default = "bool::default")] // default is false - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, }, import_view_only_account { view_private_key: String, diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index 4eb699cf8..32483eb57 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -172,7 +172,7 @@ where block_version, sender_memo_credential_subaddress_index, payment_request_id, - subaddress_to_spend_from, + spend_from_subaddress, } => { // The user can specify a list of addresses and values, // or a single address and a single value. @@ -219,7 +219,7 @@ where comment, transaction_memo, block_version, - subaddress_to_spend_from, + spend_from_subaddress, ) .await .map_err(format_error)?; @@ -243,7 +243,7 @@ where tombstone_block, max_spendable_value, block_version, - subaddress_to_spend_from, + spend_from_subaddress, } => { let mut memo_data = [0; BurnRedemptionMemo::MEMO_DATA_LEN]; if let Some(redemption_memo_hex) = redemption_memo_hex { @@ -279,7 +279,7 @@ where max_spendable_value, TransactionMemo::BurnRedemption(memo_data), block_version, - subaddress_to_spend_from, + spend_from_subaddress, ) .await .map_err(format_error)?; @@ -302,7 +302,7 @@ where block_version, sender_memo_credential_subaddress_index, payment_request_id, - subaddress_to_spend_from, + spend_from_subaddress, } => { // The user can specify a list of addresses and values, // or a single address and a single value. @@ -348,7 +348,7 @@ where max_spendable_value, transaction_memo, block_version, - subaddress_to_spend_from, + spend_from_subaddress, ) .await .map_err(format_error)?; @@ -368,7 +368,7 @@ where tombstone_block, max_spendable_value, block_version, - subaddress_to_spend_from, + spend_from_subaddress, } => { let mut memo_data = [0; BurnRedemptionMemo::MEMO_DATA_LEN]; if let Some(redemption_memo_hex) = redemption_memo_hex { @@ -404,7 +404,7 @@ where max_spendable_value, TransactionMemo::BurnRedemption(memo_data), block_version, - subaddress_to_spend_from, + spend_from_subaddress, ) .map_err(format_error)?) .try_into() @@ -426,7 +426,7 @@ where input_txo_ids, max_spendable_value, block_version, - subaddress_to_spend_from, + spend_from_subaddress, } => { let mut addresses_and_amounts = addresses_and_amounts.unwrap_or_default(); if let (Some(address), Some(amount)) = (recipient_public_address, amount) { @@ -452,7 +452,7 @@ where max_spendable_value, TransactionMemo::Empty, block_version, - subaddress_to_spend_from, + spend_from_subaddress, ) .map_err(format_error)?) .try_into() @@ -507,7 +507,7 @@ where JsonCommandRequest::create_account { name, fog_info, - spend_only_from_subaddress_mode, + require_spend_subaddresses, } => { let fog_info = fog_info.unwrap_or_default(); @@ -516,7 +516,7 @@ where name, fog_info.report_url, fog_info.authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, ) .map_err(format_error)?; @@ -1118,7 +1118,7 @@ where first_block_index, next_subaddress_index, fog_info, - spend_only_from_subaddress_mode, + require_spend_subaddresses, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1139,7 +1139,7 @@ where ns, fog_info.report_url, fog_info.authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, ) .map_err(format_error)?; @@ -1167,7 +1167,7 @@ where first_block_index, next_subaddress_index, fog_info, - spend_only_from_subaddress_mode, + require_spend_subaddresses, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1188,7 +1188,7 @@ where ns, fog_info.report_url, fog_info.authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, ) .map_err(format_error)?; diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_subaddress_to_spend_from.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs similarity index 98% rename from full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_subaddress_to_spend_from.rs rename to full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs index 2d0d2811c..80dfe58e9 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_subaddress_to_spend_from.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs @@ -31,7 +31,7 @@ mod e2e_transaction { // respectively from an external source. Bob sends 42 MOB to Alice, and the // balances should end up as [Alice: 142 MOB, Bob: 158 MOB, Carol: 300 MOB]. #[test_with_logger] - fn test_build_and_submit_transaction_with_subaddress_to_spend_from(logger: Logger) { + fn test_build_and_submit_transaction_with_spend_from_subaddress(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); @@ -42,7 +42,7 @@ mod e2e_transaction { "method": "create_account", "params": { "name": "Exchange Main Account", - "spend_only_from_subaddress_mode": true, + "require_spend_subaddresses": true, } }); let res = dispatch(&client, body, &logger); @@ -183,7 +183,7 @@ mod e2e_transaction { "account_id": account_id, "recipient_public_address": alice_b58_public_address, "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB - "subaddress_to_spend_from": bob_b58_public_address, + "spend_from_subaddress": bob_b58_public_address, } }); let res = dispatch(&client, body, &logger); diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs index 5e70bd4b9..fe9b27e46 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs @@ -1,5 +1,5 @@ mod build_and_submit; -mod build_and_submit_with_subaddress_to_spend_from; +mod build_and_submit_with_spend_from_subaddress; mod build_then_submit; mod build_unsigned; mod large_transaction; diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index 1c39a2d05..48ee90aef 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -162,14 +162,14 @@ pub trait AccountService { ///| `name` | A label for this account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `spend_only_from_subaddress_mode` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | + ///| `require_spend_subaddresses` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// fn create_account( &self, name: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -184,7 +184,7 @@ pub trait AccountService { ///| `next_subaddress_index` | The next known unused subaddress index for the account. | | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `spend_only_from_subaddress_mode` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | + ///| `require_spend_subaddresses` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// #[allow(clippy::too_many_arguments)] fn import_account( @@ -195,7 +195,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, ) -> Result; /// Import an existing account to the wallet using the entropy. @@ -210,7 +210,7 @@ pub trait AccountService { ///| `next_subaddress_index` | The next known unused subaddress index for the account. | | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `spend_only_from_subaddress_mode` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | + ///| `require_spend_subaddresses` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// #[allow(clippy::too_many_arguments)] fn import_account_from_legacy_root_entropy( @@ -221,7 +221,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -377,7 +377,7 @@ where name: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, ) -> Result { log::info!(self.logger, "Creating account {:?}", name,); @@ -415,7 +415,7 @@ where &name.unwrap_or_default(), fog_report_url, fog_authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, conn, )?; let account = Account::get(&account_id, conn)?; @@ -431,7 +431,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, ) -> Result { log::info!( self.logger, @@ -465,7 +465,7 @@ where next_subaddress_index, fog_report_url, fog_authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, conn, )?) }) @@ -479,7 +479,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - spend_only_from_subaddress_mode: bool, + require_spend_subaddresses: bool, ) -> Result { log::info!( self.logger, @@ -506,7 +506,7 @@ where next_subaddress_index, fog_report_url, fog_authority_spki, - spend_only_from_subaddress_mode, + require_spend_subaddresses, conn, )?) }) diff --git a/full-service/src/service/gift_code.rs b/full-service/src/service/gift_code.rs index 2eea41a06..eeea089ea 100644 --- a/full-service/src/service/gift_code.rs +++ b/full-service/src/service/gift_code.rs @@ -544,7 +544,7 @@ where subaddress_index: None, }, None, - None, /* NOTE: Assuming for now that we will not support subaddress_to_spend_from + None, /* NOTE: Assuming for now that we will not support spend_from_subaddress * in gift_code construction */ )?; diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index f02a3516c..f9164a589 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -342,7 +342,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `subaddress_to_spend_from` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | + ///| `spend_from_subaddress` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | /// #[allow(clippy::too_many_arguments)] fn build_transaction( @@ -356,7 +356,7 @@ pub trait TransactionService { max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, ) -> Result; /// Build a transaction and sign it before submitting it to the network. @@ -374,7 +374,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `subaddress_to_spend_from` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | + ///| `spend_from_subaddress` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | /// #[allow(clippy::too_many_arguments)] async fn build_and_sign_transaction( @@ -388,7 +388,7 @@ pub trait TransactionService { max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, ) -> Result; /// Submits a pre-built TxProposal to the MobileCoin Consensus Network. @@ -423,7 +423,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `subaddress_to_spend_from` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | + ///| `spend_from_subaddress` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | /// #[allow(clippy::too_many_arguments)] async fn build_sign_and_submit_transaction( @@ -438,7 +438,7 @@ pub trait TransactionService { comment: Option, memo: TransactionMemo, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, ) -> Result<(TransactionLog, AssociatedTxos, ValueMap, TxProposal), TransactionServiceError>; } @@ -459,7 +459,7 @@ where max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, ) -> Result { validate_number_inputs(input_txo_ids.unwrap_or(&Vec::new()).len() as u64)?; validate_number_outputs(addresses_and_amounts.len() as u64)?; @@ -469,15 +469,15 @@ where exclusive_transaction(conn, |conn| { if Account::get(&AccountID(account_id_hex.to_string()), conn)? - .spend_only_from_subaddress + .require_spend_subaddresses { - if subaddress_to_spend_from.is_none() { + if spend_from_subaddress.is_none() { return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( "This account is configured to spend only from a specific subaddress. Please provide a subaddress to spend from.".to_string() ))); } } else { - if subaddress_to_spend_from.is_some() { + if spend_from_subaddress.is_some() { return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( "This account is not configured to spend only from a specific subaddress. Please do not provide a subaddress to spend from.".to_string() ))); @@ -536,12 +536,11 @@ where if let Some(inputs) = input_txo_ids { builder.set_txos(conn, inputs)?; } else { - if let Some(subaddress) = subaddress_to_spend_from { + if let Some(subaddress) = spend_from_subaddress { let assigned_subaddress = AssignedSubaddress::get(&subaddress, conn)?; // Ensure the builder will filter to txos only from the specified subaddress - builder.set_subaddress_to_spend_from( - assigned_subaddress.subaddress_index as u64, - )?; + builder + .set_spend_from_subaddress(assigned_subaddress.subaddress_index as u64)?; } let max_spendable = if let Some(msv) = max_spendable_value { @@ -569,7 +568,7 @@ where max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, ) -> Result { let unsigned_tx_proposal = self.build_transaction( account_id_hex, @@ -581,7 +580,7 @@ where max_spendable_value, memo, block_version, - subaddress_to_spend_from, + spend_from_subaddress, )?; let mut pooled_conn = self.get_pooled_conn()?; @@ -670,7 +669,7 @@ where comment: Option, memo: TransactionMemo, block_version: Option, - subaddress_to_spend_from: Option, + spend_from_subaddress: Option, ) -> Result<(TransactionLog, AssociatedTxos, ValueMap, TxProposal), TransactionServiceError> { let tx_proposal = self @@ -684,7 +683,7 @@ where max_spendable_value, memo, block_version, - subaddress_to_spend_from, + spend_from_subaddress, ) .await?; @@ -1739,7 +1738,7 @@ mod tests { // transaction change arrives back to that subaddress. // This is a long, complicated test, so I'll list out the steps here for // readability: - // 1. Create exchange account with subaddress_to_spend_from_mode + // 1. Create exchange account with require_subaddress // 2. Create subaddresses for Alice and Bob // 3. Add a block with a transaction for 100 MOB from some external source for // Alice. Balances [Alice: 100, Bob: 0] @@ -1757,7 +1756,7 @@ mod tests { // confirm it fails. [Alice -> 58 (+fee) -> Bob (Fails)] // 11. Confirm final balances [Alice: 58, Bob: 242] #[async_test_with_logger] - async fn test_send_transaction_with_subaddress_to_spend_from(logger: Logger) { + async fn test_send_transaction_with_spend_from_subaddress(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); let known_recipients: Vec = Vec::new(); diff --git a/full-service/src/service/transaction_builder.rs b/full-service/src/service/transaction_builder.rs index 49cae246e..20a9c79e7 100644 --- a/full-service/src/service/transaction_builder.rs +++ b/full-service/src/service/transaction_builder.rs @@ -101,7 +101,7 @@ impl WalletTransactionBuilder { } /// Sets the subaddress from which to restrict TXOs for spending. - pub fn set_subaddress_to_spend_from( + pub fn set_spend_from_subaddress( &mut self, subaddress_index: u64, ) -> Result<(), WalletTransactionBuilderError> { @@ -158,7 +158,7 @@ impl WalletTransactionBuilder { 0 }; - let subaddress_to_spend_from = + let spend_from_subaddress = if let Some(subaddress_index_to_spend_from) = self.subaddress_index_to_spend_from { let account = Account::get(&AccountID(self.account_id_hex.clone()), conn)?; let subaddress = account.public_address(subaddress_index_to_spend_from)?; @@ -172,7 +172,7 @@ impl WalletTransactionBuilder { &self.account_id_hex, target_value, max_spendable_value, - subaddress_to_spend_from.as_deref(), + spend_from_subaddress.as_deref(), *token_id, fee_value, conn, From d67ff6c3de414b8ed55c2c9004cb05e16f30b0d3 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Tue, 21 May 2024 16:49:01 -0700 Subject: [PATCH 05/21] Add e2e tests for mode/build mismatch Signed-off-by: sugargoat --- ...d_and_submit_with_spend_from_subaddress.rs | 180 +++++++++++++++++- 1 file changed, 179 insertions(+), 1 deletion(-) diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs index 80dfe58e9..337ca7ad3 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs @@ -113,7 +113,7 @@ mod e2e_transaction { ); assert_eq!(ledger_db.num_blocks().unwrap(), 15); - // Get balance for the exchange, which should include all three suabddress + // Get balance for the exchange, which should include all three subaddress // balances. The state of the wallet should be: // // Overall Balance: 600 MOB @@ -308,4 +308,182 @@ mod e2e_transaction { assert_eq!(secreted, "0"); assert_eq!(orphaned, "0"); } + + #[test_with_logger] + fn test_build_and_submit_transaction_with_require_spend_subaddress_mismatch_fails_if_set( + logger: Logger, + ) { + use crate::error::WalletTransactionBuilderError::InvalidArgument as transaction_error; + let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); + let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); + + // Add an account + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "create_account", + "params": { + "name": "Exchange Main Account", + "require_spend_subaddresses": true, + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + let account_id = account_obj.get("id").unwrap().as_str().unwrap(); + + let ( + (alice_public_address, alice_b58_public_address), + (bob_public_address, bob_b58_public_address), + (carol_public_address, carol_b58_public_address), + ) = [ + "Subaddress for Alice", + "Subaddress for Bob", + "Subaddress for Carol", + ] + .iter() + .map(|metadata| { + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "assign_address_for_account", + "params": { + "account_id": account_id, + "metadata": metadata, + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let address = result.get("address").unwrap(); + let b58_address = address.get("public_address_b58").unwrap().as_str().unwrap(); + let public_address = b58_decode_public_address(b58_address).unwrap(); + (public_address, b58_address.to_string()) + }) + .collect_tuple() + .unwrap(); + + // Add a block with a txo for Bob + add_block_to_ledger_db( + &mut ledger_db, + &vec![bob_public_address.clone()], + 200 * MOB, + &[KeyImage::from(rng.next_u64())], + &mut rng, + ); + + manually_sync_account( + &ledger_db, + &db_ctx.get_db_instance(logger.clone()), + &AccountID(account_id.to_string()), + &logger, + ); + + // Imagine that Bob is sending 42.0 MOB to Alice + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "build_and_submit_transaction", + "params": { + "account_id": account_id, + "recipient_public_address": alice_b58_public_address, + "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB + } + }); + 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(&transaction_error("This account is configured to spend only from a specific subaddress. Please provide a subaddress to spend from.".to_string()).to_string())); + } + + #[test_with_logger] + fn test_build_and_submit_transaction_with_require_spend_subaddress_mismatch_fails_if_not_set( + logger: Logger, + ) { + use crate::error::WalletTransactionBuilderError::InvalidArgument as transaction_error; + let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); + let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); + + // Add an account + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "create_account", + "params": { + "name": "Exchange Main Account", + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + let account_id = account_obj.get("id").unwrap().as_str().unwrap(); + + let ( + (alice_public_address, alice_b58_public_address), + (bob_public_address, bob_b58_public_address), + (carol_public_address, carol_b58_public_address), + ) = [ + "Subaddress for Alice", + "Subaddress for Bob", + "Subaddress for Carol", + ] + .iter() + .map(|metadata| { + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "assign_address_for_account", + "params": { + "account_id": account_id, + "metadata": metadata, + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let address = result.get("address").unwrap(); + let b58_address = address.get("public_address_b58").unwrap().as_str().unwrap(); + let public_address = b58_decode_public_address(b58_address).unwrap(); + (public_address, b58_address.to_string()) + }) + .collect_tuple() + .unwrap(); + + // Add a block with a txo for Bob + add_block_to_ledger_db( + &mut ledger_db, + &vec![bob_public_address.clone()], + 200 * MOB, + &[KeyImage::from(rng.next_u64())], + &mut rng, + ); + + manually_sync_account( + &ledger_db, + &db_ctx.get_db_instance(logger.clone()), + &AccountID(account_id.to_string()), + &logger, + ); + + // Imagine that Bob is sending 42.0 MOB to Alice + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "build_and_submit_transaction", + "params": { + "account_id": account_id, + "recipient_public_address": alice_b58_public_address, + "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB + "spend_from_subaddress": bob_b58_public_address, + } + }); + 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(&transaction_error("This account is not configured to spend only from a specific subaddress. Please do not provide a subaddress to spend from.".to_string()).to_string())); + } } From 0d4eda3dc7eb142f1b052e641ed1ab556a852f8a Mon Sep 17 00:00:00 2001 From: sugargoat Date: Tue, 21 May 2024 17:16:11 -0700 Subject: [PATCH 06/21] Fix up db and migrations Signed-off-by: sugargoat --- .../2024-05-21-035622_subaddress-only-account/down.sql | 2 +- .../2024-05-21-035622_subaddress-only-account/up.sql | 2 +- full-service/src/db/account.rs | 6 +++--- full-service/src/db/models.rs | 2 +- full-service/src/db/schema.rs | 2 +- full-service/src/service/transaction.rs | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql b/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql index e739fde5b..4dfffc4dc 100644 --- a/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql +++ b/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql @@ -1,2 +1,2 @@ -- This file should undo anything in `up.sql` -ALTER TABLE accounts DROP COLUMN spend_only_from_subaddress; \ No newline at end of file +ALTER TABLE accounts DROP COLUMN require_spend_subaddresses; \ No newline at end of file diff --git a/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql b/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql index edf6e0851..624f67238 100644 --- a/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql +++ b/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql @@ -1,3 +1,3 @@ -- Your SQL goes here ALTER TABLE accounts - ADD COLUMN spend_only_from_subaddress BOOLEAN NOT NULL DEFAULT FALSE; + ADD COLUMN require_spend_subaddresses 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 e4fa33443..76b99dcf5 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -563,7 +563,7 @@ impl AccountModel for Account { fog_enabled, view_only: false, managed_by_hardware_wallet: false, - spend_only_from_subaddress: require_spend_subaddresses, + require_spend_subaddresses: require_spend_subaddresses, }; diesel::insert_into(accounts::table) @@ -676,7 +676,7 @@ impl AccountModel for Account { fog_enabled: false, view_only: true, managed_by_hardware_wallet, - spend_only_from_subaddress: false, + require_spend_subaddresses: false, }; diesel::insert_into(accounts::table) @@ -747,7 +747,7 @@ impl AccountModel for Account { fog_enabled: true, view_only: true, managed_by_hardware_wallet: true, - spend_only_from_subaddress: false, + require_spend_subaddresses: false, }; diesel::insert_into(accounts::table) diff --git a/full-service/src/db/models.rs b/full-service/src/db/models.rs index 18a7e35ac..28fe0d31d 100644 --- a/full-service/src/db/models.rs +++ b/full-service/src/db/models.rs @@ -60,7 +60,7 @@ pub struct NewAccount<'a> { pub fog_enabled: bool, pub view_only: bool, pub managed_by_hardware_wallet: bool, - pub spend_only_from_subaddress: bool, + pub require_spend_subaddresses: 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 acc1f8aca..c54a4a2ef 100644 --- a/full-service/src/db/schema.rs +++ b/full-service/src/db/schema.rs @@ -12,7 +12,7 @@ diesel::table! { view_only -> Bool, managed_by_hardware_wallet -> Bool, resyncing -> Bool, - spend_only_from_subaddress -> Bool, + require_spend_subaddresses -> Bool, } } diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index f9164a589..e38cc27a8 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -374,7 +374,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `spend_from_subaddress` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | + ///| `spend_from_subaddress` | The subaddress index to spend from. | | /// #[allow(clippy::too_many_arguments)] async fn build_and_sign_transaction( @@ -423,7 +423,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `spend_from_subaddress` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | + ///| `spend_from_subaddress` | The subaddress index to spend from. | | /// #[allow(clippy::too_many_arguments)] async fn build_sign_and_submit_transaction( From 94435b2b0933abba320f9c60cf0a2aac0607f089 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 12:20:24 -0700 Subject: [PATCH 07/21] Rename for conciseness & clarity Signed-off-by: sugargoat --- .../down.sql | 2 +- .../up.sql | 2 +- full-service/src/db/account.rs | 54 +++++++++---------- full-service/src/db/models.rs | 4 +- full-service/src/db/schema.rs | 2 +- full-service/src/json_rpc/v1/api/wallet.rs | 4 +- full-service/src/json_rpc/v2/api/request.rs | 16 +++--- full-service/src/json_rpc/v2/api/wallet.rs | 32 +++++------ ...build_and_submit_with_spend_subaddress.rs} | 10 ++-- .../e2e_tests/transaction/build_submit/mod.rs | 2 +- full-service/src/service/account.rs | 24 ++++----- full-service/src/service/gift_code.rs | 2 +- full-service/src/service/transaction.rs | 36 ++++++------- .../src/service/transaction_builder.rs | 10 ++-- 14 files changed, 99 insertions(+), 101 deletions(-) rename full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/{build_and_submit_with_spend_from_subaddress.rs => build_and_submit_with_spend_subaddress.rs} (98%) diff --git a/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql b/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql index 4dfffc4dc..b70d56702 100644 --- a/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql +++ b/full-service/migrations/2024-05-21-035622_subaddress-only-account/down.sql @@ -1,2 +1,2 @@ -- This file should undo anything in `up.sql` -ALTER TABLE accounts DROP COLUMN require_spend_subaddresses; \ No newline at end of file +ALTER TABLE accounts DROP COLUMN require_spend_subaddress; \ No newline at end of file diff --git a/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql b/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql index 624f67238..ea368e4b3 100644 --- a/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql +++ b/full-service/migrations/2024-05-21-035622_subaddress-only-account/up.sql @@ -1,3 +1,3 @@ -- Your SQL goes here ALTER TABLE accounts - ADD COLUMN require_spend_subaddresses BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file + ADD COLUMN require_spend_subaddress 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 76b99dcf5..d360a74b8 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -82,7 +82,7 @@ pub trait AccountModel { ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddress` | If enabled, this mode requires all transactions to spend from a provided subaddress | | /// /// # Returns: /// * (account_id, main_subaddress_b58) @@ -95,7 +95,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -112,7 +112,7 @@ pub trait AccountModel { ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddress` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -126,7 +126,7 @@ pub trait AccountModel { name: &str, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -144,7 +144,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `name` | The display name for the account. | A label can have duplicates, but it is not recommended. | ///| `fog_enabled` | Indicate if fog server is enabled or disabled | | - ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddress` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -159,7 +159,7 @@ pub trait AccountModel { next_subaddress_index: Option, name: &str, fog_enabled: bool, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError>; @@ -176,7 +176,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddress` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -190,7 +190,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result; @@ -207,7 +207,7 @@ pub trait AccountModel { ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `require_spend_subaddresses` | If enabled, this mode requires all transactions to spend from a provided subaddress | | + ///| `require_spend_subaddress` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -221,7 +221,7 @@ pub trait AccountModel { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result; @@ -463,7 +463,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -485,7 +485,7 @@ impl AccountModel for Account { next_subaddress_index, name, fog_enabled, - require_spend_subaddresses, + require_spend_subaddress, conn, ) } @@ -498,7 +498,7 @@ impl AccountModel for Account { name: &str, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { let fog_enabled = !fog_report_url.is_empty(); @@ -520,7 +520,7 @@ impl AccountModel for Account { next_subaddress_index, name, fog_enabled, - require_spend_subaddresses, + require_spend_subaddress, conn, ) } @@ -534,7 +534,7 @@ impl AccountModel for Account { next_subaddress_index: Option, name: &str, fog_enabled: bool, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result<(AccountID, String), WalletDbError> { use crate::db::schema::accounts; @@ -563,7 +563,7 @@ impl AccountModel for Account { fog_enabled, view_only: false, managed_by_hardware_wallet: false, - require_spend_subaddresses: require_spend_subaddresses, + require_spend_subaddress: require_spend_subaddress, }; diesel::insert_into(accounts::table) @@ -599,7 +599,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_mnemonic( @@ -610,7 +610,7 @@ impl AccountModel for Account { &name.unwrap_or_default(), fog_report_url, fog_authority_spki, - require_spend_subaddresses, + require_spend_subaddress, conn, )?; Account::get(&account_id, conn) @@ -624,7 +624,7 @@ impl AccountModel for Account { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result { let (account_id, _public_address_b58) = Account::create_from_root_entropy( @@ -635,7 +635,7 @@ impl AccountModel for Account { &name.unwrap_or_default(), fog_report_url, fog_authority_spki, - require_spend_subaddresses, + require_spend_subaddress, conn, )?; Account::get(&account_id, conn) @@ -676,7 +676,7 @@ impl AccountModel for Account { fog_enabled: false, view_only: true, managed_by_hardware_wallet, - require_spend_subaddresses: false, + require_spend_subaddress: false, }; diesel::insert_into(accounts::table) @@ -747,7 +747,7 @@ impl AccountModel for Account { fog_enabled: true, view_only: true, managed_by_hardware_wallet: true, - require_spend_subaddresses: false, + require_spend_subaddress: false, }; diesel::insert_into(accounts::table) @@ -1018,7 +1018,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, - require_spend_subaddresses: false, + require_spend_subaddress: false, }; assert_eq!(expected_account, acc); @@ -1087,7 +1087,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, - require_spend_subaddresses: false, + require_spend_subaddress: false, }; assert_eq!(expected_account_secondary, acc_secondary); @@ -1257,7 +1257,7 @@ mod tests { view_only: false, managed_by_hardware_wallet: false, resyncing: false, - require_spend_subaddresses: false, + require_spend_subaddress: false, }; assert_eq!(expected_account, acc); } @@ -1316,7 +1316,7 @@ mod tests { view_only: true, managed_by_hardware_wallet: false, resyncing: false, - require_spend_subaddresses: false, + require_spend_subaddress: false, }; assert_eq!(expected_account, account); } @@ -1377,7 +1377,7 @@ mod tests { view_only: true, managed_by_hardware_wallet: true, resyncing: false, - require_spend_subaddresses: false, + require_spend_subaddress: false, }; // Check to make sure the account in the database is correct diff --git a/full-service/src/db/models.rs b/full-service/src/db/models.rs index 28fe0d31d..284275947 100644 --- a/full-service/src/db/models.rs +++ b/full-service/src/db/models.rs @@ -41,7 +41,7 @@ pub struct Account { pub managed_by_hardware_wallet: bool, pub resyncing: bool, /// If true, this account is only allowed to spend from subaddresses. - pub require_spend_subaddresses: bool, + pub require_spend_subaddress: bool, } /// A structure that can be inserted to create a new entity in the `accounts` @@ -60,7 +60,7 @@ pub struct NewAccount<'a> { pub fog_enabled: bool, pub view_only: bool, pub managed_by_hardware_wallet: bool, - pub require_spend_subaddresses: bool, + pub require_spend_subaddress: 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 c54a4a2ef..a8e39c6da 100644 --- a/full-service/src/db/schema.rs +++ b/full-service/src/db/schema.rs @@ -12,7 +12,7 @@ diesel::table! { view_only -> Bool, managed_by_hardware_wallet -> Bool, resyncing -> Bool, - require_spend_subaddresses -> Bool, + require_spend_subaddress -> Bool, } } diff --git a/full-service/src/json_rpc/v1/api/wallet.rs b/full-service/src/json_rpc/v1/api/wallet.rs index 97cbce34e..3538ac21c 100644 --- a/full-service/src/json_rpc/v1/api/wallet.rs +++ b/full-service/src/json_rpc/v1/api/wallet.rs @@ -181,7 +181,7 @@ where subaddress_index: None, }, None, - None, // Note: Not including spend_from_subaddress in V1 API + None, // Note: Not including spend_subaddress in V1 API ) .await .map_err(format_error)?; @@ -298,7 +298,7 @@ where subaddress_index: None, }, None, - None, // Note: not including spend_from_subaddress in V1 API + None, // Note: not including spend_subaddress in V1 API ) .await .map_err(format_error)?; diff --git a/full-service/src/json_rpc/v2/api/request.rs b/full-service/src/json_rpc/v2/api/request.rs index 236768610..6b1c9572d 100644 --- a/full-service/src/json_rpc/v2/api/request.rs +++ b/full-service/src/json_rpc/v2/api/request.rs @@ -57,7 +57,7 @@ pub enum JsonCommandRequest { block_version: Option, sender_memo_credential_subaddress_index: Option, payment_request_id: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, }, build_burn_transaction { account_id: String, @@ -69,7 +69,7 @@ pub enum JsonCommandRequest { tombstone_block: Option, max_spendable_value: Option, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, }, build_transaction { account_id: String, @@ -84,7 +84,7 @@ pub enum JsonCommandRequest { block_version: Option, sender_memo_credential_subaddress_index: Option, payment_request_id: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, }, build_unsigned_burn_transaction { account_id: String, @@ -96,7 +96,7 @@ pub enum JsonCommandRequest { tombstone_block: Option, max_spendable_value: Option, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, }, build_unsigned_transaction { account_id: String, @@ -109,7 +109,7 @@ pub enum JsonCommandRequest { tombstone_block: Option, max_spendable_value: Option, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, }, check_b58_type { b58_code: String, @@ -122,7 +122,7 @@ pub enum JsonCommandRequest { name: Option, fog_info: Option, #[serde(default = "bool::default")] // default is false - require_spend_subaddresses: bool, + require_spend_subaddress: bool, }, create_payment_request { account_id: String, @@ -229,7 +229,7 @@ pub enum JsonCommandRequest { next_subaddress_index: Option, fog_info: Option, #[serde(default = "bool::default")] // default is false - require_spend_subaddresses: bool, + require_spend_subaddress: bool, }, import_account { mnemonic: String, @@ -238,7 +238,7 @@ pub enum JsonCommandRequest { next_subaddress_index: Option, fog_info: Option, #[serde(default = "bool::default")] // default is false - require_spend_subaddresses: bool, + require_spend_subaddress: bool, }, import_view_only_account { view_private_key: String, diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index 32483eb57..8975ad7b3 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -172,7 +172,7 @@ where block_version, sender_memo_credential_subaddress_index, payment_request_id, - spend_from_subaddress, + spend_subaddress, } => { // The user can specify a list of addresses and values, // or a single address and a single value. @@ -219,7 +219,7 @@ where comment, transaction_memo, block_version, - spend_from_subaddress, + spend_subaddress, ) .await .map_err(format_error)?; @@ -243,7 +243,7 @@ where tombstone_block, max_spendable_value, block_version, - spend_from_subaddress, + spend_subaddress, } => { let mut memo_data = [0; BurnRedemptionMemo::MEMO_DATA_LEN]; if let Some(redemption_memo_hex) = redemption_memo_hex { @@ -279,7 +279,7 @@ where max_spendable_value, TransactionMemo::BurnRedemption(memo_data), block_version, - spend_from_subaddress, + spend_subaddress, ) .await .map_err(format_error)?; @@ -302,7 +302,7 @@ where block_version, sender_memo_credential_subaddress_index, payment_request_id, - spend_from_subaddress, + spend_subaddress, } => { // The user can specify a list of addresses and values, // or a single address and a single value. @@ -348,7 +348,7 @@ where max_spendable_value, transaction_memo, block_version, - spend_from_subaddress, + spend_subaddress, ) .await .map_err(format_error)?; @@ -368,7 +368,7 @@ where tombstone_block, max_spendable_value, block_version, - spend_from_subaddress, + spend_subaddress, } => { let mut memo_data = [0; BurnRedemptionMemo::MEMO_DATA_LEN]; if let Some(redemption_memo_hex) = redemption_memo_hex { @@ -404,7 +404,7 @@ where max_spendable_value, TransactionMemo::BurnRedemption(memo_data), block_version, - spend_from_subaddress, + spend_subaddress, ) .map_err(format_error)?) .try_into() @@ -426,7 +426,7 @@ where input_txo_ids, max_spendable_value, block_version, - spend_from_subaddress, + spend_subaddress, } => { let mut addresses_and_amounts = addresses_and_amounts.unwrap_or_default(); if let (Some(address), Some(amount)) = (recipient_public_address, amount) { @@ -452,7 +452,7 @@ where max_spendable_value, TransactionMemo::Empty, block_version, - spend_from_subaddress, + spend_subaddress, ) .map_err(format_error)?) .try_into() @@ -507,7 +507,7 @@ where JsonCommandRequest::create_account { name, fog_info, - require_spend_subaddresses, + require_spend_subaddress, } => { let fog_info = fog_info.unwrap_or_default(); @@ -516,7 +516,7 @@ where name, fog_info.report_url, fog_info.authority_spki, - require_spend_subaddresses, + require_spend_subaddress, ) .map_err(format_error)?; @@ -1118,7 +1118,7 @@ where first_block_index, next_subaddress_index, fog_info, - require_spend_subaddresses, + require_spend_subaddress, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1139,7 +1139,7 @@ where ns, fog_info.report_url, fog_info.authority_spki, - require_spend_subaddresses, + require_spend_subaddress, ) .map_err(format_error)?; @@ -1167,7 +1167,7 @@ where first_block_index, next_subaddress_index, fog_info, - require_spend_subaddresses, + require_spend_subaddress, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1188,7 +1188,7 @@ where ns, fog_info.report_url, fog_info.authority_spki, - require_spend_subaddresses, + require_spend_subaddress, ) .map_err(format_error)?; diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs similarity index 98% rename from full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs rename to full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs index 337ca7ad3..d5678c711 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_from_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs @@ -31,7 +31,7 @@ mod e2e_transaction { // respectively from an external source. Bob sends 42 MOB to Alice, and the // balances should end up as [Alice: 142 MOB, Bob: 158 MOB, Carol: 300 MOB]. #[test_with_logger] - fn test_build_and_submit_transaction_with_spend_from_subaddress(logger: Logger) { + fn test_build_and_submit_transaction_with_spend_subaddress(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); @@ -42,7 +42,7 @@ mod e2e_transaction { "method": "create_account", "params": { "name": "Exchange Main Account", - "require_spend_subaddresses": true, + "require_spend_subaddress": true, } }); let res = dispatch(&client, body, &logger); @@ -183,7 +183,7 @@ mod e2e_transaction { "account_id": account_id, "recipient_public_address": alice_b58_public_address, "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB - "spend_from_subaddress": bob_b58_public_address, + "spend_subaddress": bob_b58_public_address, } }); let res = dispatch(&client, body, &logger); @@ -324,7 +324,7 @@ mod e2e_transaction { "method": "create_account", "params": { "name": "Exchange Main Account", - "require_spend_subaddresses": true, + "require_spend_subaddress": true, } }); let res = dispatch(&client, body, &logger); @@ -475,7 +475,7 @@ mod e2e_transaction { "account_id": account_id, "recipient_public_address": alice_b58_public_address, "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB - "spend_from_subaddress": bob_b58_public_address, + "spend_subaddress": bob_b58_public_address, } }); let res = dispatch(&client, body, &logger); diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs index fe9b27e46..2d471178b 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs @@ -1,5 +1,5 @@ mod build_and_submit; -mod build_and_submit_with_spend_from_subaddress; +mod build_and_submit_with_spend_subaddress; mod build_then_submit; mod build_unsigned; mod large_transaction; diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index 48ee90aef..7bb164e62 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -162,14 +162,14 @@ pub trait AccountService { ///| `name` | A label for this account. | A label can have duplicates, but it is not recommended. | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `require_spend_subaddresses` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | + ///| `require_spend_subaddress` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// fn create_account( &self, name: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -184,7 +184,7 @@ pub trait AccountService { ///| `next_subaddress_index` | The next known unused subaddress index for the account. | | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `require_spend_subaddresses` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | + ///| `require_spend_subaddress` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// #[allow(clippy::too_many_arguments)] fn import_account( @@ -195,7 +195,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, ) -> Result; /// Import an existing account to the wallet using the entropy. @@ -210,7 +210,7 @@ pub trait AccountService { ///| `next_subaddress_index` | The next known unused subaddress index for the account. | | ///| `fog_report_url` | Fog Report server url. | Applicable only if user has Fog service, empty string otherwise. | ///| `fog_authority_spki` | Fog Authority Subject Public Key Info. | Applicable only if user has Fog service, empty string otherwise. | - ///| `require_spend_subaddresses` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | + ///| `require_spend_subaddress` | Spend only from subaddress. | Only allow the account to spend from give subaddresses. | /// #[allow(clippy::too_many_arguments)] fn import_account_from_legacy_root_entropy( @@ -221,7 +221,7 @@ pub trait AccountService { next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, ) -> Result; /// Import an existing account to the wallet using the mnemonic. @@ -377,7 +377,7 @@ where name: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, ) -> Result { log::info!(self.logger, "Creating account {:?}", name,); @@ -415,7 +415,7 @@ where &name.unwrap_or_default(), fog_report_url, fog_authority_spki, - require_spend_subaddresses, + require_spend_subaddress, conn, )?; let account = Account::get(&account_id, conn)?; @@ -431,7 +431,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, ) -> Result { log::info!( self.logger, @@ -465,7 +465,7 @@ where next_subaddress_index, fog_report_url, fog_authority_spki, - require_spend_subaddresses, + require_spend_subaddress, conn, )?) }) @@ -479,7 +479,7 @@ where next_subaddress_index: Option, fog_report_url: String, fog_authority_spki: String, - require_spend_subaddresses: bool, + require_spend_subaddress: bool, ) -> Result { log::info!( self.logger, @@ -506,7 +506,7 @@ where next_subaddress_index, fog_report_url, fog_authority_spki, - require_spend_subaddresses, + require_spend_subaddress, conn, )?) }) diff --git a/full-service/src/service/gift_code.rs b/full-service/src/service/gift_code.rs index eeea089ea..7a5dcf8b3 100644 --- a/full-service/src/service/gift_code.rs +++ b/full-service/src/service/gift_code.rs @@ -544,7 +544,7 @@ where subaddress_index: None, }, None, - None, /* NOTE: Assuming for now that we will not support spend_from_subaddress + None, /* NOTE: Assuming for now that we will not support spend_subaddress * in gift_code construction */ )?; diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index e38cc27a8..093671cbd 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -342,7 +342,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `spend_from_subaddress` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | + ///| `spend_subaddress` | The subaddress index to spend from. | (optional) ONLY use this parameter if you will ALWAYS use this parameter when spending, or else you may get unexpected balances because normal spending can pull any account txos no matter which subaddress they were received at | /// #[allow(clippy::too_many_arguments)] fn build_transaction( @@ -356,7 +356,7 @@ pub trait TransactionService { max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, ) -> Result; /// Build a transaction and sign it before submitting it to the network. @@ -374,7 +374,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `spend_from_subaddress` | The subaddress index to spend from. | | + ///| `spend_subaddress` | The subaddress index to spend from. | | /// #[allow(clippy::too_many_arguments)] async fn build_and_sign_transaction( @@ -388,7 +388,7 @@ pub trait TransactionService { max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, ) -> Result; /// Submits a pre-built TxProposal to the MobileCoin Consensus Network. @@ -423,7 +423,7 @@ pub trait TransactionService { ///| `max_spendable_value` | The maximum amount for an input TXO selected for this transaction | | ///| `memo` | Memo for the transaction | | ///| `block_version` | The block version to build this transaction for. | Defaults to the network block version | - ///| `spend_from_subaddress` | The subaddress index to spend from. | | + ///| `spend_subaddress` | The subaddress index to spend from. | | /// #[allow(clippy::too_many_arguments)] async fn build_sign_and_submit_transaction( @@ -438,7 +438,7 @@ pub trait TransactionService { comment: Option, memo: TransactionMemo, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, ) -> Result<(TransactionLog, AssociatedTxos, ValueMap, TxProposal), TransactionServiceError>; } @@ -459,7 +459,7 @@ where max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, ) -> Result { validate_number_inputs(input_txo_ids.unwrap_or(&Vec::new()).len() as u64)?; validate_number_outputs(addresses_and_amounts.len() as u64)?; @@ -468,16 +468,15 @@ where let conn = pooled_conn.deref_mut(); exclusive_transaction(conn, |conn| { - if Account::get(&AccountID(account_id_hex.to_string()), conn)? - .require_spend_subaddresses + if Account::get(&AccountID(account_id_hex.to_string()), conn)?.require_spend_subaddress { - if spend_from_subaddress.is_none() { + if spend_subaddress.is_none() { return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( "This account is configured to spend only from a specific subaddress. Please provide a subaddress to spend from.".to_string() ))); } } else { - if spend_from_subaddress.is_some() { + if spend_subaddress.is_some() { return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( "This account is not configured to spend only from a specific subaddress. Please do not provide a subaddress to spend from.".to_string() ))); @@ -536,11 +535,10 @@ where if let Some(inputs) = input_txo_ids { builder.set_txos(conn, inputs)?; } else { - if let Some(subaddress) = spend_from_subaddress { + if let Some(subaddress) = spend_subaddress { let assigned_subaddress = AssignedSubaddress::get(&subaddress, conn)?; // Ensure the builder will filter to txos only from the specified subaddress - builder - .set_spend_from_subaddress(assigned_subaddress.subaddress_index as u64)?; + builder.set_spend_subaddress(assigned_subaddress.subaddress_index as u64)?; } let max_spendable = if let Some(msv) = max_spendable_value { @@ -568,7 +566,7 @@ where max_spendable_value: Option, memo: TransactionMemo, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, ) -> Result { let unsigned_tx_proposal = self.build_transaction( account_id_hex, @@ -580,7 +578,7 @@ where max_spendable_value, memo, block_version, - spend_from_subaddress, + spend_subaddress, )?; let mut pooled_conn = self.get_pooled_conn()?; @@ -669,7 +667,7 @@ where comment: Option, memo: TransactionMemo, block_version: Option, - spend_from_subaddress: Option, + spend_subaddress: Option, ) -> Result<(TransactionLog, AssociatedTxos, ValueMap, TxProposal), TransactionServiceError> { let tx_proposal = self @@ -683,7 +681,7 @@ where max_spendable_value, memo, block_version, - spend_from_subaddress, + spend_subaddress, ) .await?; @@ -1756,7 +1754,7 @@ mod tests { // confirm it fails. [Alice -> 58 (+fee) -> Bob (Fails)] // 11. Confirm final balances [Alice: 58, Bob: 242] #[async_test_with_logger] - async fn test_send_transaction_with_spend_from_subaddress(logger: Logger) { + async fn test_send_transaction_with_spend_subaddress(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([20u8; 32]); let known_recipients: Vec = Vec::new(); diff --git a/full-service/src/service/transaction_builder.rs b/full-service/src/service/transaction_builder.rs index 20a9c79e7..cb85917ce 100644 --- a/full-service/src/service/transaction_builder.rs +++ b/full-service/src/service/transaction_builder.rs @@ -101,7 +101,7 @@ impl WalletTransactionBuilder { } /// Sets the subaddress from which to restrict TXOs for spending. - pub fn set_spend_from_subaddress( + pub fn set_spend_subaddress( &mut self, subaddress_index: u64, ) -> Result<(), WalletTransactionBuilderError> { @@ -158,7 +158,7 @@ impl WalletTransactionBuilder { 0 }; - let spend_from_subaddress = + let spend_subaddress = if let Some(subaddress_index_to_spend_from) = self.subaddress_index_to_spend_from { let account = Account::get(&AccountID(self.account_id_hex.clone()), conn)?; let subaddress = account.public_address(subaddress_index_to_spend_from)?; @@ -172,7 +172,7 @@ impl WalletTransactionBuilder { &self.account_id_hex, target_value, max_spendable_value, - spend_from_subaddress.as_deref(), + spend_subaddress.as_deref(), *token_id, fee_value, conn, @@ -460,7 +460,7 @@ impl WalletTransactionBuilder { // Send the change back to the subaddress that is spending the inputs. // In the future, we may want to allow this to be a bit more configurable let change_address = account.public_address(subaddress_index_to_spend_from)?; - let reserved_subaddresses_for_spend_from_subaddress_mode = + let reserved_subaddresses_for_spend_subaddress_mode = ReservedSubaddresses::from_subaddress_index( &account.account_key()?, self.subaddress_index_to_spend_from, @@ -471,7 +471,7 @@ impl WalletTransactionBuilder { // inputs, with the DestinationMemo properly constructed as a Change Output let tx_out_context = transaction_builder.add_change_output( change_amount, - &reserved_subaddresses_for_spend_from_subaddress_mode, + &reserved_subaddresses_for_spend_subaddress_mode, &mut rng, )?; From e640641f4e79c134be85fccb77d925facad11904 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 12:28:28 -0700 Subject: [PATCH 08/21] Add require_spend_sudaddress for import_view_only methods Signed-off-by: sugargoat --- full-service/src/db/account.rs | 11 +++++++++-- full-service/src/json_rpc/v2/api/wallet.rs | 3 ++- full-service/src/service/account.rs | 8 ++++++++ full-service/src/service/address.rs | 9 ++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index d360a74b8..7cbbf1dfb 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -238,6 +238,7 @@ pub trait AccountModel { ///| `first_block_index` | Index of the first block when this account may have received funds. | Defaults to 0 if not provided | ///| `next_subaddress_index` | This index represents the next subaddress to be assigned as an address. | This is useful information in case the account is imported elsewhere. | ///| `managed_by_hardware_wallet` | Whether the account is managed by a hardware wallet. | | + ///| `require_spend_subaddress` | If enabled, this mode requires all transactions to spend from a provided subaddress | | ///| `conn` | An reference to the pool connection of wallet database | | /// /// # Returns: @@ -250,6 +251,7 @@ pub trait AccountModel { first_block_index: Option, next_subaddress_index: Option, managed_by_hardware_wallet: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result; @@ -259,6 +261,7 @@ pub trait AccountModel { import_block_index: u64, first_block_index: Option, default_public_address: &PublicAddress, + require_spend_subaddress: bool, conn: Conn, ) -> Result; @@ -648,6 +651,7 @@ impl AccountModel for Account { first_block_index: Option, next_subaddress_index: Option, managed_by_hardware_wallet: bool, + require_spend_subaddress: bool, conn: Conn, ) -> Result { use crate::db::schema::accounts; @@ -676,7 +680,7 @@ impl AccountModel for Account { fog_enabled: false, view_only: true, managed_by_hardware_wallet, - require_spend_subaddress: false, + require_spend_subaddress, }; diesel::insert_into(accounts::table) @@ -722,6 +726,7 @@ impl AccountModel for Account { import_block_index: u64, first_block_index: Option, default_public_address: &PublicAddress, + require_spend_subaddress: bool, conn: Conn, ) -> Result { use crate::db::schema::accounts; @@ -747,7 +752,7 @@ impl AccountModel for Account { fog_enabled: true, view_only: true, managed_by_hardware_wallet: true, - require_spend_subaddress: false, + require_spend_subaddress, }; diesel::insert_into(accounts::table) @@ -1285,6 +1290,7 @@ mod tests { None, None, false, + false, conn, ) .unwrap() @@ -1349,6 +1355,7 @@ mod tests { 12, None, &default_public_address, + false, conn, ) .unwrap() diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index 8975ad7b3..3c220b0b4 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -1245,6 +1245,7 @@ where name, fb, ns, + false, ) .map_err(format_error)?; let next_subaddress_index = service @@ -1274,7 +1275,7 @@ where .map_err(format_error)?; let account = service - .import_view_only_account_from_hardware_wallet(name, fb, fog_info) + .import_view_only_account_from_hardware_wallet(name, fb, fog_info, false) .await .map_err(format_error)?; diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index 7bb164e62..e62dac849 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -243,6 +243,7 @@ pub trait AccountService { name: Option, first_block_index: Option, next_subaddress_index: Option, + require_spend_subaddress: bool, ) -> Result; async fn import_view_only_account_from_hardware_wallet( @@ -250,6 +251,7 @@ pub trait AccountService { name: Option, first_block_index: Option, fog_info: Option, + require_spend_subaddress: bool, ) -> Result; /// Re-create sync request for a view only account @@ -519,6 +521,7 @@ where name: Option, first_block_index: Option, next_subaddress_index: Option, + require_spend_subaddress: bool, ) -> Result { log::info!( self.logger, @@ -542,6 +545,7 @@ where first_block_index, next_subaddress_index, false, + require_spend_subaddress, conn, )?) }) @@ -552,6 +556,7 @@ where name: Option, first_block_index: Option, fog_info: Option, + require_spend_subaddress: bool, ) -> Result { let view_account = get_view_only_account_keys().await?; @@ -583,6 +588,7 @@ where import_block_index, first_block_index, &default_public_address, + false, conn, )?) }) @@ -595,6 +601,7 @@ where first_block_index, None, true, + false, conn, )?) }), @@ -1248,6 +1255,7 @@ mod tests { None, None, None, + false, ) .unwrap(); diff --git a/full-service/src/service/address.rs b/full-service/src/service/address.rs index 6fd0b08a5..b09721a78 100644 --- a/full-service/src/service/address.rs +++ b/full-service/src/service/address.rs @@ -250,7 +250,14 @@ mod tests { // Create an account. let account = service - .import_view_only_account(&view_private_key, &spend_public_key, None, None, None) + .import_view_only_account( + &view_private_key, + &spend_public_key, + None, + None, + None, + false, + ) .unwrap(); assert_eq!(account.clone().next_subaddress_index(conn).unwrap(), 2); From 275d01279ba42d1d77f3d5a998dca22b688abafd Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 12:40:44 -0700 Subject: [PATCH 09/21] Update schema to include autogenerated comment Signed-off-by: sugargoat --- full-service/src/db/schema.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/full-service/src/db/schema.rs b/full-service/src/db/schema.rs index a8e39c6da..63e85cf4e 100644 --- a/full-service/src/db/schema.rs +++ b/full-service/src/db/schema.rs @@ -1,3 +1,4 @@ +// @generated automatically by Diesel CLI. diesel::table! { accounts (id) { id -> Text, From 7fb3593b2a945eac9ca8021a7e292c0ac658d325 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 12:41:44 -0700 Subject: [PATCH 10/21] Whitespace Signed-off-by: sugargoat --- full-service/src/db/schema.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/full-service/src/db/schema.rs b/full-service/src/db/schema.rs index 63e85cf4e..e1a9c9a5b 100644 --- a/full-service/src/db/schema.rs +++ b/full-service/src/db/schema.rs @@ -1,4 +1,5 @@ // @generated automatically by Diesel CLI. + diesel::table! { accounts (id) { id -> Text, From 43777b2170a57202b10a95229bc074fdb7bd88f3 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 13:15:50 -0700 Subject: [PATCH 11/21] Address case for spend even without require, use more proper error type, and clean up tests Signed-off-by: sugargoat --- full-service/src/error.rs | 2 +- .../build_and_submit_with_spend_subaddress.rs | 112 ++++++++---------- full-service/src/service/account.rs | 2 +- full-service/src/service/transaction.rs | 10 +- 4 files changed, 52 insertions(+), 74 deletions(-) diff --git a/full-service/src/error.rs b/full-service/src/error.rs index 368be1f80..0decd9eb1 100644 --- a/full-service/src/error.rs +++ b/full-service/src/error.rs @@ -314,7 +314,7 @@ pub enum WalletTransactionBuilderError { /// Error interacting with fog: {0} FogError(String), - /// Attempting to build a transaction from a TXO without a subaddress: {0} + /// Subaddress is required for spending on this account: {0} NullSubaddress(String), /// Error executing diesel transaction: {0} diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs index d5678c711..14be77e96 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs @@ -313,7 +313,7 @@ mod e2e_transaction { fn test_build_and_submit_transaction_with_require_spend_subaddress_mismatch_fails_if_set( logger: Logger, ) { - use crate::error::WalletTransactionBuilderError::InvalidArgument as transaction_error; + use crate::error::WalletTransactionBuilderError::NullSubaddress as transaction_error; let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); @@ -333,34 +333,29 @@ mod e2e_transaction { let account_id = account_obj.get("id").unwrap().as_str().unwrap(); let ( - (alice_public_address, alice_b58_public_address), - (bob_public_address, bob_b58_public_address), - (carol_public_address, carol_b58_public_address), - ) = [ - "Subaddress for Alice", - "Subaddress for Bob", - "Subaddress for Carol", - ] - .iter() - .map(|metadata| { - let body = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "assign_address_for_account", - "params": { - "account_id": account_id, - "metadata": metadata, - } - }); - let res = dispatch(&client, body, &logger); - let result = res.get("result").unwrap(); - let address = result.get("address").unwrap(); - let b58_address = address.get("public_address_b58").unwrap().as_str().unwrap(); - let public_address = b58_decode_public_address(b58_address).unwrap(); - (public_address, b58_address.to_string()) - }) - .collect_tuple() - .unwrap(); + (_alice_public_address, alice_b58_public_address), + (bob_public_address, _bob_b58_public_address), + ) = ["Subaddress for Alice", "Subaddress for Bob"] + .iter() + .map(|metadata| { + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "assign_address_for_account", + "params": { + "account_id": account_id, + "metadata": metadata, + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let address = result.get("address").unwrap(); + let b58_address = address.get("public_address_b58").unwrap().as_str().unwrap(); + let public_address = b58_decode_public_address(b58_address).unwrap(); + (public_address, b58_address.to_string()) + }) + .collect_tuple() + .unwrap(); // Add a block with a txo for Bob add_block_to_ledger_db( @@ -395,14 +390,13 @@ mod e2e_transaction { let details = data.get("details").unwrap(); assert!(details .to_string() - .contains(&transaction_error("This account is configured to spend only from a specific subaddress. Please provide a subaddress to spend from.".to_string()).to_string())); + .contains(&transaction_error("This account requires subaddresses be specified when spending. Please provide a subaddress to spend from.".to_string()).to_string())); } #[test_with_logger] fn test_build_and_submit_transaction_with_require_spend_subaddress_mismatch_fails_if_not_set( logger: Logger, ) { - use crate::error::WalletTransactionBuilderError::InvalidArgument as transaction_error; let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); @@ -421,34 +415,29 @@ mod e2e_transaction { let account_id = account_obj.get("id").unwrap().as_str().unwrap(); let ( - (alice_public_address, alice_b58_public_address), + (_alice_public_address, alice_b58_public_address), (bob_public_address, bob_b58_public_address), - (carol_public_address, carol_b58_public_address), - ) = [ - "Subaddress for Alice", - "Subaddress for Bob", - "Subaddress for Carol", - ] - .iter() - .map(|metadata| { - let body = json!({ - "jsonrpc": "2.0", - "id": 1, - "method": "assign_address_for_account", - "params": { - "account_id": account_id, - "metadata": metadata, - } - }); - let res = dispatch(&client, body, &logger); - let result = res.get("result").unwrap(); - let address = result.get("address").unwrap(); - let b58_address = address.get("public_address_b58").unwrap().as_str().unwrap(); - let public_address = b58_decode_public_address(b58_address).unwrap(); - (public_address, b58_address.to_string()) - }) - .collect_tuple() - .unwrap(); + ) = ["Subaddress for Alice", "Subaddress for Bob"] + .iter() + .map(|metadata| { + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "assign_address_for_account", + "params": { + "account_id": account_id, + "metadata": metadata, + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let address = result.get("address").unwrap(); + let b58_address = address.get("public_address_b58").unwrap().as_str().unwrap(); + let public_address = b58_decode_public_address(b58_address).unwrap(); + (public_address, b58_address.to_string()) + }) + .collect_tuple() + .unwrap(); // Add a block with a txo for Bob add_block_to_ledger_db( @@ -479,11 +468,6 @@ mod e2e_transaction { } }); 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(&transaction_error("This account is not configured to spend only from a specific subaddress. Please do not provide a subaddress to spend from.".to_string()).to_string())); + assert!(res.get("result").is_some()); } } diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index e62dac849..8ea4cccf5 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -588,7 +588,7 @@ where import_block_index, first_block_index, &default_public_address, - false, + require_spend_subaddress, conn, )?) }) diff --git a/full-service/src/service/transaction.rs b/full-service/src/service/transaction.rs index 093671cbd..9bd5fffdc 100644 --- a/full-service/src/service/transaction.rs +++ b/full-service/src/service/transaction.rs @@ -471,14 +471,8 @@ where if Account::get(&AccountID(account_id_hex.to_string()), conn)?.require_spend_subaddress { if spend_subaddress.is_none() { - return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( - "This account is configured to spend only from a specific subaddress. Please provide a subaddress to spend from.".to_string() - ))); - } - } else { - if spend_subaddress.is_some() { - return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::InvalidArgument( - "This account is not configured to spend only from a specific subaddress. Please do not provide a subaddress to spend from.".to_string() + return Err(TransactionServiceError::TransactionBuilder(WalletTransactionBuilderError::NullSubaddress( + "This account requires subaddresses be specified when spending. Please provide a subaddress to spend from.".to_string() ))); } } From 73fe096fd442e9d6a0cc878b1d7459fb075c39c6 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 13:23:03 -0700 Subject: [PATCH 12/21] Minor cleanups Signed-off-by: sugargoat --- .../build_submit/build_and_submit_with_spend_subaddress.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs index 14be77e96..ee5c6b56b 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs @@ -182,7 +182,7 @@ mod e2e_transaction { "params": { "account_id": account_id, "recipient_public_address": alice_b58_public_address, - "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB + "amount": { "value": (42 * MOB).to_string(), "token_id": "0" }, "spend_subaddress": bob_b58_public_address, } }); @@ -381,7 +381,7 @@ mod e2e_transaction { "params": { "account_id": account_id, "recipient_public_address": alice_b58_public_address, - "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB + "amount": { "value": (42 * MOB).to_string(), "token_id": "0" }, } }); let res = dispatch(&client, body, &logger); @@ -463,7 +463,7 @@ mod e2e_transaction { "params": { "account_id": account_id, "recipient_public_address": alice_b58_public_address, - "amount": { "value": "42000000000000", "token_id": "0" }, // 42.0 MOB + "amount": { "value": (42 * MOB).to_string(), "token_id": "0" }, "spend_subaddress": bob_b58_public_address, } }); From c3d748db57ac80153cfd7ac95dc5c1a6669e605b Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 13:24:16 -0700 Subject: [PATCH 13/21] Update test name for accuracy Signed-off-by: sugargoat --- .../build_submit/build_and_submit_with_spend_subaddress.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs index ee5c6b56b..5b99ab5f7 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs @@ -394,7 +394,7 @@ mod e2e_transaction { } #[test_with_logger] - fn test_build_and_submit_transaction_with_require_spend_subaddress_mismatch_fails_if_not_set( + fn test_build_and_submit_without_require_spend_subaddress_allows_spending_from_subaddress( logger: Logger, ) { let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); From adbdb0f35ce01b6d894377bd14baa5c92d46ae7b Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 15:03:51 -0700 Subject: [PATCH 14/21] Add "enable/disable" Signed-off-by: sugargoat --- full-service/src/db/account.rs | 32 +++++++++- full-service/src/json_rpc/v2/api/request.rs | 6 ++ full-service/src/json_rpc/v2/api/response.rs | 6 ++ full-service/src/json_rpc/v2/api/wallet.rs | 40 +++++++++++++ .../e2e_tests/transaction/build_submit/mod.rs | 2 +- ...pend_subaddress.rs => spend_subaddress.rs} | 60 +++++++++++++++++++ full-service/src/service/account.rs | 27 +++++++++ 7 files changed, 171 insertions(+), 2 deletions(-) rename full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/{build_and_submit_with_spend_subaddress.rs => spend_subaddress.rs} (90%) diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index 7cbbf1dfb..a00b38981 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -316,7 +316,7 @@ pub trait AccountModel { ) -> Result, WalletDbError>; /// Update the account name for current account. - /// * The only updatable field is the name. Any other desired update requires adding a new account, and deleting the existing if desired. + /// * The only updatable fields are the name and require_spend_subaddress. Any other desired update requires adding a new account, and deleting the existing if desired. /// /// # Arguments ///| Name | Purpose | Notes | @@ -332,6 +332,23 @@ pub trait AccountModel { conn: Conn ) -> Result<(), WalletDbError>; + /// Update the account's require_spend_subaddress mode. + /// * The only updatable fields are the name and require_spend_subaddress. Any other desired update requires adding a new account, and deleting the existing if desired. + /// + /// # Arguments + ///| Name | Purpose | Notes | + ///|------------|----------------------------------------------------------|-------| + ///| `require_spend_subaddress` | The new account name used to perform this update action. | | + ///| `conn` | An reference to the pool connection of wallet database | | + /// + /// # Returns: + /// * unit + fn update_require_spend_subaddress( + &self, + require_spend_subaddress: bool, + conn: Conn, + ) -> Result<(), WalletDbError>; + /// Update the next block index in current account that needs to sync. /// /// # Arguments @@ -831,6 +848,19 @@ impl AccountModel for Account { Ok(()) } + fn update_require_spend_subaddress( + &self, + require_spend_subaddress: bool, + conn: Conn, + ) -> Result<(), WalletDbError> { + use crate::db::schema::accounts; + + diesel::update(accounts::table.filter(accounts::id.eq(&self.id))) + .set(accounts::require_spend_subaddress.eq(require_spend_subaddress)) + .execute(conn)?; + Ok(()) + } + fn update_next_block_index( &self, next_block_index: u64, diff --git a/full-service/src/json_rpc/v2/api/request.rs b/full-service/src/json_rpc/v2/api/request.rs index 6b1c9572d..e48cf4ca5 100644 --- a/full-service/src/json_rpc/v2/api/request.rs +++ b/full-service/src/json_rpc/v2/api/request.rs @@ -139,6 +139,12 @@ pub enum JsonCommandRequest { create_view_only_account_sync_request { account_id: String, }, + disable_require_spend_subaddress { + account_id: String, + }, + enable_require_spend_subaddress { + account_id: String, + }, export_account_secrets { account_id: String, }, diff --git a/full-service/src/json_rpc/v2/api/response.rs b/full-service/src/json_rpc/v2/api/response.rs index 9367a1c07..8c565c048 100644 --- a/full-service/src/json_rpc/v2/api/response.rs +++ b/full-service/src/json_rpc/v2/api/response.rs @@ -87,6 +87,12 @@ pub enum JsonCommandResponse { create_view_only_account_sync_request { txo_sync_request: TxoSyncReq, }, + disable_require_spend_subaddress { + account: Account, + }, + enable_require_spend_subaddress { + account: Account, + }, export_account_secrets { account_secrets: AccountSecrets, }, diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index 3c220b0b4..8e5adc838 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -616,6 +616,46 @@ where JsonCommandResponse::create_view_only_account_sync_request { txo_sync_request } } + JsonCommandRequest::disable_require_spend_subaddress { account_id } => { + let account_id = AccountID(account_id); + let account = service + .update_require_spend_subaddress(&account_id, false) + .map_err(format_error)?; + let next_subaddress_index = service + .get_next_subaddress_index_for_account(&AccountID(account.id.clone())) + .map_err(format_error)?; + let main_public_address: mc_account_keys::PublicAddress = (&service + .get_address_for_account( + &account.id.clone().into(), + DEFAULT_SUBADDRESS_INDEX as i64, + ) + .map_err(format_error)?) + .try_into() + .map_err(format_error)?; + let account = Account::new(&account, &main_public_address, next_subaddress_index) + .map_err(format_error)?; + JsonCommandResponse::enable_require_spend_subaddress { account } + } + JsonCommandRequest::enable_require_spend_subaddress { account_id } => { + let account_id = AccountID(account_id); + let account = service + .update_require_spend_subaddress(&account_id, true) + .map_err(format_error)?; + let next_subaddress_index = service + .get_next_subaddress_index_for_account(&AccountID(account.id.clone())) + .map_err(format_error)?; + let main_public_address: mc_account_keys::PublicAddress = (&service + .get_address_for_account( + &account.id.clone().into(), + DEFAULT_SUBADDRESS_INDEX as i64, + ) + .map_err(format_error)?) + .try_into() + .map_err(format_error)?; + let account = Account::new(&account, &main_public_address, next_subaddress_index) + .map_err(format_error)?; + JsonCommandResponse::enable_require_spend_subaddress { account } + } JsonCommandRequest::export_account_secrets { account_id } => { let account = service .get_account(&AccountID(account_id)) diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs index 2d471178b..109641134 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/mod.rs @@ -1,6 +1,6 @@ mod build_and_submit; -mod build_and_submit_with_spend_subaddress; mod build_then_submit; mod build_unsigned; mod large_transaction; mod multiple_outlay; +mod spend_subaddress; diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs similarity index 90% rename from full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs rename to full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs index 5b99ab5f7..d6f4f8d0a 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/build_and_submit_with_spend_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs @@ -470,4 +470,64 @@ mod e2e_transaction { let res = dispatch(&client, body, &logger); assert!(res.get("result").is_some()); } + + #[test_with_logger] + fn test_enable_and_disable_require_spend_subaddress(logger: Logger) { + let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); + let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); + + // Add an account + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "create_account", + "params": { + "name": "Exchange Main Account", + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + let account_id = account_obj.get("id").unwrap().as_str().unwrap(); + + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "enable_require_spend_subaddress", + "params": { + "account_id": account_id.to_string(), + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + assert_eq!( + account_obj + .get("require_spend_subaddress") + .unwrap() + .as_bool() + .unwrap(), + true + ); + + let body = json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "disable_require_spend_subaddress", + "params": { + "account_id": account_id.to_string(), + } + }); + let res = dispatch(&client, body, &logger); + let result = res.get("result").unwrap(); + let account_obj = result.get("account").unwrap(); + assert_eq!( + account_obj + .get("require_spend_subaddress") + .unwrap() + .as_bool() + .unwrap(), + false + ); + } } diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index 8ea4cccf5..4200012b5 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -336,6 +336,21 @@ pub trait AccountService { name: String, ) -> Result; + /// Update the require_spend_subaddress field for an account. + /// + /// # Arguments + /// + ///| Name | Purpose | Notes | + ///|--------------|----------------------------------------------|-----------------------------------| + ///| `account_id` | The account on which to perform this action. | Account must exist in the wallet. | + ///| `require_spend_subaddress` | Whether to enable require_spend_subaddress mode | | + /// + fn update_require_spend_subaddress( + &self, + account_id: &AccountID, + require_spend_subaddress: bool, + ) -> Result; + /// complete a sync request for a view only account /// /// # Arguments @@ -695,6 +710,18 @@ where Ok(Account::get(account_id, conn)?) } + fn update_require_spend_subaddress( + &self, + account_id: &AccountID, + require_spend_subaddress: bool, + ) -> Result { + let mut pooled_conn = self.get_pooled_conn()?; + let conn = pooled_conn.deref_mut(); + Account::get(account_id, conn)? + .update_require_spend_subaddress(require_spend_subaddress, conn)?; + Ok(Account::get(account_id, conn)?) + } + fn sync_account( &self, account_id: &AccountID, From 2b0d3a38acb5ab1371b56efa4d7c6a200f5d28af Mon Sep 17 00:00:00 2001 From: Henry Holtzman Date: Wed, 22 May 2024 15:46:35 -0700 Subject: [PATCH 15/21] add require_spend_subaddress to AccountMap Account model --- full-service/src/json_rpc/v2/models/account.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/full-service/src/json_rpc/v2/models/account.rs b/full-service/src/json_rpc/v2/models/account.rs index 0b9dfe330..e8427d7fa 100644 --- a/full-service/src/json_rpc/v2/models/account.rs +++ b/full-service/src/json_rpc/v2/models/account.rs @@ -59,6 +59,11 @@ pub struct Account { /// A flag that indicates if this account's private spend key is managed by /// a hardware wallet. pub managed_by_hardware_wallet: bool, + + /// A flag that indicates that the account requires a spend_subaddress be + /// specified when building a transaction in order to keep subaddress balances + /// correct. + pub require_spend_subaddress: bool, } impl Account { @@ -82,6 +87,7 @@ impl Account { fog_enabled: src.fog_enabled, view_only: src.view_only, managed_by_hardware_wallet: src.managed_by_hardware_wallet, + require_spend_subaddress: src.require_spend_subaddress, }) } } From 049657f96d3a0ba1cdd9cc4ba9b71ece3a57cff8 Mon Sep 17 00:00:00 2001 From: Henry Holtzman Date: Wed, 22 May 2024 16:25:00 -0700 Subject: [PATCH 16/21] update AccountSecrets model to expose require_from_subaddress when exporting --- full-service/src/json_rpc/v2/models/account.rs | 4 ++-- full-service/src/json_rpc/v2/models/account_secrets.rs | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/full-service/src/json_rpc/v2/models/account.rs b/full-service/src/json_rpc/v2/models/account.rs index e8427d7fa..ce83d0166 100644 --- a/full-service/src/json_rpc/v2/models/account.rs +++ b/full-service/src/json_rpc/v2/models/account.rs @@ -61,8 +61,8 @@ pub struct Account { pub managed_by_hardware_wallet: bool, /// A flag that indicates that the account requires a spend_subaddress be - /// specified when building a transaction in order to keep subaddress balances - /// correct. + /// specified when building a transaction in order to keep subaddress + /// balances correct. pub require_spend_subaddress: bool, } diff --git a/full-service/src/json_rpc/v2/models/account_secrets.rs b/full-service/src/json_rpc/v2/models/account_secrets.rs index 3d9747c66..7d30b7480 100644 --- a/full-service/src/json_rpc/v2/models/account_secrets.rs +++ b/full-service/src/json_rpc/v2/models/account_secrets.rs @@ -42,6 +42,11 @@ pub struct AccountSecrets { /// Private keys for receiving and spending MobileCoin. #[serde(serialize_with = "expose_secret")] pub view_account_key: Secret>, + + /// Indicates that the account requires a spend_subaddress be + /// specified when building a transaction in order to keep subaddress + /// balances correct. + pub require_spend_subaddress: bool, } impl TryFrom<&Account> for AccountSecrets { @@ -62,6 +67,7 @@ impl TryFrom<&Account> for AccountSecrets { key_derivation_version: src.key_derivation_version.to_string(), account_key: Secret::new(None), view_account_key: Secret::new(Some(ViewAccountKey::from(&view_account_key))), + require_spend_subaddress: src.require_spend_subaddress, }) } else { let account_key: mc_account_keys::AccountKey = mc_util_serial::decode(&src.account_key) @@ -99,6 +105,7 @@ impl TryFrom<&Account> for AccountSecrets { }, )?)), view_account_key: Secret::new(None), + require_spend_subaddress: src.require_spend_subaddress, }) } } From a672fe8368b2d8bfff8c0d887b0d9c1576fdc034 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 16:10:48 -0700 Subject: [PATCH 17/21] Debugging test Signed-off-by: sugargoat --- .../e2e_tests/transaction/build_submit/spend_subaddress.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs index d6f4f8d0a..5746da490 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs @@ -15,7 +15,7 @@ mod e2e_transaction { util::b58::b58_decode_public_address, }; - use mc_common::logger::{test_with_logger, Logger}; + use mc_common::logger::{log, test_with_logger, Logger}; use mc_ledger_db::Ledger; use mc_rand::rand_core::RngCore; use mc_transaction_core::{ring_signature::KeyImage, tokens::Mob, Token}; @@ -474,7 +474,7 @@ mod e2e_transaction { #[test_with_logger] fn test_enable_and_disable_require_spend_subaddress(logger: Logger) { let mut rng: StdRng = SeedableRng::from_seed([3u8; 32]); - let (client, mut ledger_db, db_ctx, _network_state) = setup(&mut rng, logger.clone()); + let (client, _ledger_db, _db_ctx, _network_state) = setup(&mut rng, logger.clone()); // Add an account let body = json!({ @@ -501,6 +501,7 @@ mod e2e_transaction { let res = dispatch(&client, body, &logger); let result = res.get("result").unwrap(); let account_obj = result.get("account").unwrap(); + log::info!(logger, "account_obj: {:?}", account_obj); assert_eq!( account_obj .get("require_spend_subaddress") From 585ccf995237cb072f020f68353bb68f84924ad5 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 16:29:35 -0700 Subject: [PATCH 18/21] Rather than enable/disable: set Signed-off-by: sugargoat --- full-service/src/json_rpc/v2/api/request.rs | 10 ++- full-service/src/json_rpc/v2/api/response.rs | 9 +-- full-service/src/json_rpc/v2/api/wallet.rs | 63 +++++++------------ .../build_submit/spend_subaddress.rs | 6 +- 4 files changed, 34 insertions(+), 54 deletions(-) diff --git a/full-service/src/json_rpc/v2/api/request.rs b/full-service/src/json_rpc/v2/api/request.rs index e48cf4ca5..383df7928 100644 --- a/full-service/src/json_rpc/v2/api/request.rs +++ b/full-service/src/json_rpc/v2/api/request.rs @@ -139,12 +139,6 @@ pub enum JsonCommandRequest { create_view_only_account_sync_request { account_id: String, }, - disable_require_spend_subaddress { - account_id: String, - }, - enable_require_spend_subaddress { - account_id: String, - }, export_account_secrets { account_id: String, }, @@ -271,6 +265,10 @@ pub enum JsonCommandRequest { search_ledger { query: String, }, + set_require_spend_subaddress { + account_id: String, + require_spend_subaddress: bool, + }, submit_transaction { tx_proposal: TxProposal, comment: Option, diff --git a/full-service/src/json_rpc/v2/api/response.rs b/full-service/src/json_rpc/v2/api/response.rs index 8c565c048..3336cf703 100644 --- a/full-service/src/json_rpc/v2/api/response.rs +++ b/full-service/src/json_rpc/v2/api/response.rs @@ -87,12 +87,6 @@ pub enum JsonCommandResponse { create_view_only_account_sync_request { txo_sync_request: TxoSyncReq, }, - disable_require_spend_subaddress { - account: Account, - }, - enable_require_spend_subaddress { - account: Account, - }, export_account_secrets { account_secrets: AccountSecrets, }, @@ -206,6 +200,9 @@ pub enum JsonCommandResponse { search_ledger { results: Vec, }, + set_require_spend_subaddress { + account: Account, + }, submit_transaction { transaction_log: Option, }, diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index 8e5adc838..619a85433 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -616,46 +616,6 @@ where JsonCommandResponse::create_view_only_account_sync_request { txo_sync_request } } - JsonCommandRequest::disable_require_spend_subaddress { account_id } => { - let account_id = AccountID(account_id); - let account = service - .update_require_spend_subaddress(&account_id, false) - .map_err(format_error)?; - let next_subaddress_index = service - .get_next_subaddress_index_for_account(&AccountID(account.id.clone())) - .map_err(format_error)?; - let main_public_address: mc_account_keys::PublicAddress = (&service - .get_address_for_account( - &account.id.clone().into(), - DEFAULT_SUBADDRESS_INDEX as i64, - ) - .map_err(format_error)?) - .try_into() - .map_err(format_error)?; - let account = Account::new(&account, &main_public_address, next_subaddress_index) - .map_err(format_error)?; - JsonCommandResponse::enable_require_spend_subaddress { account } - } - JsonCommandRequest::enable_require_spend_subaddress { account_id } => { - let account_id = AccountID(account_id); - let account = service - .update_require_spend_subaddress(&account_id, true) - .map_err(format_error)?; - let next_subaddress_index = service - .get_next_subaddress_index_for_account(&AccountID(account.id.clone())) - .map_err(format_error)?; - let main_public_address: mc_account_keys::PublicAddress = (&service - .get_address_for_account( - &account.id.clone().into(), - DEFAULT_SUBADDRESS_INDEX as i64, - ) - .map_err(format_error)?) - .try_into() - .map_err(format_error)?; - let account = Account::new(&account, &main_public_address, next_subaddress_index) - .map_err(format_error)?; - JsonCommandResponse::enable_require_spend_subaddress { account } - } JsonCommandRequest::export_account_secrets { account_id } => { let account = service .get_account(&AccountID(account_id)) @@ -1400,6 +1360,29 @@ where results: results.iter().map(Into::into).collect(), } } + JsonCommandRequest::set_require_spend_subaddress { + account_id, + require_spend_subaddress, + } => { + let account_id = AccountID(account_id); + let account = service + .update_require_spend_subaddress(&account_id, require_spend_subaddress) + .map_err(format_error)?; + let next_subaddress_index = service + .get_next_subaddress_index_for_account(&AccountID(account.id.clone())) + .map_err(format_error)?; + let main_public_address: mc_account_keys::PublicAddress = (&service + .get_address_for_account( + &account.id.clone().into(), + DEFAULT_SUBADDRESS_INDEX as i64, + ) + .map_err(format_error)?) + .try_into() + .map_err(format_error)?; + let account = Account::new(&account, &main_public_address, next_subaddress_index) + .map_err(format_error)?; + JsonCommandResponse::set_require_spend_subaddress { account } + } JsonCommandRequest::submit_transaction { tx_proposal, comment, diff --git a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs index 5746da490..b8e749de3 100644 --- a/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs +++ b/full-service/src/json_rpc/v2/e2e_tests/transaction/build_submit/spend_subaddress.rs @@ -493,9 +493,10 @@ mod e2e_transaction { let body = json!({ "jsonrpc": "2.0", "id": 1, - "method": "enable_require_spend_subaddress", + "method": "set_require_spend_subaddress", "params": { "account_id": account_id.to_string(), + "require_spend_subaddress": true, } }); let res = dispatch(&client, body, &logger); @@ -514,9 +515,10 @@ mod e2e_transaction { let body = json!({ "jsonrpc": "2.0", "id": 1, - "method": "disable_require_spend_subaddress", + "method": "set_require_spend_subaddress", "params": { "account_id": account_id.to_string(), + "require_spend_subaddress": false, } }); let res = dispatch(&client, body, &logger); From 8f1e4ab7aa6c73895bb4e752965b79f8a9a1fdeb Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 16:58:34 -0700 Subject: [PATCH 19/21] Expose `import` endpoints Signed-off-by: sugargoat --- full-service/src/json_rpc/v2/api/request.rs | 4 ++++ full-service/src/json_rpc/v2/api/wallet.rs | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/full-service/src/json_rpc/v2/api/request.rs b/full-service/src/json_rpc/v2/api/request.rs index 383df7928..5890d64fb 100644 --- a/full-service/src/json_rpc/v2/api/request.rs +++ b/full-service/src/json_rpc/v2/api/request.rs @@ -246,11 +246,15 @@ pub enum JsonCommandRequest { name: Option, first_block_index: Option, next_subaddress_index: Option, + #[serde(default = "bool::default")] // default is false + require_spend_subaddress: bool, }, import_view_only_account_from_hardware_wallet { name: Option, first_block_index: Option, fog_info: Option, + #[serde(default = "bool::default")] // default is false + require_spend_subaddress: bool, }, remove_account { account_id: String, diff --git a/full-service/src/json_rpc/v2/api/wallet.rs b/full-service/src/json_rpc/v2/api/wallet.rs index 619a85433..a46fea985 100644 --- a/full-service/src/json_rpc/v2/api/wallet.rs +++ b/full-service/src/json_rpc/v2/api/wallet.rs @@ -1216,6 +1216,7 @@ where name, first_block_index, next_subaddress_index, + require_spend_subaddress, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1245,7 +1246,7 @@ where name, fb, ns, - false, + require_spend_subaddress, ) .map_err(format_error)?; let next_subaddress_index = service @@ -1268,6 +1269,7 @@ where name, first_block_index, fog_info, + require_spend_subaddress, } => { let fb = first_block_index .map(|fb| fb.parse::()) @@ -1275,7 +1277,12 @@ where .map_err(format_error)?; let account = service - .import_view_only_account_from_hardware_wallet(name, fb, fog_info, false) + .import_view_only_account_from_hardware_wallet( + name, + fb, + fog_info, + require_spend_subaddress, + ) .await .map_err(format_error)?; From d86640b7701f1096772f8903202e37ef07bff3e6 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 17:02:02 -0700 Subject: [PATCH 20/21] Fixup import_view_only_account request create Signed-off-by: sugargoat --- full-service/src/service/account.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/full-service/src/service/account.rs b/full-service/src/service/account.rs index 4200012b5..c9279b873 100644 --- a/full-service/src/service/account.rs +++ b/full-service/src/service/account.rs @@ -652,7 +652,8 @@ where spend_public_key: hex::encode(spend_public_key.to_bytes()), name: Some(account.name.clone()), first_block_index: Some(account.first_block_index.to_string()), - next_subaddress_index: Some(account.next_subaddress_index(conn)?.to_string()), + next_subaddress_index: Some(account.clone().next_subaddress_index(conn)?.to_string()), + require_spend_subaddress: account.require_spend_subaddress, }; let src_json: serde_json::Value = serde_json::json!(json_command_request); From c416c747fd2c87d01830c8beda8acb6305836912 Mon Sep 17 00:00:00 2001 From: sugargoat Date: Wed, 22 May 2024 17:22:43 -0700 Subject: [PATCH 21/21] Fix clippy Signed-off-by: sugargoat --- full-service/src/db/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/full-service/src/db/account.rs b/full-service/src/db/account.rs index a00b38981..9c8d4aede 100644 --- a/full-service/src/db/account.rs +++ b/full-service/src/db/account.rs @@ -583,7 +583,7 @@ impl AccountModel for Account { fog_enabled, view_only: false, managed_by_hardware_wallet: false, - require_spend_subaddress: require_spend_subaddress, + require_spend_subaddress, }; diesel::insert_into(accounts::table)