Misc. wallet API and wallet2 changes

This commit is contained in:
thotbot 2021-07-03 22:54:49 +02:00 committed by wowario
parent 45a63f10ba
commit 60ff2f668e
No known key found for this signature in database
GPG Key ID: 24DCBE762DE9C111
12 changed files with 336 additions and 34 deletions

View File

@ -67,7 +67,10 @@ void SubaddressImpl::refresh(uint32_t accountIndex)
clearRows();
for (size_t i = 0; i < m_wallet->m_wallet->get_num_subaddresses(accountIndex); ++i)
{
m_rows.push_back(new SubaddressRow(i, m_wallet->m_wallet->get_subaddress_as_str({accountIndex, (uint32_t)i}), m_wallet->m_wallet->get_subaddress_label({accountIndex, (uint32_t)i})));
m_rows.push_back(new SubaddressRow(i,
m_wallet->m_wallet->get_subaddress_as_str({accountIndex, (uint32_t)i}),
m_wallet->m_wallet->get_subaddress_label({accountIndex, (uint32_t)i}),
m_wallet->m_wallet->get_subaddress_used({accountIndex, (uint32_t)i})));
}
}

View File

@ -199,6 +199,9 @@ void TransactionHistoryImpl::refresh()
ti->m_transfers.push_back({d.amount, d.address(m_wallet->m_wallet->nettype(), pd.m_payment_id)});
}
for (const auto &r: pd.m_rings) {
ti->m_rings.push_back({string_tools::pod_to_hex(r.first), cryptonote::relative_output_offsets_to_absolute(r.second)});
}
m_history.push_back(ti);
}
@ -229,10 +232,15 @@ void TransactionHistoryImpl::refresh()
ti->m_label = pd.m_subaddr_indices.size() == 1 ? m_wallet->m_wallet->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : "";
ti->m_timestamp = pd.m_timestamp;
ti->m_confirmations = 0;
for (const auto &d : pd.m_dests)
for (const auto &d : pd.m_dests) {
{
ti->m_transfers.push_back({d.amount, d.address(m_wallet->m_wallet->nettype(), pd.m_payment_id)});
}
for (const auto &r: pd.m_rings) {
ti->m_rings.push_back({string_tools::pod_to_hex(r.first), cryptonote::relative_output_offsets_to_absolute(r.second)});
}
m_history.push_back(ti);
}

View File

