diff --git a/src/cpp/wallet/py_monero_wallet_model.cpp b/src/cpp/wallet/py_monero_wallet_model.cpp index a75547f..1fa4b67 100644 --- a/src/cpp/wallet/py_monero_wallet_model.cpp +++ b/src/cpp/wallet/py_monero_wallet_model.cpp @@ -1200,14 +1200,21 @@ std::shared_ptr monero_get_payment_uri::to_tx_config() } rapidjson::Value monero_get_payment_uri::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); - rapidjson::Value value_num(rapidjson::kNumberType); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); - if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); if (m_recipient_name != boost::none) monero_utils::add_json_member("recipient_name", m_recipient_name.get(), allocator, root, value_str); if (m_tx_description != boost::none) monero_utils::add_json_member("tx_description", m_tx_description.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + + // return root return root; } @@ -1225,10 +1232,15 @@ void monero_key_value::from_property_tree(const boost::property_tree::ptree& nod } rapidjson::Value monero_key_value::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_key != boost::none) monero_utils::add_json_member("key", m_key.get(), allocator, root, value_str); if (m_value != boost::none) monero_utils::add_json_member("value", m_value.get(), allocator, root, value_str); + + // return root return root; } @@ -1253,12 +1265,21 @@ monero_get_balance_params::monero_get_balance_params(uint32_t account_idx, boost } rapidjson::Value monero_get_balance_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set number values rapidjson::Value value_num(rapidjson::kNumberType); if (m_account_idx != boost::none) monero_utils::add_json_member("account_index", m_account_idx.get(), allocator, root, value_num); - if (!m_address_indices.empty()) root.AddMember("address_indices", monero_utils::to_rapidjson_val(allocator, m_address_indices), allocator); + + // set bool values if (m_all_accounts != boost::none) monero_utils::add_json_member("all_accounts", m_all_accounts.get(), allocator, root); if (m_strict != boost::none) monero_utils::add_json_member("strict", m_strict.get(), allocator, root); + + // set sub-arrays + if (!m_address_indices.empty()) root.AddMember("address_indices", monero_utils::to_rapidjson_val(allocator, m_address_indices), allocator); + + // return root return root; } @@ -1271,18 +1292,17 @@ monero_import_export_key_images_params::monero_import_export_key_images_params(c } rapidjson::Value monero_import_export_key_images_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value val_str(rapidjson::kStringType); if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); - else if (m_key_images.size() > 0) { - rapidjson::Value value_arr(rapidjson::kArrayType); - for (const auto &key_image : m_key_images) { - value_arr.PushBack(key_image->to_rapidjson_val(allocator), allocator); - } - root.AddMember("signed_key_images", value_arr, allocator); - return root; - } + // set sub-arrays + if (m_all == boost::none && m_key_images.size() > 0) root.AddMember("signed_key_images", monero_utils::to_rapidjson_val(allocator, m_key_images), allocator); + + // return root return root; } @@ -1303,24 +1323,33 @@ monero_sweep_params::monero_sweep_params(const monero_tx_config& config): } rapidjson::Value monero_sweep_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value val_str(rapidjson::kStringType); - rapidjson::Value val_num(rapidjson::kNumberType); + // set string values + rapidjson::Value val_str(rapidjson::kStringType); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, val_str); - if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, val_num); - if (m_subaddr_indices.size() > 0) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); if (m_key_image != boost::none) monero_utils::add_json_member("key_image", m_key_image.get(), allocator, root, val_str); - if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, val_num); if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, val_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, val_num); + if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, val_num); + if (m_below_amount != boost::none) monero_utils::add_json_member("below_amount", m_below_amount.get(), allocator, root, val_num); + + // set bool values if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); - if (m_below_amount != boost::none) monero_utils::add_json_member("below_amount", m_below_amount.get(), allocator, root, val_num); - bool relay = bool_equals_2(true, m_relay); monero_utils::add_json_member("do_not_relay", !relay, allocator, root); + + // set sub-arrays + if (m_subaddr_indices.size() > 0) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); + + // return root return root; } @@ -1373,228 +1402,345 @@ monero_transfer_params::monero_transfer_params(const monero::monero_tx_config &c } rapidjson::Value monero_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_num(rapidjson::kNumberType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); - if (!m_subtract_fee_from_outputs.empty()) root.AddMember("subtract_fee_from_outputs", monero_utils::to_rapidjson_val(allocator, m_subtract_fee_from_outputs), allocator); - if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); - if (!m_subaddress_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); - if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); if (m_priority != boost::none) monero_utils::add_json_member("priority", m_priority.get(), allocator, root, value_num); + + // set bool values + if (m_do_not_relay != boost::none) monero_utils::add_json_member("do_not_relay", m_do_not_relay.get(), allocator, root); if (m_get_tx_hex != boost::none) monero_utils::add_json_member("get_tx_hex", m_get_tx_hex.get(), allocator, root); if (m_get_tx_metadata != boost::none) monero_utils::add_json_member("get_tx_metadata", m_get_tx_metadata.get(), allocator, root); if (m_get_tx_keys != boost::none) monero_utils::add_json_member("get_tx_keys", m_get_tx_keys.get(), allocator, root); if (m_get_tx_key != boost::none) monero_utils::add_json_member("get_tx_key", m_get_tx_key.get(), allocator, root); - if (!m_destinations.empty()) { - rapidjson::Value value_arr(rapidjson::kArrayType); - for (const auto &dest : m_destinations) { - value_arr.PushBack(dest->to_rapidjson_val(allocator), allocator); - } + // set sub-arrays + if (!m_subtract_fee_from_outputs.empty()) root.AddMember("subtract_fee_from_outputs", monero_utils::to_rapidjson_val(allocator, m_subtract_fee_from_outputs), allocator); + if (!m_subaddress_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + if (!m_destinations.empty()) root.AddMember("destinations", monero_utils::to_rapidjson_val(allocator, m_destinations), allocator); - root.AddMember("destinations", value_arr, allocator); - } + // return root return root; } // --------------------------- MONERO MULTISIG TX DATA PARAMS --------------------------- rapidjson::Value monero_multisig_tx_data_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_multisig_tx_hex != boost::none) monero_utils::add_json_member("tx_data_hex", m_multisig_tx_hex.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO QUERY KEY PARAMS --------------------------- rapidjson::Value monero_query_key_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_key_type != boost::none) monero_utils::add_json_member("key_type", m_key_type.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO QUERY OUTPUT PARAMS --------------------------- rapidjson::Value monero_query_output_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_key_image != boost::none) monero_utils::add_json_member("key_image", m_key_image.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO GET ADDRESS PARAMS --------------------------- rapidjson::Value monero_get_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set number values rapidjson::Value value_num(rapidjson::kNumberType); - if (!m_subaddress_indices.empty()) root.AddMember("address_index", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + + // set sub-arrays + if (!m_subaddress_indices.empty()) root.AddMember("address_index", monero_utils::to_rapidjson_val(allocator, m_subaddress_indices), allocator); + + // return root return root; } // --------------------------- MONERO GET ADDRESS INDEX PARAMS --------------------------- rapidjson::Value monero_get_address_index_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO MAKE INTEGRATED ADDRESS PARAMS --------------------------- rapidjson::Value monero_make_integrated_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_standard_address != boost::none) monero_utils::add_json_member("standard_address", m_standard_address.get(), allocator, root, value_str); if (m_payment_id != boost::none) monero_utils::add_json_member("payment_id", m_payment_id.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO SPLIT INTEGRATED ADDRESS PARAMS --------------------------- rapidjson::Value monero_split_integrated_address_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_integrated_address != boost::none) monero_utils::add_json_member("integrated_address", m_integrated_address.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO PREPARE MULTISIG PARAMS --------------------------- rapidjson::Value monero_prepare_multisig_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set bool values if (m_enable_multisig_experimental != boost::none) monero_utils::add_json_member("enable_multisig_experimental", m_enable_multisig_experimental.get(), allocator, root); + + // return root return root; } // --------------------------- MONERO IMPORT MULTISIG HEX PARAMS --------------------------- rapidjson::Value monero_import_multisig_hex_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set sub-arrays if (!m_multisig_hexes.empty()) root.AddMember("info", monero_utils::to_rapidjson_val(allocator, m_multisig_hexes), allocator); + + // return root return root; } // --------------------------- MONERO MAKE MULTISIG PARAMS --------------------------- rapidjson::Value monero_make_multisig_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value val_num(rapidjson::kNumberType); + + // set string values rapidjson::Value val_str(rapidjson::kStringType); - if (!m_multisig_info.empty()) root.AddMember("multisig_info", monero_utils::to_rapidjson_val(allocator, m_multisig_info), allocator); - if (m_threshold != boost::none) monero_utils::add_json_member("threshold", m_threshold.get(), allocator, root, val_num); if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, val_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); + if (m_threshold != boost::none) monero_utils::add_json_member("threshold", m_threshold.get(), allocator, root, val_num); + + // set sub-arrays + if (!m_multisig_info.empty()) root.AddMember("multisig_info", monero_utils::to_rapidjson_val(allocator, m_multisig_info), allocator); + + // return root return root; } // --------------------------- MONERO PARSE PAYMENT URI PARAMS --------------------------- rapidjson::Value monero_parse_payment_uri_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value val_str(rapidjson::kStringType); if (m_uri != boost::none) monero_utils::add_json_member("uri", m_uri.get(), allocator, root, val_str); + + // return root return root; } // --------------------------- MONERO CREATE ACCOUNT PARAMS --------------------------- rapidjson::Value monero_create_account_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_tag != boost::none) monero_utils::add_json_member("label", m_tag.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO CLOSE WALLET PARAMS --------------------------- rapidjson::Value monero_close_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set bool values if (m_save != boost::none) monero_utils::add_json_member("autosave_current", m_save.get(), allocator, root); + + // return root return root; } // --------------------------- MONERO CHANGE WALLET PASSWORD PARAMS --------------------------- rapidjson::Value monero_change_wallet_password_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_old_password != boost::none) monero_utils::add_json_member("old_password", m_old_password.get(), allocator, root, value_str); if (m_new_password != boost::none) monero_utils::add_json_member("new_password", m_new_password.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO SET DAEMON PARAMS --------------------------- rapidjson::Value monero_set_daemon_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); if (m_username != boost::none) monero_utils::add_json_member("username", m_username.get(), allocator, root, value_str); if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, value_str); - if (m_trusted != boost::none) monero_utils::add_json_member("trusted", m_trusted.get(), allocator, root); if (m_ssl_support != boost::none) monero_utils::add_json_member("ssl_support", m_ssl_support.get(), allocator, root, value_str); - if (m_ssl_private_key_path != boost::none) monero_utils::add_json_member("ssl_private_key_path", m_ssl_private_key_path.get(), allocator, root, value_str); - if (m_ssl_certificate_path != boost::none) monero_utils::add_json_member("ssl_certificate_path", m_ssl_certificate_path.get(), allocator, root, value_str); - if (m_ssl_ca_file != boost::none) monero_utils::add_json_member("ssl_ca_file", m_ssl_ca_file.get(), allocator, root, value_str); - if (!m_ssl_allowed_fingerprints.empty()) root.AddMember("ssl_allowed_fingerprints", monero_utils::to_rapidjson_val(allocator, m_ssl_allowed_fingerprints), allocator); - if (m_ssl_allow_any_cert != boost::none) monero_utils::add_json_member("ssl_allow_any_cert", m_ssl_allow_any_cert.get(), allocator, root); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_private_key_path != boost::none) monero_utils::add_json_member("ssl_private_key_path", m_ssl_options->m_ssl_private_key_path.get(), allocator, root, value_str); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_certificate_path != boost::none) monero_utils::add_json_member("ssl_certificate_path", m_ssl_options->m_ssl_certificate_path.get(), allocator, root, value_str); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_ca_file != boost::none) monero_utils::add_json_member("ssl_ca_file", m_ssl_options->m_ssl_ca_file.get(), allocator, root, value_str); + + // set bool values + if (m_trusted != boost::none) monero_utils::add_json_member("trusted", m_trusted.get(), allocator, root); + if (m_ssl_options != boost::none && m_ssl_options->m_ssl_allow_any_cert != boost::none) monero_utils::add_json_member("ssl_allow_any_cert", m_ssl_options->m_ssl_allow_any_cert.get(), allocator, root); + // set sub-arrays + if (m_ssl_options != boost::none && !m_ssl_options->m_ssl_allowed_fingerprints.empty()) root.AddMember("ssl_allowed_fingerprints", monero_utils::to_rapidjson_val(allocator, m_ssl_options->m_ssl_allowed_fingerprints), allocator); + + // return root return root; } // --------------------------- MONERO AUTO REFRESH PARAMS --------------------------- rapidjson::Value monero_auto_refresh_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set bool values if (m_enable != boost::none) monero_utils::add_json_member("enable", m_enable.get(), allocator, root); + + // return root return root; } // --------------------------- MONERO TAG ACCOUNT PARAMS --------------------------- rapidjson::Value monero_tag_accounts_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_tag != boost::none) monero_utils::add_json_member("tag", m_tag.get(), allocator, root, value_str); if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, value_str); - if (!m_account_indices.empty()) root.AddMember("accounts", monero_utils::to_rapidjson_val(allocator, m_account_indices), allocator); + + // set sub-arrays + if (!m_account_indices.empty()) root.AddMember("accounts", monero_utils::to_rapidjson_val(allocator, m_account_indices), allocator); + + // return root return root; } // --------------------------- MONERO TX NOTES PARAMS --------------------------- rapidjson::Value monero_tx_notes_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set sub-arrays if (!m_tx_hashes.empty()) root.AddMember("txids", monero_utils::to_rapidjson_val(allocator, m_tx_hashes), allocator); if (!m_notes.empty()) root.AddMember("notes", monero_utils::to_rapidjson_val(allocator, m_notes), allocator); + + // return root return root; } // --------------------------- MONERO ADDRESS BOOK ENTRY PARAMS --------------------------- rapidjson::Value monero_address_book_entry_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); + if (m_description != boost::none) monero_utils::add_json_member("description", m_description.get(), allocator, root, value_str); + + // set number values rapidjson::Value value_num(rapidjson::kNumberType); if (m_index != boost::none) monero_utils::add_json_member("index", m_index.get(), allocator, root, value_num); + + // set bool values if (m_set_address != boost::none) monero_utils::add_json_member("set_address", m_set_address.get(), allocator, root); - if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); if (m_set_description != boost::none) monero_utils::add_json_member("set_description", m_set_description.get(), allocator, root); - if (m_description != boost::none) monero_utils::add_json_member("description", m_description.get(), allocator, root, value_str); + + // set sub-arrays if (!m_entries.empty()) root.AddMember("entries", monero_utils::to_rapidjson_val(allocator, m_entries), allocator); + + // return root return root; } // --------------------------- MONERO VERIFY SIGN MESSAGE PARAMS --------------------------- rapidjson::Value monero_verify_sign_message_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); - rapidjson::Value value_num(rapidjson::kNumberType); if (m_data != boost::none) monero_utils::add_json_member("data", m_data.get(), allocator, root, value_str); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); @@ -1606,59 +1752,87 @@ rapidjson::Value monero_verify_sign_message_params::to_rapidjson_val(rapidjson:: monero_utils::add_json_member("signature_type", std::string("spend"), allocator, root, value_str); } } + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); if (m_address_index != boost::none) monero_utils::add_json_member("address_index", m_address_index.get(), allocator, root, value_num); + // return root return root; } // --------------------------- MONERO CHECK TX KEY PARAMS --------------------------- rapidjson::Value monero_check_tx_key_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_tx_hash != boost::none) monero_utils::add_json_member("txid", m_tx_hash.get(), allocator, root, value_str); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); if (m_tx_key != boost::none) monero_utils::add_json_member("tx_key", m_tx_key.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO SIGN DESCRIBE TRANSFER PARAMS --------------------------- rapidjson::Value monero_sign_describe_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_unsigned_txset != boost::none) monero_utils::add_json_member("unsigned_txset", m_unsigned_txset.get(), allocator, root, value_str); if (m_multisig_txset != boost::none) monero_utils::add_json_member("multisig_txset", m_multisig_txset.get(), allocator, root, value_str); + // return root return root; } // --------------------------- MONERO WALLET RELAY TX PARAMS --------------------------- rapidjson::Value monero_wallet_relay_tx_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_hex != boost::none) monero_utils::add_json_member("hex", m_hex.get(), allocator, root, value_str); + + // return root return root; } // --------------------------- MONERO SUBMIT TRANSFER PARAMS --------------------------- rapidjson::Value monero_submit_transfer_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value val_str(rapidjson::kStringType); if (m_signed_tx_hex != boost::none) monero_utils::add_json_member("tx_data_hex", m_signed_tx_hex.get(), allocator, root, val_str); + + // return root return root; } // --------------------------- MONERO CREATE EDIT SUBADDRESS PARAMS --------------------------- rapidjson::Value monero_create_edit_subaddress_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value val_str(rapidjson::kStringType); - rapidjson::Value val_num(rapidjson::kNumberType); if (m_label != boost::none) monero_utils::add_json_member("label", m_label.get(), allocator, root, val_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); if (m_account_index != boost::none && m_subaddress_index != boost::none) { rapidjson::Value index(rapidjson::kObjectType); monero_utils::add_json_member("major", m_account_index.get(), allocator, index, val_num); @@ -1666,97 +1840,150 @@ rapidjson::Value monero_create_edit_subaddress_params::to_rapidjson_val(rapidjso root.AddMember("index", index, allocator); } else if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, val_num); + + // return root return root; } // --------------------------- MONERO IMPORT EXPORT OUTPUTS PARAMS --------------------------- rapidjson::Value monero_import_export_outputs_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value val_str(rapidjson::kStringType); - if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); if (m_outputs_hex != boost::none) monero_utils::add_json_member("outputs_data_hex", m_outputs_hex.get(), allocator, root, val_str); + + // set bool values + if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); + + // return root return root; } // --------------------------- MONERO CREATE OPEN WALLET PARAMS --------------------------- rapidjson::Value monero_create_open_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value val_str(rapidjson::kStringType); - rapidjson::Value val_num(rapidjson::kNumberType); + if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, val_str); + if (m_view_key != boost::none && !m_view_key->empty()) monero_utils::add_json_member("viewkey", m_view_key.get(), allocator, root, val_str); + if (m_spend_key != boost::none && !m_spend_key->empty()) monero_utils::add_json_member("spendkey", m_spend_key.get(), allocator, root, val_str); if (m_filename != boost::none) monero_utils::add_json_member("filename", m_filename.get(), allocator, root, val_str); if (m_password != boost::none) monero_utils::add_json_member("password", m_password.get(), allocator, root, val_str); if (m_language != boost::none) monero_utils::add_json_member("language", m_language.get(), allocator, root, val_str); if (m_seed != boost::none) monero_utils::add_json_member("seed", m_seed.get(), allocator, root, val_str); if (m_seed_offset != boost::none) monero_utils::add_json_member("seed_offset", m_seed_offset.get(), allocator, root, val_str); + + // set number values + rapidjson::Value val_num(rapidjson::kNumberType); if (m_restore_height != boost::none) monero_utils::add_json_member("restore_height", m_restore_height.get(), allocator, root, val_num); + + // set bool values if (m_autosave_current != boost::none) monero_utils::add_json_member("autosave_current", m_autosave_current.get(), allocator, root); if (m_enable_multisig_experimental != boost::none) monero_utils::add_json_member("enable_multisig_experimental", m_enable_multisig_experimental.get(), allocator, root); - if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, val_str); - if (m_view_key != boost::none && !m_view_key->empty()) monero_utils::add_json_member("viewkey", m_view_key.get(), allocator, root, val_str); - if (m_spend_key != boost::none && !m_spend_key->empty()) monero_utils::add_json_member("spendkey", m_spend_key.get(), allocator, root, val_str); + + // return root return root; } // --------------------------- MONERO RESERVE PROOF PARAMS --------------------------- rapidjson::Value monero_reserve_proof_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); - rapidjson::Value value_num(rapidjson::kNumberType); - if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); if (m_message != boost::none) monero_utils::add_json_member("message", m_message.get(), allocator, root, value_str); if (m_tx_hash != boost::none) monero_utils::add_json_member("txid", m_tx_hash.get(), allocator, root, value_str); - if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); - if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); if (m_address != boost::none) monero_utils::add_json_member("address", m_address.get(), allocator, root, value_str); if (m_signature != boost::none) monero_utils::add_json_member("signature", m_signature.get(), allocator, root, value_str); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + if (m_amount != boost::none) monero_utils::add_json_member("amount", m_amount.get(), allocator, root, value_num); + + // set bool values + if (m_all != boost::none) monero_utils::add_json_member("all", m_all.get(), allocator, root); + + // return root return root; } // --------------------------- MONERO REFRESH WALLET PARAMS --------------------------- rapidjson::Value monero_refresh_wallet_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set number values rapidjson::Value value_num(rapidjson::kNumberType); - if (m_enable != boost::none) monero_utils::add_json_member("enable", m_enable.get(), allocator, root); if (m_period != boost::none) monero_utils::add_json_member("period", m_period.get(), allocator, root, value_num); if (m_start_height != boost::none) monero_utils::add_json_member("start_height", m_start_height.get(), allocator, root, value_num); + + // set bool values + if (m_enable != boost::none) monero_utils::add_json_member("enable", m_enable.get(), allocator, root); + + // return root return root; } // --------------------------- MONERO GET INCOMING TRANSFERS PARAMS --------------------------- rapidjson::Value monero_get_incoming_transfers_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); - rapidjson::Value value_num(rapidjson::kNumberType); + + // set string values rapidjson::Value value_str(rapidjson::kStringType); if (m_transfer_type != boost::none) monero_utils::add_json_member("transfer_type", m_transfer_type.get(), allocator, root, value_str); - if (m_verbose != boost::none) monero_utils::add_json_member("verbose", m_verbose.get(), allocator, root); + + // set number values + rapidjson::Value value_num(rapidjson::kNumberType); if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + + // set bool values + if (m_verbose != boost::none) monero_utils::add_json_member("verbose", m_verbose.get(), allocator, root); + + // set sub-arrays if (!m_subaddr_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); + + // return root return root; } // --------------------------- MONERO GET TRANSFERS PARAMS --------------------------- rapidjson::Value monero_get_transfers_params::to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const { + // create root rapidjson::Value root(rapidjson::kObjectType); + + // set number values rapidjson::Value value_num(rapidjson::kNumberType); - rapidjson::Value value_str(rapidjson::kStringType); - bool filter_by_height = m_min_height != boost::none || m_max_height != boost::none; - monero_utils::add_json_member("filter_by_height", filter_by_height, allocator, root); + + if (m_min_height != boost::none) monero_utils::add_json_member("min_height", m_min_height.get(), allocator, root, value_num); + if (m_max_height != boost::none) monero_utils::add_json_member("max_height", m_max_height.get(), allocator, root, value_num); + if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + + // set bool values + monero_utils::add_json_member("filter_by_height", m_min_height != boost::none || m_max_height != boost::none, allocator, root); if (m_in != boost::none) monero_utils::add_json_member("in", m_in.get(), allocator, root); if (m_out != boost::none) monero_utils::add_json_member("out", m_out.get(), allocator, root); if (m_pool != boost::none) monero_utils::add_json_member("pool", m_pool.get(), allocator, root); if (m_pending != boost::none) monero_utils::add_json_member("pending", m_pending.get(), allocator, root); if (m_failed != boost::none) monero_utils::add_json_member("failed", m_failed.get(), allocator, root); - if (m_min_height != boost::none) monero_utils::add_json_member("min_height", m_min_height.get(), allocator, root, value_num); - if (m_max_height != boost::none) monero_utils::add_json_member("max_height", m_max_height.get(), allocator, root, value_num); if (m_all_accounts != boost::none) monero_utils::add_json_member("all_accounts", m_all_accounts.get(), allocator, root); - if (m_account_index != boost::none) monero_utils::add_json_member("account_index", m_account_index.get(), allocator, root, value_num); + + // set sub-arrays if (!m_subaddr_indices.empty()) root.AddMember("subaddr_indices", monero_utils::to_rapidjson_val(allocator, m_subaddr_indices), allocator); + + // return root return root; } diff --git a/src/cpp/wallet/py_monero_wallet_model.h b/src/cpp/wallet/py_monero_wallet_model.h index d301188..1da87b6 100644 --- a/src/cpp/wallet/py_monero_wallet_model.h +++ b/src/cpp/wallet/py_monero_wallet_model.h @@ -441,11 +441,7 @@ struct monero_set_daemon_params : public monero_json_request_params { boost::optional m_password; boost::optional m_trusted; boost::optional m_ssl_support; - boost::optional m_ssl_private_key_path; - boost::optional m_ssl_certificate_path; - boost::optional m_ssl_ca_file; - std::vector m_ssl_allowed_fingerprints; - boost::optional m_ssl_allow_any_cert; + boost::optional m_ssl_options; rapidjson::Value to_rapidjson_val(rapidjson::Document::AllocatorType& allocator) const override; }; diff --git a/src/cpp/wallet/py_monero_wallet_rpc.cpp b/src/cpp/wallet/py_monero_wallet_rpc.cpp index 97c32d7..4517a45 100644 --- a/src/cpp/wallet/py_monero_wallet_rpc.cpp +++ b/src/cpp/wallet/py_monero_wallet_rpc.cpp @@ -402,14 +402,7 @@ void monero_wallet_rpc::set_daemon_connection(const boost::optionalm_trusted = is_trusted; params->m_ssl_support = "autodetect"; - - if (ssl_options != boost::none) { - params->m_ssl_private_key_path = ssl_options->m_ssl_private_key_path; - params->m_ssl_certificate_path = ssl_options->m_ssl_certificate_path; - params->m_ssl_ca_file = ssl_options->m_ssl_ca_file; - params->m_ssl_allowed_fingerprints = ssl_options->m_ssl_allowed_fingerprints; - params->m_ssl_allow_any_cert = ssl_options->m_ssl_allow_any_cert; - } + params->m_ssl_options = ssl_options; m_rpc->send_json_request("set_daemon", params); diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index cfb38a5..f168b8b 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -43,6 +43,7 @@ from .sync_seed_tester import SyncSeedTester from .send_and_update_txs_tester import SendAndUpdateTxsTester from .sync_with_pool_submit_tester import SyncWithPoolSubmitTester +from .txs_structure_tester import TxsStructureTester from .docker_wallet_rpc_manager import DockerWalletRpcManager from .rpc_connection_utils import RpcConnectionUtils from .base_test_class import BaseTestClass @@ -96,5 +97,6 @@ 'BaseTestClass', 'WalletErrorUtils', 'WalletSendUtils', - 'WalletTestUtils' + 'WalletTestUtils', + 'TxsStructureTester' ] diff --git a/tests/utils/tx_wallet_tester.py b/tests/utils/tx_wallet_tester.py new file mode 100644 index 0000000..b01f821 --- /dev/null +++ b/tests/utils/tx_wallet_tester.py @@ -0,0 +1,300 @@ +import logging + +from typing import Optional + +from monero import ( + MoneroTxWallet, MoneroTransfer, MoneroTxConfig, + MoneroUtils, MoneroDestination +) + +from .gen_utils import GenUtils +from .output_utils import OutputUtils +from .transfer_utils import TransferUtils +from .context import TxContext + +logger: logging.Logger = logging.getLogger("TxWalletTester") + + +class TxWalletTester: + + tx: MoneroTxWallet + ctx: TxContext + + def __init__(self, tx: MoneroTxWallet, context: Optional[TxContext]) -> None: + self.tx = tx + # validate / sanitize inputs + self.ctx = TxContext(context) + self.ctx.wallet = None # TODO: re-enable + assert tx is not None + if self.ctx.is_send_response is None or self.ctx.config is None: + assert self.ctx.is_send_response is None, "if either send_request or is_send_response is defined, they must both be defined" + assert self.ctx.config is None, "if either send_request or is_send_response is defined, they must both be defined" + + def _test_common(self) -> None: + # test common field types + assert self.tx.hash is not None + assert self.tx.is_confirmed is not None + assert self.tx.is_miner_tx is not None + assert self.tx.is_failed is not None + assert self.tx.is_relayed is not None + assert self.tx.in_tx_pool is not None + assert self.tx.is_locked is not None + GenUtils.test_unsigned_big_integer(self.tx.fee) + if self.tx.payment_id is not None: + # default payment id converted to None + assert MoneroTxWallet.DEFAULT_PAYMENT_ID != self.tx.payment_id + if self.tx.note is not None: + # empty notes converted to undefined + assert len(self.tx.note) > 0 + + assert self.tx.unlock_time is not None + assert self.tx.unlock_time >= 0 + assert self.tx.size is None # TODO monero-wallet-rpc: add tx_size to get_transfers and get_transfer_by_txid + assert self.tx.received_timestamp is None # TODO monero-wallet-rpc: return received timestamp (asked to file issue if wanted) + + def _test_send(self) -> None: + # test send tx + if self.ctx.is_send_response is True: + assert self.tx.weight is not None + assert self.tx.weight > 0 + assert len(self.tx.inputs) > 0 + for tx_input in self.tx.inputs: + assert tx_input.tx == self.tx + else: + assert self.tx.weight is None + assert len(self.tx.inputs) == 0 + + def _test_pool_status(self) -> None: + # test confirmed + if self.tx.is_confirmed: + assert self.tx.block is not None + assert self.tx in self.tx.block.txs + assert self.tx.block.height is not None + assert self.tx.block.height > 0 + assert self.tx.block.timestamp is not None + assert self.tx.block.timestamp > 0 + assert self.tx.relay is True + assert self.tx.is_relayed is True + assert self.tx.is_failed is False + assert self.tx.in_tx_pool is False + assert self.tx.is_double_spend_seen is False + assert self.tx.num_confirmations is not None + assert self.tx.num_confirmations > 0 + else: + assert self.tx.block is None + assert self.tx.num_confirmations is not None + assert self.tx.num_confirmations == 0 + + # test in tx pool + if self.tx.in_tx_pool: + assert self.tx.is_confirmed is False + assert self.tx.relay is True + assert self.tx.is_relayed is True + assert self.tx.is_double_spend_seen is False + assert self.tx.is_locked is True + + # these should be initialized unless a response from sending + # TODO re-enable when received timestamp returned in wallet rpc + #if cself.self.tx.is_send_response: + # assert self.tx.received_timestamp > 0 + else: + assert self.tx.last_relayed_timestamp is None + + def _test_status(self) -> None: + # test miner tx + if self.tx.is_miner_tx: + assert self.tx.fee is not None + assert self.tx.fee == 0 + + # test failed + # TODO what else to test associated with failed + if self.tx.is_failed: + assert isinstance(self.tx.outgoing_transfer, MoneroTransfer) + # TODO re-enable when received timestamp returned in wallet rpc + #assert self.tx.received_timestamp > 0 + else: + if self.tx.is_relayed: + assert self.tx.is_double_spend_seen is False + else: + assert self.tx.relay is False + assert self.tx.is_relayed is False + assert self.tx.is_double_spend_seen is None + + assert self.tx.last_failed_height is None + assert self.tx.last_failed_hash is None + + # received time only for tx pool or failed txs + if self.tx.received_timestamp is not None: + assert self.tx.in_tx_pool or self.tx.is_failed + + # test relayed tx + if self.tx.is_relayed: + assert self.tx.relay is True + if self.tx.relay is False: + assert (not self.tx.is_relayed) is True + + def _test_outgoing_transfer(self) -> None: + # test outgoing transfer per configuration + if self.ctx.has_outgoing_transfer is False: + assert self.tx.outgoing_transfer is None + if self.ctx.has_destinations is True: + assert self.tx.outgoing_transfer is not None + assert len(self.tx.outgoing_transfer.destinations) > 0 + + # test outgoing transfer + if self.tx.outgoing_transfer is not None: + assert self.tx.is_outgoing is True + TransferUtils.test_transfer(self.tx.outgoing_transfer, self.ctx) + if self.ctx.is_sweep_response is True: + assert len(self.tx.outgoing_transfer.destinations) == 1, f"Expected 1 tx, got {len(self.tx.outgoing_transfer.destinations)}" + # TODO handle special cases + else: + assert len(self.tx.incoming_transfers) > 0 + assert self.tx.get_outgoing_amount() == 0 + assert self.tx.outgoing_transfer is None + assert self.tx.ring_size is None + assert self.tx.full_hex is None + assert self.tx.metadata is None + assert self.tx.key is None + + def _test_incoming_transfers(self) -> None: + # test incoming transfers + if len(self.tx.incoming_transfers) > 0: + assert self.tx.is_incoming is True + GenUtils.test_unsigned_big_integer(self.tx.get_incoming_amount()) + assert self.tx.is_failed is False + + # test each transfer and collect transfer sum + transfer_sum: int = 0 + for transfer in self.tx.incoming_transfers: + assert transfer.account_index is not None + assert transfer.subaddress_index is not None + TransferUtils.test_transfer(transfer, self.ctx) + assert transfer.amount is not None + transfer_sum += transfer.amount + if self.ctx.wallet is not None: + addr = self.ctx.wallet.get_address(transfer.account_index, transfer.subaddress_index) + assert transfer.address == addr + # TODO special case: transfer amount of 0 + + # incoming transfers add up to incoming tx amount + assert self.tx.get_incoming_amount() == transfer_sum + else: + assert self.tx.outgoing_transfer is not None + assert self.tx.get_incoming_amount() == 0 + assert len(self.tx.incoming_transfers) == 0 + + def _test_relay(self, config: MoneroTxConfig) -> None: + if config.relay is True: + # test relayed txs + assert self.tx.in_tx_pool is True + assert self.tx.relay is True + assert self.tx.is_relayed is True + assert self.tx.last_relayed_timestamp is not None + assert self.tx.last_relayed_timestamp > 0 + assert self.tx.is_double_spend_seen is False + else: + # test non-relayed txs + assert self.tx.in_tx_pool is False + assert self.tx.relay is False + assert self.tx.is_relayed is False + assert self.tx.last_relayed_timestamp is None + assert self.tx.is_double_spend_seen is None + + def _test_send_response(self) -> None: + # test tx set + assert self.tx.tx_set is not None + found: bool = False + for a_tx in self.tx.tx_set.txs: + if a_tx == self.tx: + found = True + break + + if self.ctx.is_copy is True: + assert found is False + else: + assert found + + # test common attributes + assert self.ctx.config is not None + config: MoneroTxConfig = self.ctx.config + assert self.tx.is_confirmed is False + TransferUtils.test_transfer(self.tx.outgoing_transfer, self.ctx) + assert self.tx.ring_size == MoneroUtils.get_ring_size() + assert self.tx.unlock_time == 0 + assert self.tx.block is None + assert self.tx.key is not None + assert len(self.tx.key) > 0 + assert self.tx.full_hex is not None + assert len(self.tx.full_hex) > 0 + assert self.tx.metadata is not None + assert self.tx.received_timestamp is None + assert self.tx.is_locked is True + + # get locked state + if self.tx.unlock_time == 0: + assert self.tx.is_confirmed == (not self.tx.is_locked) + else: + assert self.tx.is_locked is True + + # TODO implement is_locked + #for output in self.tx.get_outputs_wallet(): + # assert self.tx.is_locked == output.is_locked + + # test destinations of sent tx + assert self.tx.outgoing_transfer is not None + if len(self.tx.outgoing_transfer.destinations) == 0: + assert config.can_split is True + # TODO: remove this after >18.3.1 when amounts_by_dest_list official + logger.warning("Destinations not returned from split transactions") + else: + subtract_fee_from_dests: bool = len(config.subtract_fee_from) > 0 + if self.ctx.is_sweep_response is True: + dests: list[MoneroDestination] = config.get_normalized_destinations() + assert len(dests) == 1 + assert dests[0].amount is None + if not subtract_fee_from_dests: + assert self.tx.outgoing_transfer.amount == self.tx.outgoing_transfer.destinations[0].amount + + self._test_relay(config) + + def _test_inputs_and_outputs(self) -> None: + # test inputs + if self.tx.is_outgoing is True and self.ctx.is_send_response is True: + assert len(self.tx.inputs) > 0 + + for self.wallet_input in self.tx.get_inputs_wallet(): + OutputUtils.test_input_wallet(self.wallet_input) + + # test outputs + if self.tx.is_incoming is True and self.ctx.include_outputs is True: + if self.tx.is_confirmed is True: + assert len(self.tx.outputs) > 0 + else: + assert len(self.tx.outputs) == 0 + + for output in self.tx.get_outputs_wallet(): + OutputUtils.test_output_wallet(output) + + def run(self) -> None: + """Run test.""" + self._test_common() + self._test_send() + self._test_pool_status() + self._test_status() + self._test_outgoing_transfer() + self._test_incoming_transfers() + + if self.ctx.is_send_response: + self._test_send_response() + else: + # test tx result query + # tx set only initialized on send responses + assert self.tx.tx_set is None + assert self.tx.ring_size is None + assert self.tx.key is None + assert self.tx.full_hex is None + assert self.tx.metadata is None + assert self.tx.last_relayed_timestamp is None + + self._test_inputs_and_outputs() diff --git a/tests/utils/tx_wallet_utils.py b/tests/utils/tx_wallet_utils.py index 6ed1b66..0f659bf 100644 --- a/tests/utils/tx_wallet_utils.py +++ b/tests/utils/tx_wallet_utils.py @@ -5,10 +5,8 @@ from monero import ( - MoneroTransfer, MoneroTxWallet, - MoneroTxConfig, MoneroUtils, - MoneroDestination, MoneroTxSet, - MoneroTxQuery, MoneroBlock, + MoneroTxWallet, MoneroUtils, + MoneroTxSet, MoneroTxQuery, MoneroNetworkType, MoneroCheckTx, MoneroCheckReserve ) @@ -16,10 +14,11 @@ from .assert_utils import AssertUtils from .gen_utils import GenUtils from .context import TxContext -from .block_utils import BlockUtils -from .output_utils import OutputUtils from .transfer_utils import TransferUtils +from .tx_wallet_tester import TxWalletTester +from .txs_structure_tester import TxsStructureTester + logger: logging.Logger = logging.getLogger("TxWalletUtils") @@ -36,258 +35,9 @@ def test_tx_wallet(cls, tx: Optional[MoneroTxWallet], context: Optional[TxContex :param MoneroTxWallet | None tx: wallet transaction to test. :param TxContext | None context: test context (default `None`). """ - # validate / sanitize inputs - ctx = TxContext(context) - ctx.wallet = None # TODO: re-enable assert tx is not None - if ctx.is_send_response is None or ctx.config is None: - assert ctx.is_send_response is None, "if either sendRequest or isSendResponse is defined, they must both be defined" - assert ctx.config is None, "if either sendRequest or isSendResponse is defined, they must both be defined" - - # test common field types - assert tx.hash is not None - assert tx.is_confirmed is not None - assert tx.is_miner_tx is not None - assert tx.is_failed is not None - assert tx.is_relayed is not None - assert tx.in_tx_pool is not None - assert tx.is_locked is not None - GenUtils.test_unsigned_big_integer(tx.fee) - if tx.payment_id is not None: - # default payment id converted to None - assert MoneroTxWallet.DEFAULT_PAYMENT_ID != tx.payment_id - if tx.note is not None: - # empty notes converted to undefined - assert len(tx.note) > 0 - - assert tx.unlock_time is not None - assert tx.unlock_time >= 0 - assert tx.size is None # TODO monero-wallet-rpc: add tx_size to get_transfers and get_transfer_by_txid - assert tx.received_timestamp is None # TODO monero-wallet-rpc: return received timestamp (asked to file issue if wanted) - - # test send tx - if ctx.is_send_response is True: - assert tx.weight is not None - assert tx.weight > 0 - assert len(tx.inputs) > 0 - for tx_input in tx.inputs: - assert tx_input.tx == tx - else: - assert tx.weight is None - assert len(tx.inputs) == 0 - - # test confirmed - if tx.is_confirmed: - assert tx.block is not None - assert tx in tx.block.txs - assert tx.block.height is not None - assert tx.block.height > 0 - assert tx.block.timestamp is not None - assert tx.block.timestamp > 0 - assert tx.relay is True - assert tx.is_relayed is True - assert tx.is_failed is False - assert tx.in_tx_pool is False - assert tx.is_double_spend_seen is False - assert tx.num_confirmations is not None - assert tx.num_confirmations > 0 - else: - assert tx.block is None - assert tx.num_confirmations is not None - assert tx.num_confirmations == 0 - - # test in tx pool - if tx.in_tx_pool: - assert tx.is_confirmed is False - assert tx.relay is True - assert tx.is_relayed is True - assert tx.is_double_spend_seen is False - assert tx.is_locked is True - - # these should be initialized unless a response from sending - # TODO re-enable when received timestamp returned in wallet rpc - #if ctx.is_send_response: - # assert tx.received_timestamp > 0 - else: - assert tx.last_relayed_timestamp is None - - # test miner tx - if tx.is_miner_tx: - assert tx.fee is not None - assert tx.fee == 0 - - # test failed - # TODO what else to test associated with failed - if tx.is_failed: - assert isinstance(tx.outgoing_transfer, MoneroTransfer) - # TODO re-enable when received timestamp returned in wallet rpc - #assert tx.received_timestamp > 0 - else: - if tx.is_relayed: - assert tx.is_double_spend_seen is False - else: - assert tx.relay is False - assert tx.is_relayed is False - assert tx.is_double_spend_seen is None - - assert tx.last_failed_height is None - assert tx.last_failed_hash is None - - # received time only for tx pool or failed txs - if tx.received_timestamp is not None: - assert tx.in_tx_pool or tx.is_failed - - # test relayed tx - if tx.is_relayed: - assert tx.relay is True - if tx.relay is False: - assert (not tx.is_relayed) is True - - # test outgoing transfer per configuration - if ctx.has_outgoing_transfer is False: - assert tx.outgoing_transfer is None - if ctx.has_destinations is True: - assert tx.outgoing_transfer is not None - assert len(tx.outgoing_transfer.destinations) > 0 - - # test outgoing transfer - if tx.outgoing_transfer is not None: - assert tx.is_outgoing is True - TransferUtils.test_transfer(tx.outgoing_transfer, ctx) - if ctx.is_sweep_response is True: - assert len(tx.outgoing_transfer.destinations) == 1, f"Expected 1 tx, got {len(tx.outgoing_transfer.destinations)}" - # TODO handle special cases - else: - assert len(tx.incoming_transfers) > 0 - assert tx.get_outgoing_amount() == 0 - assert tx.outgoing_transfer is None - assert tx.ring_size is None - assert tx.full_hex is None - assert tx.metadata is None - assert tx.key is None - - # test incoming transfers - if len(tx.incoming_transfers) > 0: - assert tx.is_incoming is True - GenUtils.test_unsigned_big_integer(tx.get_incoming_amount()) - assert tx.is_failed is False - - # test each transfer and collect transfer sum - transfer_sum: int = 0 - for transfer in tx.incoming_transfers: - TransferUtils.test_transfer(transfer, ctx) - assert transfer.amount is not None - transfer_sum += transfer.amount - if ctx.wallet is not None: - addr = ctx.wallet.get_address(transfer.account_index, transfer.subaddress_index) - assert transfer.address == addr - # TODO special case: transfer amount of 0 - - # incoming transfers add up to incoming tx amount - assert tx.get_incoming_amount() == transfer_sum - else: - assert tx.outgoing_transfer is not None - assert tx.get_incoming_amount() == 0 - assert len(tx.incoming_transfers) == 0 - - # test tx results from send or relay - if ctx.is_send_response is True: - # test tx set - assert tx.tx_set is not None - found: bool = False - for a_tx in tx.tx_set.txs: - if a_tx == tx: - found = True - break - - if ctx.is_copy is True: - assert found is False - else: - assert found - - # test common attributes - assert ctx.config is not None - config: MoneroTxConfig = ctx.config - assert tx.is_confirmed is False - TransferUtils.test_transfer(tx.outgoing_transfer, ctx) - assert tx.ring_size == MoneroUtils.get_ring_size() - assert tx.unlock_time == 0 - assert tx.block is None - assert tx.key is not None - assert len(tx.key) > 0 - assert tx.full_hex is not None - assert len(tx.full_hex) > 0 - assert tx.metadata is not None - assert tx.received_timestamp is None - assert tx.is_locked is True - - # get locked state - if tx.unlock_time == 0: - assert tx.is_confirmed == (not tx.is_locked) - else: - assert tx.is_locked is True - - # TODO implement is_locked - #for output in tx.get_outputs_wallet(): - # assert tx.is_locked == output.is_locked - - # test destinations of sent tx - assert tx.outgoing_transfer is not None - if len(tx.outgoing_transfer.destinations) == 0: - assert config.can_split is True - # TODO: remove this after >18.3.1 when amounts_by_dest_list official - logger.warning("Destinations not returned from split transactions") - else: - subtract_fee_from_dests: bool = len(config.subtract_fee_from) > 0 - if ctx.is_sweep_response is True: - dests: list[MoneroDestination] = config.get_normalized_destinations() - assert len(dests) == 1 - assert dests[0].amount is None - if not subtract_fee_from_dests: - assert tx.outgoing_transfer.amount == tx.outgoing_transfer.destinations[0].amount - - if config.relay is True: - # test relayed txs - assert tx.in_tx_pool is True - assert tx.relay is True - assert tx.is_relayed is True - assert tx.last_relayed_timestamp is not None - assert tx.last_relayed_timestamp > 0 - assert tx.is_double_spend_seen is False - else: - # test non-relayed txs - assert tx.in_tx_pool is False - assert tx.relay is False - assert tx.is_relayed is False - assert tx.last_relayed_timestamp is None - assert tx.is_double_spend_seen is None - - else: - # test tx result query - # tx set only initialized on send responses - assert tx.tx_set is None - assert tx.ring_size is None - assert tx.key is None - assert tx.full_hex is None - assert tx.metadata is None - assert tx.last_relayed_timestamp is None - - # test inputs - if tx.is_outgoing is True and ctx.is_send_response is True: - assert len(tx.inputs) > 0 - - for wallet_input in tx.get_inputs_wallet(): - OutputUtils.test_input_wallet(wallet_input) - - # test outputs - if tx.is_incoming is True and ctx.include_outputs is True: - if tx.is_confirmed is True: - assert len(tx.outputs) > 0 - else: - assert len(tx.outputs) == 0 - - for output in tx.get_outputs_wallet(): - OutputUtils.test_output_wallet(output) + tester: TxWalletTester = TxWalletTester(tx, context) + tester.run() # TODO test deep copy #if ctx.is_copy is not True: @@ -347,110 +97,17 @@ def test_described_tx_set(cls, described_tx_set: MoneroTxSet, network_type: Mone TransferUtils.test_destination(destination) @classmethod - def test_get_txs_structure(cls, txs: list[MoneroTxWallet], q: Optional[MoneroTxQuery], regtest: bool) -> None: + def test_get_txs_structure(cls, txs: list[MoneroTxWallet], query: Optional[MoneroTxQuery], regtest: bool) -> None: """ Tests the integrity of the full structure in the given txs from the block down to transfers / destinations. :param list[MoneroTxWallet] txs: list of txs to get structure from. - :param MoneroTxQuery | None q: filter txs by query, if set. + :param MoneroTxQuery | None query: filter txs by query, if set. :param bool regtest: indicates if running test on regtest network. """ - query = q if q is not None else MoneroTxQuery() - # collect unique blocks in order - seen_blocks: set[MoneroBlock] = set() - blocks: list[MoneroBlock] = [] - unconfirmed_txs: list[MoneroTxWallet] = [] - - for tx in txs: - if tx.block is None: - unconfirmed_txs.append(tx) - else: - assert BlockUtils.is_tx_in_block(tx.hash, tx.block) - if tx.block not in seen_blocks: - seen_blocks.add(tx.block) - blocks.append(tx.block) - - # tx hashes must be in order if requested - if len(query.hashes) > 0: - assert len(txs) == len(query.hashes) - for i, query_hash in enumerate(query.hashes): - assert query_hash == txs[i].hash - - # test that txs and blocks reference each other and blocks are in ascending order unless specific tx hashes queried - index: int = 0 - prev_block_height: Optional[int] = None - for block in blocks: - if prev_block_height is None: - prev_block_height = block.height - elif len(query.hashes) == 0: - assert block.height is not None - msg = f"Blocks are not in order of heights: {prev_block_height} vs {block.height}" - assert block.height > prev_block_height, msg - - for tx in block.txs: - assert tx.block == block - if len(query.hashes) == 0: - other = txs[index] - if not regtest: - assert other.hash == tx.hash, "Txs in block are not in order" - # verify tx order is self-consistent with blocks unless txs manually re-ordered by querying by hash - assert other == tx - else: - # TODO regtest wallet2 has inconsinstent txs order betwenn - assert other in block.txs, "Tx not found in block" - - index += 1 - - assert len(txs) == index + len(unconfirmed_txs), f"txs: {len(txs)}, unconfirmed txs: {len(unconfirmed_txs)}, index: {index}" - - # test that incoming transfers are in order of ascending accounts and subaddresses - for tx in txs: - if len(tx.incoming_transfers) == 0: - continue - - prev_account_idx: Optional[int] = None - prev_subaddress_idx: Optional[int] = None - for transfer in tx.incoming_transfers: - if prev_account_idx is None: - prev_account_idx = transfer.account_index - - else: - assert prev_account_idx is not None - assert transfer.account_index is not None - assert prev_account_idx <= transfer.account_index - if prev_account_idx < transfer.account_index: - prev_subaddress_idx = None - prev_account_idx = transfer.account_index - if prev_subaddress_idx is None: - prev_subaddress_idx = transfer.subaddress_index - else: - assert transfer.subaddress_index is not None - assert prev_subaddress_idx < transfer.subaddress_index - - # test that outputs are in order of ascending accounts and subaddresses - for tx in txs: - if len(tx.outputs) == 0: - continue - - prev_account_idx: Optional[int] = None - prev_subaddress_idx: Optional[int] = None - for output in tx.get_outputs_wallet(): - if prev_account_idx is None: - prev_account_idx = output.account_index - else: - assert output.account_index is not None - assert prev_account_idx <= output.account_index - if prev_account_idx < output.account_index: - prev_subaddress_idx = None - prev_account_idx = output.account_index - if prev_subaddress_idx is None: - prev_subaddress_idx = output.subaddress_index - else: - assert prev_subaddress_idx is not None - assert output.subaddress_index is not None - # TODO: this does not test that index < other index if subaddresses are equal - assert prev_subaddress_idx <= output.subaddress_index + tester: TxsStructureTester = TxsStructureTester(txs, query, regtest) + tester.run() @classmethod def test_common_tx_sets(cls, txs: list[MoneroTxWallet], has_signed: bool, has_unsigned: bool, has_multisig: bool) -> None: diff --git a/tests/utils/txs_structure_tester.py b/tests/utils/txs_structure_tester.py new file mode 100644 index 0000000..353674f --- /dev/null +++ b/tests/utils/txs_structure_tester.py @@ -0,0 +1,163 @@ +from typing import Optional + +from monero import MoneroBlock, MoneroTxWallet, MoneroTxQuery + +from .block_utils import BlockUtils + + +class TxsStructureTester: + """Tests the integrity of the full structure in the given txs from the block down to transfers / destinations.""" + + txs: list[MoneroTxWallet] + """Txs to test structure.""" + + query: MoneroTxQuery + """Filter txs by query.""" + + regtest: bool + """Indicates if running test on regtest network.""" + + seen_blocks: set[MoneroBlock] + """Unique set of seen blocks in txs.""" + + blocks: list[MoneroBlock] + """All blocks seen by txs.""" + + unconfirmed_txs: list[MoneroTxWallet] + """Unconfirmed transactions to test.""" + + @property + def num_txs(self) -> int: + """Number of transactions to test.""" + return len(self.txs) + + @property + def num_unconfirmed_txs(self) -> int: + """Number of unconfirmed txs to test.""" + return len(self.unconfirmed_txs) + + @property + def num_tx_hashes(self) -> int: + """Number of tx hashes set in tx query.""" + return len(self.query.hashes) + + def __init__(self, txs: list[MoneroTxWallet], query: Optional[MoneroTxQuery], regtest: bool) -> None: + """Initialize a new txs structure tester. + + :param list[MoneroTxWallet] txs: list of txs to get structure from. + :param MoneroTxQuery | None query: filter txs by query, if set. + :param bool regtest: indicates if running test on regtest network. + """ + self.txs = txs + self.query = query if query is not None else MoneroTxQuery() + self.seen_blocks = set() + self.blocks = [] + self.unconfirmed_txs = [] + self.regtest = regtest + + # initialize + for tx in txs: + if tx.block is None: + self.unconfirmed_txs.append(tx) + else: + assert BlockUtils.is_tx_in_block(tx.hash, tx.block) + if tx.block not in self.seen_blocks: + self.seen_blocks.add(tx.block) + self.blocks.append(tx.block) + + def _test_txs_order(self) -> None: + """Test that txs and blocks reference each other and blocks are in + ascending order unless specific tx hashes queried. + """ + # tx hashes must be in order if requested + if self.num_tx_hashes > 0: + assert self.num_txs == self.num_tx_hashes + for i, query_hash in enumerate(self.query.hashes): + assert query_hash == self.txs[i].hash + + # test that txs and blocks reference each other and blocks are in ascending order unless specific tx hashes queried + index: int = 0 + prev_block_height: Optional[int] = None + for block in self.blocks: + if prev_block_height is None: + prev_block_height = block.height + elif self.num_tx_hashes == 0: + assert block.height is not None + msg = f"Blocks are not in order of heights: {prev_block_height} vs {block.height}" + assert block.height > prev_block_height, msg + + for tx in block.txs: + assert tx.block == block + if self.num_tx_hashes == 0: + other = self.txs[index] + if not self.regtest: + assert other.hash == tx.hash, "Txs in block are not in order" + # verify tx order is self-consistent with blocks unless txs manually re-ordered by querying by hash + assert other == tx + else: + # TODO regtest wallet2 has inconsinstent txs order betwenn + assert other in block.txs, "Tx not found in block" + + index += 1 + + assert self.num_txs == index + self.num_unconfirmed_txs, f"txs: {self.num_txs}, unconfirmed txs: {self.num_unconfirmed_txs}, index: {index}" + + def _test_incoming_transfers_order(self) -> None: + """Test that incoming transfers are in order + of ascending accounts and subaddresses. + """ + # test that incoming transfers are in order of ascending accounts and subaddresses + for tx in self.txs: + if len(tx.incoming_transfers) == 0: + continue + + prev_account_idx: Optional[int] = None + prev_subaddress_idx: Optional[int] = None + for transfer in tx.incoming_transfers: + if prev_account_idx is None: + prev_account_idx = transfer.account_index + + else: + assert prev_account_idx is not None + assert transfer.account_index is not None + assert prev_account_idx <= transfer.account_index + if prev_account_idx < transfer.account_index: + prev_subaddress_idx = None + prev_account_idx = transfer.account_index + if prev_subaddress_idx is None: + prev_subaddress_idx = transfer.subaddress_index + else: + assert transfer.subaddress_index is not None + assert prev_subaddress_idx < transfer.subaddress_index + + def _test_outputs_order(self) -> None: + """test that outputs are in order of ascending accounts and subaddresses.""" + # test that outputs are in order of ascending accounts and subaddresses + for tx in self.txs: + if len(tx.outputs) == 0: + continue + + prev_account_idx: Optional[int] = None + prev_subaddress_idx: Optional[int] = None + for output in tx.get_outputs_wallet(): + if prev_account_idx is None: + prev_account_idx = output.account_index + else: + assert output.account_index is not None + assert prev_account_idx <= output.account_index + if prev_account_idx < output.account_index: + prev_subaddress_idx = None + prev_account_idx = output.account_index + if prev_subaddress_idx is None: + prev_subaddress_idx = output.subaddress_index + else: + assert prev_subaddress_idx is not None + assert output.subaddress_index is not None + # TODO: this does not test that index < other index if subaddresses are equal + assert prev_subaddress_idx <= output.subaddress_index + + def run(self) -> None: + """Run test.""" + self._test_txs_order() + self._test_incoming_transfers_order() + self._test_outputs_order() diff --git a/tests/utils/wallet_equality_utils.py b/tests/utils/wallet_equality_utils.py index a913000..2fd57b5 100644 --- a/tests/utils/wallet_equality_utils.py +++ b/tests/utils/wallet_equality_utils.py @@ -81,6 +81,15 @@ def test_wallet_full_equality_on_chain(cls, wallet1: MoneroWalletFull, wallet2: assert wallet1.get_seed_language() == wallet2.get_seed_language() # TODO more pybind specific extensions + @classmethod + def test_account(cls, accounts: list[MoneroAccount], j: int, size: int) -> None: + while j < size: + assert 0 == accounts[j].balance + assert len(accounts[j].subaddresses) >= 1 + for subaddress in accounts[j].subaddresses: + assert subaddress.is_used is False + j += 1 + @classmethod def test_accounts_equal_on_chain(cls, accounts1: list[MoneroAccount], accounts2: list[MoneroAccount]) -> None: """Test account lists equality based on on-chain data. @@ -96,25 +105,10 @@ def test_accounts_equal_on_chain(cls, accounts1: list[MoneroAccount], accounts2: if i < accounts1_size and i < accounts2_size: cls.test_account_equal_on_chain(accounts1[i], accounts2[i]) elif i >= accounts1_size: - j: int = i - - while j < accounts2_size: - assert 0 == accounts2[j].balance - assert len(accounts2[j].subaddresses) >= 1 - for subaddress in accounts2[j].subaddresses: - assert subaddress.is_used is False - j += 1 - + cls.test_account(accounts2, i, accounts2_size) return else: - j: int = i - while j < accounts1_size: - assert 0 == accounts1[j].balance - assert len(accounts1[j].subaddresses) >= 1 - for subaddress in accounts1[j].subaddresses: - assert subaddress.is_used is False - j += 1 - + cls.test_account(accounts1, i, accounts1_size) return @classmethod @@ -182,26 +176,7 @@ def test_subaddress_equal_on_chain(cls, subaddress1: MoneroSubaddress, subaddres AssertUtils.assert_equals(subaddress1, subaddress2) @classmethod - def test_tx_wallets_equal_on_chain(cls, txs_1: list[MoneroTxWallet], txs_2: list[MoneroTxWallet]) -> None: - """Test wallet txs equality based on on-chain data. - - :param list[MoneroTxWallet] txs_1: first wallet tx list to compare on-chain data. - :param list[MoneroTxWallet] txs_2: second wallet tx list to compare on-chain data. - """ - # remove pool or failed txs for comparison - txs1: list[MoneroTxWallet] = list(filter(lambda tx: not tx.in_tx_pool and not tx.is_failed, txs_1)) - txs2: list[MoneroTxWallet] = list(filter(lambda tx: not tx.in_tx_pool and not tx.is_failed, txs_2)) - - # nullify off-chain data for comparison - all_txs: list[MoneroTxWallet] = txs1.copy() - all_txs.extend(txs2) - for tx in all_txs: - tx.note = None - if tx.outgoing_transfer is not None: - tx.outgoing_transfer.addresses = [] - - # compare txs - assert len(txs1) == len(txs2) + def test_txs_wallet_equality(cls, txs1: list[MoneroTxWallet], txs2: list[MoneroTxWallet]) -> None: for tx1 in txs1: found: bool = False for tx2 in txs2: @@ -235,6 +210,29 @@ def test_tx_wallets_equal_on_chain(cls, txs_1: list[MoneroTxWallet], txs_2: list # each tx must have one and only one match assert found, "Tx not found" + @classmethod + def test_tx_wallets_equal_on_chain(cls, txs_1: list[MoneroTxWallet], txs_2: list[MoneroTxWallet]) -> None: + """Test wallet txs equality based on on-chain data. + + :param list[MoneroTxWallet] txs_1: first wallet tx list to compare on-chain data. + :param list[MoneroTxWallet] txs_2: second wallet tx list to compare on-chain data. + """ + # remove pool or failed txs for comparison + txs1: list[MoneroTxWallet] = list(filter(lambda tx: not tx.in_tx_pool and not tx.is_failed, txs_1)) + txs2: list[MoneroTxWallet] = list(filter(lambda tx: not tx.in_tx_pool and not tx.is_failed, txs_2)) + + # nullify off-chain data for comparison + all_txs: list[MoneroTxWallet] = txs1.copy() + all_txs.extend(txs2) + for tx in all_txs: + tx.note = None + if tx.outgoing_transfer is not None: + tx.outgoing_transfer.addresses = [] + + # compare txs + assert len(txs1) == len(txs2) + cls.test_txs_wallet_equality(txs1, txs2) + @classmethod def transfer_cached_info(cls, src: MoneroTxWallet, tgt: MoneroTxWallet) -> None: """Transfer cached wallet transaction info. @@ -265,6 +263,42 @@ def transfer_cached_info(cls, src: MoneroTxWallet, tgt: MoneroTxWallet) -> None: if tgt.outgoing_transfer is not None: tgt.payment_id = src.payment_id + @classmethod + def compare_transfers(cls, txs_transfers_1: dict[str, list[MoneroTransfer]], txs_transfers_2: dict[str, list[MoneroTransfer]]) -> None: + # compare collected transfers per tx for equality + for tx_hash in txs_transfers_1: + tx_transfers1 = txs_transfers_1[tx_hash] + tx_transfers2 = txs_transfers_2[tx_hash] + assert len(tx_transfers1) == len(tx_transfers2) + + # normalize and compare transfers + for i, transfer1 in enumerate(tx_transfers1): + transfer2 = tx_transfers2[i] + + # normalize outgoing transfers + if isinstance(transfer1, MoneroOutgoingTransfer): + assert isinstance(transfer2, MoneroOutgoingTransfer) + + # transfer destination info if known for comparison + if len(transfer1.destinations) > 0: + if len(transfer2.destinations) == 0: + cls.transfer_cached_info(transfer1.tx, transfer2.tx) + elif len(transfer2.destinations) > 0: + cls.transfer_cached_info(transfer2.tx, transfer1.tx) + + # nullify other local wallet data + transfer1.addresses = [] + transfer2.addresses = [] + else: + # normalize incoming transfers + assert isinstance(transfer1, MoneroIncomingTransfer) + assert isinstance(transfer2, MoneroIncomingTransfer) + transfer1.address = None + transfer2.address = None + + # compare transfer equality + AssertUtils.assert_equals(transfer1, transfer2) + @classmethod def test_transfers_equal_on_chain(cls, transfers1: list[MoneroTransfer], transfers2: list[MoneroTransfer]) -> None: """Test transfers equality based on on-chain data. @@ -327,39 +361,21 @@ def test_transfers_equal_on_chain(cls, transfers1: list[MoneroTransfer], transfe tx_transfers2.append(transfer2) - # compare collected transfers per tx for equality - for tx_hash in txs_transfers_1: - tx_transfers1 = txs_transfers_1[tx_hash] - tx_transfers2 = txs_transfers_2[tx_hash] - assert len(tx_transfers1) == len(tx_transfers2) + cls.compare_transfers(txs_transfers_1, txs_transfers_2) - # normalize and compare transfers - for i, transfer1 in enumerate(tx_transfers1): - transfer2 = tx_transfers2[i] - - # normalize outgoing transfers - if isinstance(transfer1, MoneroOutgoingTransfer): - assert isinstance(transfer2, MoneroOutgoingTransfer) - - # transfer destination info if known for comparison - if len(transfer1.destinations) > 0: - if len(transfer2.destinations) == 0: - cls.transfer_cached_info(transfer1.tx, transfer2.tx) - elif len(transfer2.destinations) > 0: - cls.transfer_cached_info(transfer2.tx, transfer1.tx) - - # nullify other local wallet data - transfer1.addresses = [] - transfer2.addresses = [] - else: - # normalize incoming transfers - assert isinstance(transfer1, MoneroIncomingTransfer) - assert isinstance(transfer2, MoneroIncomingTransfer) - transfer1.address = None - transfer2.address = None + @classmethod + def compare_outputs(cls, txs_outputs1: dict[str, list[MoneroOutputWallet]], txs_outputs2: dict[str, list[MoneroOutputWallet]]) -> None: + # compare collected outputs per tx for equality + for tx_hash in txs_outputs2: + tx_outputs1 = txs_outputs1[tx_hash] + tx_outputs2 = txs_outputs2[tx_hash] + assert len(tx_outputs1) == len(tx_outputs2) - # compare transfer equality - AssertUtils.assert_equals(transfer1, transfer2) + # normalize and compare outputs + for i, output1 in enumerate(tx_outputs1): + output2: MoneroOutputWallet = tx_outputs2[i] + assert output1.tx.hash == output2.tx.hash + AssertUtils.assert_equals(output1, output2) @classmethod def test_output_wallets_equal_on_chain(cls, outputs1: list[MoneroOutputWallet], outputs2: list[MoneroOutputWallet]) -> None: @@ -423,14 +439,4 @@ def test_output_wallets_equal_on_chain(cls, outputs1: list[MoneroOutputWallet], tx_outputs2.append(output2) - # compare collected outputs per tx for equality - for tx_hash in txs_outputs2: - tx_outputs1 = txs_outputs1[tx_hash] - tx_outputs2 = txs_outputs2[tx_hash] - assert len(tx_outputs1) == len(tx_outputs2) - - # normalize and compare outputs - for i, output1 in enumerate(tx_outputs1): - output2: MoneroOutputWallet = tx_outputs2[i] - assert output1.tx.hash == output2.tx.hash - AssertUtils.assert_equals(output1, output2) + cls.compare_outputs(txs_outputs1, txs_outputs2)