Compare commits

..

4 Commits

Author SHA1 Message Date
Some Random Crypto Guy 9570c7a910 Merge branch 'hotfix-v0.9.5a' 2025-03-02 22:26:32 +00:00
Some Random Crypto Guy 23756691b7 Hotfix release to address 2 wallet issues:
1. new wallet "set" parameter "send-change-back-to-subaddress [1|0]"
   The change for a transaction can now be sent back to the main address for the wallet (default)
   or to the sending subaddress
   (* please note that for AUDIT TX type, it will ALWAYS be sent back to the subaddress)

2. fixed erroneous code setting "source_asset" in wallet2::process_new_transaction()
   This bug caused change entries to appear in all TXs when querying using "get_transfer_by_txid"
   RPC method as "in" output types. Whilst this is fully conformant to the Monero Docs that
   describe the method in question, it did not conform to the experience of the RPC method users
   or to traditional Monero RPC output.
2025-03-02 22:15:50 +00:00
Some Random Crypto Guy 3eb986fc51 fixed debug build without audit tool 2025-02-25 15:54:50 +00:00
Some Random Crypto Guy b713a08a81 bumped version, ready for fork; fixed up wallet API method for auditing 2025-02-25 13:04:29 +00:00
11 changed files with 155 additions and 126 deletions
+5 -5
View File
@@ -1,4 +1,4 @@
# Salvium Zero v0.9.4
# Salvium Zero v0.9.5
Copyright (c) 2023-2024, Salvium
Portions Copyright (c) 2014-2023, The Monero Project
@@ -172,7 +172,7 @@ invokes cmake commands as needed.
```bash
cd salvium
git checkout v0.9.4
git checkout v0.9.5
make
```
@@ -251,7 +251,7 @@ Tested on a Raspberry Pi Zero with a clean install of minimal Raspbian Stretch (
```bash
git clone https://github.com/salvium/salvium
cd salvium
git checkout v0.9.4
git checkout v0.9.5
```
* Build:
@@ -370,10 +370,10 @@ application.
cd salvium
```
* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v0.9.4'. If you don't care about the version and just want binaries from master, skip this step:
* If you would like a specific [version/tag](https://github.com/salvium/salvium/tags), do a git checkout for that version. eg. 'v0.9.5'. If you don't care about the version and just want binaries from master, skip this step:
```bash
git checkout v0.9.4
git checkout v0.9.5
```
* If you are on a 64-bit system, run:
+2 -4
View File
@@ -142,8 +142,7 @@ set(blockchain_scanner_private_headers)
monero_private_headers(blockchain_scanner
${blockchain_scanner_private_headers})
if (BUILD_TAG)
else()
if (BUILD_AUDIT)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp" AND NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp")
set(blockchain_audit_sources
blockchain_audit.cpp
@@ -330,8 +329,7 @@ set_property(TARGET blockchain_scanner
OUTPUT_NAME "salvium-blockchain-scanner")
install(TARGETS blockchain_scanner DESTINATION bin)
if (BUILD_TAG)
else()
if (BUILD_AUDIT)
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp" AND NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/blockchain_audit.cpp")
monero_add_executable(blockchain_audit
${blockchain_audit_sources}
Binary file not shown.
+64 -90
View File
@@ -1611,103 +1611,57 @@ bool Blockchain::validate_protocol_transaction(const block& b, uint64_t height,
break;
}
}
/*
const std::map<uint8_t, std::pair<uint64_t, std::pair<std::string, std::string>>> audit_hard_forks = get_config(m_nettype).AUDIT_HARD_FORKS;
uint64_t audit_lock_period = get_config(m_nettype).AUDIT_LOCK_PERIOD;
uint64_t matured_audit_height = height - audit_lock_period - 1;
uint8_t hf = m_hardfork->get_ideal_version(matured_audit_height);
if (audit_hard_forks.find(hf) != audit_hard_forks.end()) {
// Maturing height was during an audit - process accordingly
cryptonote::audit_block_info abi_matured;
ok = get_abi_entry(matured_audit_height, abi_matured);
if (!ok) {
LOG_PRINT_L1("Block at height: " << height << " - failed to obtain audit block information - aborting");
return false;
} else if (abi_matured.locked_coins_this_block == 0) {
LOG_PRINT_L1("Block at height: " << height << " - no audit payouts due - skipping");
} else {
// Iterate over the cached data for audits, calculating the audit payouts due
if (!calculate_audit_payouts(matured_audit_height, audit_payouts)) {
LOG_ERROR("Block at height: " << height << " - Failed to obtain audit payout information - aborting");
return false;
}
}
}
*/
// Check we have the correct number of entries
CHECK_AND_ASSERT_MES(b.protocol_tx.vout.size() == yield_payouts.size() + audit_payouts.size(), false, "Invalid number of outputs in protocol_tx - aborting");
// go through each vout and validate
std::set<crypto::public_key> used_keys;
for (auto& o : b.protocol_tx.vout) {
// gather the output data
uint64_t out_amount;
uint64_t out_unlock_time;
std::string out_asset_type;
//std::set<crypto::public_key> used_keys;
// Merge the yield and audit payouts into an iterable vector
std::vector<std::pair<yield_tx_info, uint64_t>> payouts{yield_payouts};
payouts.insert(payouts.end(), audit_payouts.begin(), audit_payouts.end());
/*
if (hf_version >= HF_VERSION_AUDIT2) {
std::sort(payouts.begin(), payouts.end(), [](const auto& lhs, const auto& rhs) {
// If block heights are different (only possible with mixed AUDIT+STAKE) sort by them first
if (lhs.first.block_height < rhs.first.block_height) return true;
if (lhs.first.block_height > rhs.first.block_height) return false;
// If output keys are different, sort by them second
if (lhs.first.return_address < rhs.first.return_address) return true;
if (lhs.first.return_address > rhs.first.return_address) return false;
// If block heights _and_ output keys are same, sort by amount third
return lhs.second < rhs.second;
});
}
*/
size_t output_idx = 0;
for (auto it = payouts.begin(); it != payouts.end(); it++, output_idx++) {
// Verify the output key
crypto::public_key out_key;
if (o.target.type() == typeid(txout_to_key)) {
txout_to_key out = boost::get<txout_to_key>(o.target);
out_unlock_time = out.unlock_time;
out_asset_type = out.asset_type;
out_key = out.key;
out_amount = o.amount;
} else if (o.target.type() == typeid(txout_to_tagged_key)) {
txout_to_tagged_key out = boost::get<txout_to_tagged_key>(o.target);
out_unlock_time = out.unlock_time;
out_asset_type = out.asset_type;
out_key = out.key;
out_amount = o.amount;
} else {
MERROR("Block at height: " << height << " attempting to add protocol transaction with invalid type " << o.target.type().name());
return false;
}
cryptonote::get_output_public_key(b.protocol_tx.vout[output_idx], out_key);
CHECK_AND_ASSERT_MES(out_key == it->first.return_address, false, "Incorrect output key detected in protocol_tx");
// Check if key has already been seen
if (used_keys.count(out_key) != 0) {
LOG_ERROR("Block at height: " << height << " - Duplicated output key " << out_key << " for protocol TX - aborting");
return false;
}
// Add key to list of already-seen
used_keys.insert(out_key);
// check if there is entry in the yield payouts or audit payouts for this output
std::string expected_output_asset_type = "SAL";
auto found_yield = std::find_if(yield_payouts.begin(), yield_payouts.end(), [&](const std::pair<yield_tx_info, uint64_t>& p) {
return p.first.return_address == out_key;
});
auto found_audit = std::find_if(audit_payouts.begin(), audit_payouts.end(), [&](const std::pair<yield_tx_info, uint64_t>& p) {
return p.first.return_address == out_key;
});
if (found_yield == yield_payouts.end() && found_audit == audit_payouts.end()) {
MERROR("Block at height: " << height << " - Failed to locate output for protocol TX - rejecting block");
return false;
} else if (found_audit == audit_payouts.end()) {
// Verify the output amount
CHECK_AND_ASSERT_MES(b.protocol_tx.vout[output_idx].amount == it->second, false, "Incorrect output amount detected in protocol_tx");
// Found a YIELD entry
CHECK_AND_ASSERT_MES(out_amount == found_yield->second, false, "Incorrect value for protocol TX YIELD amount");
uint8_t hf_yield = m_hardfork->get_ideal_version(found_yield->first.block_height);
if (hf_yield >= HF_VERSION_SALVIUM_ONE_PROOFS)
expected_output_asset_type = "SAL1";
// Verify the output asset type
std::string out_asset_type;
cryptonote::get_output_asset_type(b.protocol_tx.vout[output_idx], out_asset_type);
uint8_t hf_yield = m_hardfork->get_ideal_version(it->first.block_height);
if (hf_yield >= HF_VERSION_SALVIUM_ONE_PROOFS)
CHECK_AND_ASSERT_MES(out_asset_type == "SAL1", false, "Incorrect output asset_type (!= SAL1) detected in protocol_tx");
else
CHECK_AND_ASSERT_MES(out_asset_type == "SAL", false, "Incorrect output asset_type (!= SAL) detected in protocol_tx");
} else if (found_yield == yield_payouts.end()) {
// Found an AUDIT entry
CHECK_AND_ASSERT_MES(out_amount == found_audit->second, false, "Incorrect value for protocol TX AUDIT amount");
uint8_t hf_audit = m_hardfork->get_ideal_version(found_audit->first.block_height);
if (hf_audit >= HF_VERSION_SALVIUM_ONE_PROOFS)
expected_output_asset_type = "SAL1";
} else {
// Duplicate entry in yield + audit?!?!?
MERROR("Block at height: " << height << " - Duplicated YIELD and AUDIT keys found for protocol TX - rejecting block");
return false;
}
// check other fields
CHECK_AND_ASSERT_MES(out_unlock_time == CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "Invalid unlock time on protocol_tx output");
CHECK_AND_ASSERT_MES(expected_output_asset_type == out_asset_type, false, "Incorrect asset type detected for protocol TX ouput - rejecting block");
// Verify the output unlock time
uint64_t out_unlock_time;
cryptonote::get_output_unlock_time(b.protocol_tx.vout[output_idx], out_unlock_time);
CHECK_AND_ASSERT_MES(out_unlock_time == CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW, false, "Invalid output unlock time on protocol_tx output");
}
// Everything checks out
@@ -2003,6 +1957,7 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block,
entry.type = cryptonote::transaction_type::STAKE;
entry.P_change = yield_entry.first.P_change;
entry.return_pubkey = yield_entry.first.return_pubkey;
entry.origin_height = start_height;
protocol_entries.push_back(entry);
}
}
@@ -2046,12 +2001,31 @@ bool Blockchain::create_block_template(block& b, const crypto::hash *from_block,
entry.type = cryptonote::transaction_type::AUDIT;
entry.P_change = audit_entry.first.P_change;
entry.return_pubkey = audit_entry.first.return_pubkey;
entry.origin_height = matured_audit_height;
protocol_entries.push_back(entry);
}
}
break;
}
}
/*
// From v8, we sort the protocol_tx outputs by ORIGIN_HEIGHT, OUTPUT_KEY, AMOUNT
if (b.major_version >= HF_VERSION_AUDIT2) {
std::sort(protocol_entries.begin(), protocol_entries.end(), [](const auto& lhs, const auto& rhs) {
// If origin block heights are different (only possible with mixed AUDIT+STAKE) sort by them first
if (lhs.origin_height < rhs.origin_height) return true;
if (lhs.origin_height > rhs.origin_height) return false;
// If output keys are different, sort by them second
if (lhs.return_address < rhs.return_address) return true;
if (lhs.return_address > rhs.return_address) return false;
// If block heights _and_ output keys are same, sort by amount third
return lhs.amount_burnt < rhs.amount_burnt;
});
}
*/
// Time to construct the protocol_tx
uint64_t protocol_fee = 0;
@@ -6309,7 +6283,7 @@ void Blockchain::cancel()
}
#if defined(PER_BLOCK_CHECKPOINT)
static const char expected_block_hashes_hash[] = "5065d5361119a526b7a45e9e5bdf1d5be86f80e9eb43b0398bf0e47489c81c6d";
static const char expected_block_hashes_hash[] = "131b18108fb3382b4fa82d4eb6cca8f9e1e0ee2aa7893e572361ca0c2c4118e6";
void Blockchain::load_compiled_in_block_hashes(const GetCheckpointsCallback& get_checkpoints)
{
if (get_checkpoints == nullptr || !m_fast_sync)
@@ -66,6 +66,7 @@ namespace cryptonote
uint8_t type;
crypto::public_key P_change;
crypto::public_key return_pubkey;
uint64_t origin_height;
};
//---------------------------------------------------------------
+24 -1
View File
@@ -3200,6 +3200,25 @@ bool simple_wallet::set_freeze_incoming_payments(const std::vector<std::string>
return true;
}
bool simple_wallet::set_send_change_back_to_subaddress(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if (args.size() < 2)
{
fail_msg_writer() << tr("Value not specified");
return true;
}
const auto pwd_container = get_and_verify_password();
if (pwd_container)
{
parse_bool_and_use(args[1], [&](bool r) {
m_wallet->send_change_back_to_subaddress(r);
m_wallet->rewrite(m_wallet_file, pwd_container->password());
});
}
return true;
}
bool simple_wallet::help(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if(args.empty())
@@ -3562,7 +3581,9 @@ simple_wallet::simple_wallet()
"inactivity-lock-timeout <unsigned int>\n "
" How many seconds to wait before locking the wallet (0 to disable).\n"
"freeze-incoming-payments <1|0>\n "
" Whether to have incoming payments automatically frozen, so they cannot be spent erroneously."));
" Whether to have incoming payments automatically frozen, so they cannot be spent erroneously.\n"
"send-change-back-to-subaddress <1|0>\n "
" Whether to have change from transactions sent back subaddresses (1) or to main address (0) (ignored for AUDIT commands)."));
m_cmd_binder.set_handler("encrypted_seed",
boost::bind(&simple_wallet::on_command, this, &simple_wallet::encrypted_seed, _1),
tr("Display the encrypted Electrum-style mnemonic seed."));
@@ -3979,6 +4000,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
success_msg_writer() << "load-deprecated-formats = " << m_wallet->load_deprecated_formats();
success_msg_writer() << "enable-multisig-experimental = " << m_wallet->is_multisig_enabled();
success_msg_writer() << "freeze-incoming-payments = " << m_wallet->is_freeze_incoming_payments_enabled();
success_msg_writer() << "send-change-back-to-subaddress = " << m_wallet->is_send_change_back_to_subaddress_enabled();
return true;
}
else
@@ -4047,6 +4069,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args)
CHECK_SIMPLE_VARIABLE("credits-target", set_credits_target, tr("unsigned integer"));
CHECK_SIMPLE_VARIABLE("enable-multisig-experimental", set_enable_multisig, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("freeze-incoming-payments", set_freeze_incoming_payments, tr("0 or 1"));
CHECK_SIMPLE_VARIABLE("send-change-back-to-subaddress", set_send_change_back_to_subaddress, tr("0 or 1"));
}
fail_msg_writer() << tr("set: unrecognized argument(s)");
return true;
+1
View File
@@ -156,6 +156,7 @@ namespace cryptonote
bool set_load_deprecated_formats(const std::vector<std::string> &args = std::vector<std::string>());
bool set_enable_multisig(const std::vector<std::string> &args = std::vector<std::string>());
bool set_freeze_incoming_payments(const std::vector<std::string> &args = std::vector<std::string>());
bool set_send_change_back_to_subaddress(const std::vector<std::string> &args = std::vector<std::string>());
bool set_persistent_rpc_client_id(const std::vector<std::string> &args = std::vector<std::string>());
bool set_auto_mine_for_rpc_payment_threshold(const std::vector<std::string> &args = std::vector<std::string>());
bool set_credits_target(const std::vector<std::string> &args = std::vector<std::string>());
+1 -1
View File
@@ -1,5 +1,5 @@
#define DEF_SALVIUM_VERSION_TAG "@VERSIONTAG@"
#define DEF_SALVIUM_VERSION "0.9.4"
#define DEF_SALVIUM_VERSION "0.9.5a"
#define DEF_MONERO_VERSION_TAG "release"
#define DEF_MONERO_VERSION "0.18.3.3"
#define DEF_MONERO_RELEASE_NAME "Zero"
+29 -18
View File
@@ -1569,27 +1569,38 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const Monero::transact
extra, subaddr_account, subaddr_indices);
} else {
std::vector<tools::wallet2::pending_tx> m_pending_txs;
for (auto it = subaddr_indices.begin(); it != subaddr_indices.end(); ++it) {
for (const auto subaddr_index : subaddr_indices) {
// Skip this wallet if there is no balance unlocked to audit
const auto unlocked_balance_per_subaddr = m_wallet->unlocked_balance_per_subaddress(subaddr_account, "SAL", true);
if (unlocked_balance_per_subaddr.count(*it) == 0) continue;
std::map<uint32_t, std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> unlocked_balance_per_subaddr = m_wallet->unlocked_balance_per_subaddress(subaddr_account, asset_type, true);
if (unlocked_balance_per_subaddr.count(subaddr_index) == 0) continue;
const auto result = m_wallet->create_transactions_all(
0,
converted_tx_type,
asset_type,
m_wallet->get_subaddress({subaddr_account, *it}),
((*it) > 0),
1,
fake_outs_count,
0 /* unlock_time */,
adjusted_priority,
extra,
subaddr_account,
std::set<uint32_t> {*it}
);
m_pending_txs.insert(m_pending_txs.end(), result.begin(), result.end());
try {
const auto result = m_wallet->create_transactions_all(0,
converted_tx_type,
asset_type,
m_wallet->get_subaddress({subaddr_account, subaddr_index}),
(subaddr_index > 0),
1,
fake_outs_count,
0 /* unlock_time */,
adjusted_priority,
extra,
subaddr_account,
std::set<uint32_t> {subaddr_index}
);
m_pending_txs.insert(m_pending_txs.end(), result.begin(), result.end());
} catch (const std::exception &e) {
// Let's skip this wallet - we have already reported the error
if (unlocked_balance_per_subaddr[subaddr_index].first < 250000000) {
std::ostringstream writer;
writer << boost::format(tr("Subaddress index %u has insufficient funds (%s) to pay for audit")) % subaddr_index % print_money(unlocked_balance_per_subaddr[subaddr_index].first);
setStatusError(writer.str());
}
}
}
transaction->m_pending_tx = m_pending_txs;
}
+25 -7
View File
@@ -1252,6 +1252,7 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std
m_credits_target(0),
m_enable_multisig(false),
m_freeze_incoming_payments(false),
m_send_change_back_to_subaddress(false),
m_pool_info_query_time(0),
m_has_ever_refreshed_from_node(false),
m_allow_mismatched_daemon_version(false)
@@ -2574,12 +2575,12 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
process_unconfirmed(txid, tx, height);
std::string source_asset;
if (use_fork_rules(get_salvium_one_proofs_fork(), 0)) {
if (tx.type == cryptonote::transaction_type::MINER && tx.type == cryptonote::transaction_type::PROTOCOL) {
if (tx.type == cryptonote::transaction_type::MINER || tx.type == cryptonote::transaction_type::PROTOCOL) {
if (use_fork_rules(get_salvium_one_proofs_fork(), 0)) {
source_asset = "SAL1";
} else {
source_asset = "SAL";
}
} else if (tx.type == cryptonote::transaction_type::MINER && tx.type == cryptonote::transaction_type::PROTOCOL) {
source_asset = "SAL";
} else {
source_asset = tx.source_asset_type;
}
@@ -2768,7 +2769,10 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
crypto::public_key pk_locked_coins = crypto::null_pkey;
THROW_WALLET_EXCEPTION_IF(!get_output_public_key(td_origin.m_tx.vout[td_origin.m_internal_output_index], pk_locked_coins), error::wallet_internal_error, "Failed to get output public key for locked coins");
// At this point, we need to clear the "locked coins" count, because otherwise we will be counting yield stakes twice in our balance
THROW_WALLET_EXCEPTION_IF(!m_locked_coins.erase(pk_locked_coins), error::wallet_internal_error, "Failed to remove protocol_tx entry from m_locked_coins");
//THROW_WALLET_EXCEPTION_IF(!m_locked_coins.erase(pk_locked_coins), error::wallet_internal_error, "Failed to remove protocol_tx entry from m_locked_coins");
if (!m_locked_coins.erase(pk_locked_coins)) {
LOG_ERROR("Failed to remove protocol_tx entry from m_locked_coins - possible duplicate output key detected");
}
}
}
}
@@ -5141,6 +5145,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee:
value2.SetInt(m_freeze_incoming_payments ? 1 : 0);
json.AddMember("freeze_incoming_payments", value2, json.GetAllocator());
value2.SetInt(m_send_change_back_to_subaddress ? 1 : 0);
json.AddMember("send_change_back_to_subaddress", value2, json.GetAllocator());
// Serialize the JSON object
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
@@ -5288,6 +5295,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_credits_target = 0;
m_enable_multisig = false;
m_freeze_incoming_payments = false;
m_send_change_back_to_subaddress = false;
m_allow_mismatched_daemon_version = false;
}
else if(json.IsObject())
@@ -5527,6 +5535,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st
m_enable_multisig = field_enable_multisig;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, freeze_incoming_payments, int, Int, false, false);
m_freeze_incoming_payments = field_freeze_incoming_payments;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, send_change_back_to_subaddress, int, Int, false, false);
m_send_change_back_to_subaddress = field_send_change_back_to_subaddress;
}
else
{
@@ -10241,8 +10251,16 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry
cryptonote::tx_destination_entry change_dts = AUTO_VAL_INIT(change_dts);
change_dts.amount = found_money - needed_money;
change_dts.asset_type = source_asset;
change_dts.addr = get_subaddress({subaddr_account, subaddr_index});
change_dts.is_subaddress = subaddr_account != 0 || subaddr_index != 0;
// Lazy devs can't be bothered to code a proper solution for get_transfer_by_txid,
// so we have to provide a hack for them and their poor code.
if (m_send_change_back_to_subaddress || tx_type == cryptonote::transaction_type::AUDIT) {
change_dts.addr = get_subaddress({subaddr_account, subaddr_index});
change_dts.is_subaddress = subaddr_account != 0 || subaddr_index != 0;
} else {
change_dts.addr = get_subaddress({subaddr_account, 0});
change_dts.is_subaddress = subaddr_account != 0;
}
change_dts.is_change = true;
splitted_dsts.push_back(change_dts);
+3
View File
@@ -1450,6 +1450,8 @@ private:
void enable_multisig(bool enable) { m_enable_multisig = enable; }
bool is_freeze_incoming_payments_enabled() const { return m_freeze_incoming_payments; }
void freeze_incoming_payments(bool enable) { m_freeze_incoming_payments = enable; }
bool is_send_change_back_to_subaddress_enabled() const { return m_send_change_back_to_subaddress; }
void send_change_back_to_subaddress(bool enable) { m_send_change_back_to_subaddress = enable; }
bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; }
void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; }
@@ -1982,6 +1984,7 @@ private:
uint64_t m_credits_target;
bool m_enable_multisig;
bool m_freeze_incoming_payments;
bool m_send_change_back_to_subaddress;
bool m_allow_mismatched_daemon_version;
// Aux transaction data from device