@ -139,6 +139,11 @@ const std::vector<TransactionInfo::Transfer> &TransactionInfoImpl::transfers() c
return m_transfers;
}
const std::vector<std::pair<std::string, std::vector<uint64_t>>> &TransactionInfoImpl::rings() const
{
return m_rings;
}
uint64_t TransactionInfoImpl::confirmations() const
{
return m_confirmations;

View File

@ -63,6 +63,8 @@ public:
virtual uint64_t confirmations() const override;
virtual uint64_t unlockTime() const override;
virtual const std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings() const override;
private:
int m_direction;
bool m_pending;
@ -81,6 +83,7 @@ private:
std::vector<Transfer> m_transfers;
uint64_t m_confirmations;
uint64_t m_unlock_time;
std::vector<std::pair<std::string, std::vector<uint64_t>>> m_rings;
friend class TransactionHistoryImpl;

View File

@ -35,6 +35,7 @@
#include "transaction_history.h"
#include "address_book.h"
#include "subaddress.h"
#include "coins.h"
#include "subaddress_account.h"
#include "common_defines.h"
#include "common/util.h"
@ -166,11 +167,8 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
<< ", burnt: " << print_money(burnt)
<< ", raw_output_value: " << print_money(amount)
<< ", idx: " << subaddr_index);
// do not signal on received tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->moneyReceived(tx_hash, amount - burnt);
m_listener->updated();
}
m_listener->moneyReceived(tx_hash, amount);
m_listener->updated();
}
virtual void on_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx, uint64_t amount, const cryptonote::subaddress_index& subaddr_index)
@ -182,11 +180,8 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
<< ", tx: " << tx_hash
<< ", amount: " << print_money(amount)
<< ", idx: " << subaddr_index);
// do not signal on received tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->unconfirmedMoneyReceived(tx_hash, amount);
m_listener->updated();
}
m_listener->unconfirmedMoneyReceived(tx_hash, amount);
m_listener->updated();
}
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx,
@ -198,11 +193,8 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
<< ", tx: " << tx_hash
<< ", amount: " << print_money(amount)
<< ", idx: " << subaddr_index);
// do not signal on sent tx if wallet is not syncronized completely
if (m_listener && m_wallet->synchronized()) {
m_listener->moneySpent(tx_hash, amount);
m_listener->updated();
}
m_listener->moneySpent(tx_hash, amount);
m_listener->updated();
}
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx)
@ -436,6 +428,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds)
m_refreshEnabled = false;
m_addressBook.reset(new AddressBookImpl(this));
m_subaddress.reset(new SubaddressImpl(this));
m_coins.reset(new CoinsImpl(this));
m_subaddressAccount.reset(new SubaddressAccountImpl(this));
@ -763,6 +756,35 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c
return status() == Status_Ok;
}
bool WalletImpl::recoverDeterministicWalletFromSpendKey(const std::string &path, const std::string &password, const std::string &language, const std::string &spendkey_string)
{
clearStatus();
m_errorString.clear();
m_recoveringFromSeed = true;
m_recoveringFromDevice = false;
// parse spend key
crypto::secret_key spendkey;
if (!spendkey_string.empty()) {
cryptonote::blobdata spendkey_data;
if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key))
{
setStatusError(tr("failed to parse secret spend key"));
return false;
}
spendkey = *reinterpret_cast<const crypto::secret_key*>(spendkey_data.data());
}
try {
m_wallet->generate(path, password, spendkey, true, false);
setSeedLanguage(language);
} catch (const std::exception &e) {
setStatusCritical(e.what());
}
return status() == Status_Ok;
}
bool WalletImpl::close(bool store)
{
@ -1720,6 +1742,137 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const
return createTransactionMultDest(std::vector<string> {dst_addr}, payment_id, amount ? (std::vector<uint64_t> {*amount}) : (optional<std::vector<uint64_t>>()), mixin_count, priority, subaddr_account, subaddr_indices);
}
PendingTransaction *WalletImpl::createTransactionSingle(const string &key_image, const string &dst_addr,
const size_t outputs, PendingTransaction::Priority priority)
{
clearStatus();
// Pause refresh thread while creating transaction
pauseRefresh();
cryptonote::address_parse_info info;
size_t fake_outs_count = m_wallet->adjust_mixin(m_wallet->default_mixin());
uint32_t adjusted_priority = m_wallet->adjust_priority(static_cast<uint32_t>(priority));
PendingTransactionImpl * transaction = new PendingTransactionImpl(*this);
do {
std::vector<uint8_t> extra;
std::string extra_nonce;
vector<cryptonote::tx_destination_entry> dsts;
bool error = false;
crypto::key_image ki;
if (!epee::string_tools::hex_to_pod(key_image, ki))
{
setStatusError(tr("failed to parse key image"));
error = true;
break;
}
if (!cryptonote::get_account_address_from_str_or_url(info, m_wallet->nettype(), dst_addr))
{
setStatusError(tr("Invalid destination address"));
error = true;
break;
}
if (info.has_payment_id) {
if (!extra_nonce.empty()) {
setStatusError(tr("a single transaction cannot use more than one payment id"));
error = true;
break;
}
set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id);
}
if (error) {
break;
}
if (!extra_nonce.empty() && !add_extra_nonce_to_tx_extra(extra, extra_nonce)) {
setStatusError(tr("failed to set up payment id, though it was decoded correctly"));
break;
}
try {
transaction->m_pending_tx = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress,
outputs, fake_outs_count, 0 /* unlock time */, priority, extra);
pendingTxPostProcess(transaction);
if (multisig().isMultisig) {
auto tx_set = m_wallet->make_multisig_tx_set(transaction->m_pending_tx);
transaction->m_pending_tx = tx_set.m_ptx;
transaction->m_signers = tx_set.m_signers;
}
} catch (const tools::error::daemon_busy&) {
// TODO: make it translatable with "tr"?
setStatusError(tr("daemon is busy. Please try again later."));
} catch (const tools::error::no_connection_to_daemon&) {
setStatusError(tr("no connection to daemon. Please make sure daemon is running."));
} catch (const tools::error::wallet_rpc_error& e) {
setStatusError(tr("RPC error: ") + e.to_string());
} catch (const tools::error::get_outs_error &e) {
setStatusError((boost::format(tr("failed to get outputs to mix: %s")) % e.what()).str());
} catch (const tools::error::not_enough_unlocked_money& e) {
std::ostringstream writer;
writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) %
print_money(e.available()) %
print_money(e.tx_amount());
setStatusError(writer.str());
} catch (const tools::error::not_enough_money& e) {
std::ostringstream writer;
writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) %
print_money(e.available()) %
print_money(e.tx_amount());
setStatusError(writer.str());
} catch (const tools::error::tx_not_possible& e) {
std::ostringstream writer;
writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) %
print_money(e.available()) %
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
print_money(e.fee());
setStatusError(writer.str());
} catch (const tools::error::not_enough_outs_to_mix& e) {
std::ostringstream writer;
writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":";
for (const std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) {
writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second;
}
writer << "\n" << tr("Please sweep unmixable outputs.");
setStatusError(writer.str());
} catch (const tools::error::tx_not_constructed&) {
setStatusError(tr("transaction was not constructed"));
} catch (const tools::error::tx_rejected& e) {
std::ostringstream writer;
writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status();
setStatusError(writer.str());
} catch (const tools::error::tx_sum_overflow& e) {
setStatusError(e.what());
} catch (const tools::error::zero_destination&) {
setStatusError(tr("one of destinations is zero"));
} catch (const tools::error::tx_too_big& e) {
setStatusError(tr("failed to find a suitable way to split transactions"));
} catch (const tools::error::transfer_error& e) {
setStatusError(string(tr("unknown transfer error: ")) + e.what());
} catch (const tools::error::wallet_internal_error& e) {
setStatusError(string(tr("internal error: ")) + e.what());
} catch (const std::exception& e) {
setStatusError(string(tr("unexpected error: ")) + e.what());
} catch (...) {
setStatusError(tr("unknown error"));
}
} while (false);
statusWithErrorString(transaction->m_status, transaction->m_errorString);
// Resume refresh thread
startRefresh();
return transaction;
}
PendingTransaction *WalletImpl::createSweepUnmixableTransaction()
{
@ -1839,6 +1992,11 @@ AddressBook *WalletImpl::addressBook()
return m_addressBook.get();
}
Coins *WalletImpl::coins()
{
return m_coins.get();
}
Subaddress *WalletImpl::subaddress()
{
return m_subaddress.get();

View File

@ -46,6 +46,7 @@ class PendingTransactionImpl;
class UnsignedTransactionImpl;
class AddressBookImpl;
class SubaddressImpl;
class CoinsImpl;
class SubaddressAccountImpl;
struct Wallet2CallbackImpl;
class PendingTransactionInfoImpl;
@ -78,6 +79,10 @@ public:
const std::string &address_string,
const std::string &viewkey_string,
const std::string &spendkey_string = "");
bool recoverDeterministicWalletFromSpendKey(const std::string &path,
const std::string &password,
const std::string &language,
const std::string &spendkey_string);
bool recoverFromDevice(const std::string &path,
const std::string &password,
const std::string &device_name);
@ -164,6 +169,10 @@ public:
PendingTransaction::Priority priority = PendingTransaction::Priority_Low,
uint32_t subaddr_account = 0,
std::set<uint32_t> subaddr_indices = {}) override;
PendingTransaction * createTransactionSingle(const std::string &key_image, const std::string &dst_addr,
size_t outputs = 1, PendingTransaction::Priority priority = PendingTransaction::Priority_Low) override;
virtual PendingTransaction * createSweepUnmixableTransaction() override;
bool submitTransaction(const std::string &fileName) override;
virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override;
@ -181,6 +190,7 @@ public:
PendingTransaction::Priority priority) const override;
virtual TransactionHistory * history() override;
virtual AddressBook * addressBook() override;
virtual Coins * coins() override;
virtual Subaddress * subaddress() override;
virtual SubaddressAccount * subaddressAccount() override;
virtual void setListener(WalletListener * l) override;
@ -252,6 +262,7 @@ private:
friend struct Wallet2CallbackImpl;
friend class AddressBookImpl;
friend class SubaddressImpl;
friend class CoinsImpl;
friend class SubaddressAccountImpl;
friend class PendingTransactionInfoImpl;
friend class TransactionConstructionInfoImpl;
@ -265,6 +276,7 @@ private:
std::unique_ptr<Wallet2CallbackImpl> m_wallet2Callback;
std::unique_ptr<AddressBookImpl> m_addressBook;
std::unique_ptr<SubaddressImpl> m_subaddress;
std::unique_ptr<CoinsImpl> m_coins;
std::unique_ptr<SubaddressAccountImpl> m_subaddressAccount;
// multi-threaded refresh stuff

View File

@ -249,6 +249,7 @@ struct TransactionInfo
virtual std::string paymentId() const = 0;
//! only applicable for output transactions
virtual const std::vector<Transfer> & transfers() const = 0;
virtual const std::vector<std::pair<std::string, std::vector<uint64_t>>> & rings() const = 0;
};
/**
* @brief The TransactionHistory - interface for displaying transaction history
@ -311,22 +312,66 @@ struct AddressBook
virtual int lookupPaymentID(const std::string &payment_id) const = 0;
};
/**
* @brief The CoinsInfo - interface for displaying coins information
*/
struct CoinsInfo
{
virtual ~CoinsInfo() = 0;
virtual uint64_t blockHeight() const = 0;
virtual std::string hash() const = 0;
virtual size_t internalOutputIndex() const = 0;
virtual uint64_t globalOutputIndex() const = 0;
virtual bool spent() const = 0;
virtual bool frozen() const = 0;
virtual uint64_t spentHeight() const = 0;
virtual uint64_t amount() const = 0;
virtual bool rct() const = 0;
virtual bool keyImageKnown() const = 0;
virtual size_t pkIndex() const = 0;
virtual uint32_t subaddrIndex() const = 0;
virtual uint32_t subaddrAccount() const = 0;
virtual std::string address() const = 0;
virtual std::string addressLabel() const = 0;
virtual std::string keyImage() const = 0;
virtual uint64_t unlockTime() const = 0;
virtual bool unlocked() const = 0;
virtual std::string pubKey() const = 0;
virtual bool coinbase() const = 0;
};
struct Coins
{
virtual ~Coins() = 0;
virtual int count() const = 0;
virtual CoinsInfo * coin(int index) const = 0;
virtual std::vector<CoinsInfo*> getAll() const = 0;
virtual void refresh() = 0;
virtual void setFrozen(int index) = 0;
virtual void thaw(int index) = 0;
virtual bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) = 0;
};
struct SubaddressRow {
public:
SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label):
SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label, bool _used):
m_rowId(_rowId),
m_address(_address),
m_label(_label) {}
m_label(_label),
m_used(_used) {}
private:
std::size_t m_rowId;
std::string m_address;
std::string m_label;
bool m_used;
public:
std::string extra;
std::string getAddress() const {return m_address;}
std::string getLabel() const {return m_label;}
std::size_t getRowId() const {return m_rowId;}
bool isUsed() const {return m_used;}
};
struct Subaddress
@ -914,6 +959,18 @@ struct Wallet
uint32_t subaddr_account = 0,
std::set<uint32_t> subaddr_indices = {}) = 0;
/*!
* \brief createTransactionSingle creates transaction with single input
* \param key_image key image as string
* \param dst_addr destination address as string
* \param priority
* \return PendingTransaction object. caller is responsible to check PendingTransaction::status()
* after object returned
*/
virtual PendingTransaction * createTransactionSingle(const std::string &key_image, const std::string &dst_addr,
size_t outputs = 1, PendingTransaction::Priority = PendingTransaction::Priority_Low) = 0;
/*!
* \brief createSweepUnmixableTransaction creates transaction with unmixable outputs.
* \return PendingTransaction object. caller is responsible to check PendingTransaction::status()
@ -1008,6 +1065,7 @@ struct Wallet
virtual TransactionHistory * history() = 0;
virtual AddressBook * addressBook() = 0;
virtual Coins * coins() = 0;
virtual Subaddress * subaddress() = 0;
virtual SubaddressAccount * subaddressAccount() = 0;
virtual void setListener(WalletListener *) = 0;
@ -1064,7 +1122,8 @@ struct Wallet
/*
* \brief signMessage - sign a message with the spend private key
* \param message - the message to sign (arbitrary byte data)
* \return the signature
* \param address - the address to make the signature with, defaults to primary address (optional)
* \return the signature, empty string if the address is invalid or does not belong to the wallet
*/
virtual std::string signMessage(const std::string &message, const std::string &address = "") = 0;
/*!
@ -1275,6 +1334,25 @@ struct WalletManager
return createWalletFromKeys(path, password, language, testnet ? TESTNET : MAINNET, restoreHeight, addressString, viewKeyString, spendKeyString);
}
/*!
* \brief recover deterministic wallet from spend key.
* \param path Name of wallet file to be created
* \param password Password of wallet file
* \param language language
* \param nettype Network type
* \param restoreHeight restore from start height
* \param spendKeyString spend key
* \param kdf_rounds Number of rounds for key derivation function
* \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully)
*/
virtual Wallet * createDeterministicWalletFromSpendKey(const std::string &path,
const std::string &password,
const std::string &language,
NetworkType nettype,
uint64_t restoreHeight,
const std::string &spendKeyString,
uint64_t kdf_rounds = 1) = 0;
/*!
* \deprecated this method creates a wallet WITHOUT a passphrase, use createWalletFromKeys(..., password, ...) instead
* \brief recovers existing wallet using keys. Creates a view only wallet if spend key is omitted

View File

@ -127,6 +127,22 @@ Wallet *WalletManagerImpl::createWalletFromKeys(const std::string &path,
return wallet;
}
Wallet *WalletManagerImpl::createDeterministicWalletFromSpendKey(const std::string &path,
const std::string &password,
const std::string &language,
NetworkType nettype,
uint64_t restoreHeight,
const std::string &spendkey_string,
uint64_t kdf_rounds)
{
WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds);
if(restoreHeight > 0){
wallet->setRefreshFromBlockHeight(restoreHeight);
}
wallet->recoverDeterministicWalletFromSpendKey(path, password, language, spendkey_string);
return wallet;
}
Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path,
const std::string &password,
NetworkType nettype,

View File

@ -67,6 +67,13 @@ public:
const std::string &addressString,
const std::string &viewKeyString,
const std::string &spendKeyString = "") override;
virtual Wallet * createDeterministicWalletFromSpendKey(const std::string &path,
const std::string &password,
const std::string &language,
NetworkType nettype,
uint64_t restoreHeight,
const std::string &spendkey_string,
uint64_t kdf_rounds) override;
virtual Wallet * createWalletFromDevice(const std::string &path,
const std::string &password,
NetworkType nettype,

View File

@ -193,7 +193,7 @@ boost::optional<std::string> NodeRPCProxy::get_target_height(uint64_t &height)
auto res = get_info();
if (res)
return res;
height = m_target_height;
height = m_target_height > m_height ? m_target_height : m_height;
return boost::optional<std::string>();
}

View File

@ -1550,6 +1550,14 @@ void wallet2::add_subaddress_account(const std::string& label)
m_subaddress_labels[index_major][0] = label;
}
//----------------------------------------------------------------------------------------------------
bool wallet2::get_subaddress_used(const cryptonote::subaddress_index& index)
{
return std::find_if(m_transfers.begin(), m_transfers.end(),
[this, index](const transfer_details &td) {
return td.m_subaddr_index == index;
}) != m_transfers.end();
}
//----------------------------------------------------------------------------------------------------
void wallet2::add_subaddress(uint32_t index_major, const std::string& label)
{
THROW_WALLET_EXCEPTION_IF(index_major >= m_subaddress_labels.size(), error::account_index_outofbound);
@ -2155,13 +2163,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount;
if (!pool)
{
m_transfers.push_back(transfer_details{});
transfer_details& td = m_transfers.back();
td.m_block_height = height;
td.m_internal_output_index = o;
td.m_global_output_index = o_indices[o];
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
boost::unique_lock<boost::shared_mutex> lock(m_transfers_mutex);
m_transfers.push_back(transfer_details{});
transfer_details& td = m_transfers.back();
td.m_block_height = height;
td.m_internal_output_index = o;
td.m_global_output_index = o_indices[o];
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
td.m_key_image = tx_scan_info[o].ki;
td.m_key_image_known = !m_watch_only && !m_multisig;
if (!td.m_key_image_known)
@ -2258,12 +2267,13 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
uint64_t extra_amount = amount - burnt;
if (!pool)
{
boost::unique_lock<boost::shared_mutex> lock(m_transfers_mutex);
transfer_details &td = m_transfers[kit->second];
td.m_block_height = height;
td.m_internal_output_index = o;
td.m_global_output_index = o_indices[o];
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
td.m_block_height = height;
td.m_internal_output_index = o;
td.m_global_output_index = o_indices[o];
td.m_tx = (const cryptonote::transaction_prefix&)tx;
td.m_txid = txid;
td.m_amount = amount;
td.m_pk_index = pk_index - 1;
td.m_subaddr_index = tx_scan_info[o].received->index;

View File

@ -1007,6 +1007,7 @@ private:
std::string get_subaddress_as_str(const cryptonote::subaddress_index& index) const;
std::string get_address_as_str() const { return get_subaddress_as_str({0, 0}); }
std::string get_integrated_address_as_str(const crypto::hash8& payment_id) const;
bool get_subaddress_used(const cryptonote::subaddress_index& index);
void add_subaddress_account(const std::string& label);
size_t get_num_subaddress_accounts() const { return m_subaddress_labels.size(); }
size_t get_num_subaddresses(uint32_t index_major) const { return index_major < m_subaddress_labels.size() ? m_subaddress_labels[index_major].size() : 0; }
@ -1677,6 +1678,7 @@ private:
static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; }
boost::shared_mutex m_transfers_mutex;
private:
/*!
* \brief Stores wallet information to wallet file.