-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wallet2: fix rescanning tx via scan_tx [release] #8566
Conversation
I built the GUI with this PR. I confirmed the issue (duplicate entries in transactions with "Unknown" amount displayed after scanning a tx multiple times). This PR fixes the problem in the GUI, (also when tx is pending :) ) Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggested a few cosmetic & improvement comments.
src/wallet/wallet2.cpp
Outdated
@@ -2217,10 +2217,20 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote | |||
} | |||
else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx_scan_info[o].amount) | |||
{ | |||
bool rescanning_tx = txid == m_transfers[kit->second].m_txid; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool rescanning_tx = (txid == m_transfers[kit->second].m_txid);
( ) helps readability.
src/wallet/wallet2.cpp
Outdated
@@ -2230,6 +2240,21 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote | |||
THROW_WALLET_EXCEPTION_IF(amount_iterator == tx_amounts_this_out.end(), | |||
error::wallet_internal_error, "Unexpected values of new and old outputs"); | |||
tx_amounts_this_out.erase(amount_iterator); | |||
if (rescanning_tx) | |||
{ | |||
if (tx_amounts_this_out.empty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tx_amounts_this_out - the instance name is strange. Good to be clear as its not self-descriptive.
src/wallet/wallet2.cpp
Outdated
{ | ||
transfer_details &td = m_transfers[kit->second]; | ||
LOG_PRINT_L0("Received money: " << print_money(td.m_amount) << ", with tx: " << txid); | ||
if (0 != m_callback) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comparing with NULL is a better practice, as we're comparing an instance.
and adding a braces even on a single IF is no harm, avoids confusion.
src/wallet/wallet2.cpp
Outdated
@@ -2230,6 +2240,21 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote | |||
THROW_WALLET_EXCEPTION_IF(amount_iterator == tx_amounts_this_out.end(), | |||
error::wallet_internal_error, "Unexpected values of new and old outputs"); | |||
tx_amounts_this_out.erase(amount_iterator); | |||
if (rescanning_tx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under the same "else if" block, you do..
else if (m_transfers[kit->second].m_spent || m_transfers[kit->second].amount() >= tx_scan_info[o].amount)
{
..
if (!rescanning_tx) // 2221
{
}
else
{
}
..
if (rescanning_tx) // 2243
{
}
rescanning_tx is being assigned only once. I see line 2243-2257 is independent & code could be optimized by moving it to line 2221, like:
if (rescanning_tx)
{
..
}
else // place code for if (!rescanning_tx) case
{
LOG_ERROR("Public key " << epee::string_tools::pod_to_hex(kit->first) // line 2223
..
}
.. // line 2234-2242 (common)
basically, the code could be better optimized imo.
tbh I'd skip the entire thing, partly processing seems like a recipe for trouble. Especially if the new scan results do not agree with the previous ones: I see you test amount, but it's not the only thing that could be different. And if it is different, it's a bug somewhere which isn't helped by the testing anyway. And return some bool to the higher level caller for "already known" so the user knows. |
I'm no longer seeing extra transfers with this PR, so that's good. I do see a couple differences between scanned txs (left) versus regularly synced txs (right). Otherwise the balance is tallying correctly. Looks like the only things missing are the number of confirmations (which should be knowable since the wallet has a connection to the daemon), and detecting if it's a miner tx. |
@moneromooo-monero this was my first thought too. When I looked into it, I think that will be fairly involved to do correctly unless I'm missing something. AFAICT will need to check This PR seemed like the best quick fix that gets us to a better spot, although the concern for partly processing causing trouble is warranted I think. It seems the best move is to just do it all correctly now. @woodser seems these are existing issues. Going to take me a bit to get to it but what I have so far in case someone wants to pick it up:
monero/src/wallet/wallet_rpc_server.cpp Line 362 in c9cfa25
Line 1106 in c9cfa25
Line 2621 in c9cfa25
And then confirmations are set to 0 if the tx's height >= monero/src/wallet/wallet_rpc_server.cpp Lines 97 to 98 in c9cfa25
As suggested, probably makes sense to use the daemon's height instead to set confirmations.
|
Hoping this can be worked out, because it's quite a useful feature. But either way, this PR makes the existing |
Approach from @moneromooo-monero to avoid trouble from calling 1: If a user tries to call The UX downside is that a user may end up skipping their txs, and would need to do a full wallet rescan to fix it:
A future PR could add the ability for a user to call |
Doesn't this make |
I see what you mean. Sounds like this would have to be the way to go as part of the approach then:
|
Clarifying the approach: 1: If a user calls And to clarify the trouble the approach is seeking to avoid: scanning txs out of order messes up the Line 3761 in fc907a9
This approach both prevents incorrect duplicate insertions when re-scanning, and ensures a correctly ordered transfers container. |
I haven't deeply looked at this code. But from a quick look, can't the |
To be honest, |
A better answer: assume tx2 spends an output from a prior tx1. When we call Lines 2315 to 2355 in fc907a9
Therefore |
de8e6ec
to
8b9a833
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allowing pool txes seem odd, otherwise looks good.
src/wallet/wallet2.cpp
Outdated
for (size_t i = slice; i < slice + ntxes; ++i) | ||
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids[i])); | ||
req.txs_hashes.push_back(epee::string_tools::pod_to_hex(txids_no_dups[i])); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems a bit unfortunate to add half a dozen lines of pointless rename.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was trying to de-dup in here since I noticed the RPC server doesn't de-dup, updated to just use an unordered_set
for txids passed to scan_tx
instead
src/wallet/wallet2.cpp
Outdated
// sort tx_entries by height from lowest height to highest; pool txs to the back | ||
auto cmp_tx_entry = [](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& l, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& r) | ||
{ return l.block_height < r.block_height && !l.in_pool; }; | ||
std::sort(sorted_tx_entries.tx_entries.begin(), sorted_tx_entries.tx_entries.end(), cmp_tx_entry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You seem to be allowing txes not on the chain. Why ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From DM's:
moneromooo: Why do you allow pool txes ?
jberman: Why not? It allows em today too
moneromooo: Because it seems like complication for nohting to me.
moneromooo: But if it works, fine.
moneromooo: I mean, they'd be scanned right after or before already. And there could be a race here if you're not careful.
jberman: Seems reasonable. I don't mind just not processing pool txids and limiting the complexity. It seems the primary use case for the feature was/is restore wallet from higher height -> scan_tx all old txs
moneromooo: Well, if it works now, I'm fine with it. It just seemed odd so I mentioned it.
I don't have a strong opinion on this. I figure trying to maintain the API's behavior is reasonable here and I don't see what exactly is wrong with it as is. Going to keep it unless there's more push back
src/wallet/wallet2.cpp
Outdated
return sorted_tx_entries; | ||
} | ||
//---------------------------------------------------------------------------------------------------- | ||
std::vector<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry> merge_sorted_tx_entries(std::vector<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry> l, std::vector<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry> r) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like there is no need for the copying.
tests/functional_tests/transfer.py
Outdated
self.wallet[i] = Wallet(idx = i) | ||
# close the wallet if any, will throw if none is loaded | ||
try: self.wallet[i].close_wallet() | ||
except: pass | ||
res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) | ||
res = self.wallet[i].restore_deterministic_wallet(seed = SEEDS[i]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seriously ? :D
6a3c28b
to
414ada3
Compare
Fixed an issue in my code where sweeps wouldn't re-process when calling |
Squashed and rebased. Also noting #8617 is dependent on this code. |
src/wallet/wallet2.cpp
Outdated
//---------------------------------------------------------------------------------------------------- | ||
std::vector<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry> merge_sorted_tx_entries(const std::vector<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry> &l, const std::vector<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry> &r) | ||
{ | ||
std::vector<cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry> merged_txs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
merged_txs.reserve(l.size() + r.size());
src/wallet/wallet2.cpp
Outdated
cryptonote::block_header_response wallet2::get_block_header_by_height(uint64_t height) | ||
{ | ||
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::request req = AUTO_VAL_INIT(req); | ||
cryptonote::COMMAND_RPC_GET_BLOCK_HEADER_BY_HEIGHT::response res = AUTO_VAL_INIT(res); | ||
|
||
bool r; | ||
{ | ||
const boost::lock_guard<boost::recursive_mutex> lock{m_daemon_rpc_mutex}; | ||
req.height = height; | ||
uint64_t pre_call_credits = m_rpc_payment_state.credits; | ||
req.client = get_client_signature(); | ||
r = net_utils::invoke_http_json_rpc("/json_rpc", "getblockheaderbyheight", req, res, *m_http_client, rpc_timeout); | ||
if (r && res.status == CORE_RPC_STATUS_OK) | ||
check_rpc_cost("getblockheaderbyheight", res.credits, pre_call_credits, COST_PER_BLOCK_HEADER); | ||
} | ||
|
||
THROW_WALLET_EXCEPTION_IF(!r, tools::error::no_connection_to_daemon, "getblockheaderbyheight"); | ||
THROW_WALLET_EXCEPTION_IF(res.status == CORE_RPC_STATUS_BUSY, tools::error::daemon_busy, "getblockheaderbyheight"); | ||
THROW_WALLET_EXCEPTION_IF(res.status != CORE_RPC_STATUS_OK, tools::error::wallet_generic_rpc_error, "getblockheaderbyheight", m_trusted_daemon ? res.status : "daemon error"); | ||
return res.block_header; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind putting this as a method in NodeRPCProxy
instead of here please? See #8605 if you want an example of what I mean. I'm trying to centralize all the raw RPC invocation code into one place.
When testing with restore deterministic wallet / height == 'now': passing the wallets entire in/out history (~30 txids) in a list to scan_tx i sometimes encounter this error:
The times this didn't happen , the get_transfers of the original / restored version are indeed identical |
Looks like some Edit 1: The only Edit 2: There is a |
@plowsof This didn't happen to be a multisig wallet, did it? |
edit: jberman has found the cause and has/is implementing a fix @jeffro256 just a normal wallet which has 1 unspent output and 33 txids (in and out payments) example here https://paste.debian.net/1258259/ load existing wallet -> get list of txids -> attempt to scan them all. sometimes line 69 (first scan) will not complain - sometimes it will - if the 2nd scan throws the error (even though the first scan completed) it makes my balance 0 at the end. hopefully someone can reproduce, ive tried messing with ordering of txids but doesn't seem to make a difference. |
ef42c8b
to
304e64c
Compare
The issue was: when re-scanning a tx at a height lower than the wallet's latest saved checkpoint Fixed by making sure to only call I also noticed an off-by-1 bug where if the height of the tx to re-scan was the same as the wallet's latest saved checkpoint, after re-processing, it wouldn't re-attach the lowest detached block back to the locally saved Good bugs to catch @plowsof |
In discussion with @selsta and @plowsof, one of the common use cases for Since I added the requirement that the feature be used with trusted daemons in this PR, GUI simple mode users would lose a valuable support feature. Proposed solution for untrusted daemons
This maintains Additional detailThe reason I added the requirement of trusted nodes in the first place in this PR is because I decided to re-request all of a user's txs from the daemon after the one being scanned in order to re-process all txs in order. The reason I needed to request those txs from the daemon in order to re-process them is because the wallet throws away transaction data after processing a tx and saving the tx to the wallet (specifically RCT data no longer needed like the encrypted amount). Thus, in order to re-process these transactions via Longer term, a larger re-write could seek to avoid needing to do all the re-processing inside |
Doesn't this still give away to the untrusted daemon which transaction you're asking to scan, with the implication that you're probably involved with it (likely receiver)? |
Yep, here's how the feature is exposed to a GUI user today: The implications of exposing txs beyond just the single tx the user requests to scan are more severe and less intuitive. A user could in theory reveal their entire transaction history to a daemon just by requesting to re-scan the first tx in their wallet for example. |
Updated to allow scanning the wallet's most recent tx when connected to an untrusted daemon. |
Tested the new changes. Confirmed:
Thank you! 👏 here is my test script |
couldn't the privacy concerns (of using this with a remote node) be assuaged by using a block height instead of a specific transaction hash? First, there's the basket of potential outputs in the block itself, plus you could double-down and request 10 other random blocks to scan. Or 15. So its like a ring-signature of blocks. But not really, because its not. But you know what i mean. Sure, it's gonna cost more in processing and bandwidth than 1 specific tx. But its a whole lot less than "hey lets just scan the whole blockchain!" |
I think using heights instead of hashes in that way would improve the privacy profile of the feature when using an untrusted daemon. Plus it's arguably a UX improvement: as @hMihaiDavid mentioned in #7291, it's easier to write block heights on a paper backup together with a seed, versus writing hashes (though on the downside, it may be a bit harder to cleanly communicate the difference between the "restore height" and a feature to "rescan tx at just this height" in the UI). As it relates to this PR, I would prefer to keep this PR as is since:
I think it's worth re-considering adding the ability to scan txs via height(s) in the future, and moving away from using hashes in the GUI for future PR(s). |
Just checking on this. Anything preventing progress to merge? |
tests/functional_tests/transfer.py
Outdated
|
||
test = 'Checking scan_tx on outgoing pool tx' | ||
for attempt in range(2): # test re-scanning | ||
print(test + ' (attempt ' + str(attempt+1) + ')') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftover debug trace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clarified this print statement in the latest. I put a print statement here on purpose because the first attempt is testing that scan_tx
works on first attempt, then the second attempt is testing that re-scanning works, so the print statement helps clarify it's testing different things
tests/functional_tests/transfer.py
Outdated
|
||
test = 'Checking scan_tx on incoming pool tx' | ||
for attempt in range(2): # test re-scanning | ||
print(test + ' (attempt ' + str(attempt+1) + ')') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftover debug trace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above
src/wallet/wallet2.cpp
Outdated
// sort tx_entries by height from lowest height to highest; pool txs to the back | ||
auto cmp_tx_entry = [](const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& l, const cryptonote::COMMAND_RPC_GET_TRANSACTIONS::entry& r) | ||
{ return l.block_height < r.block_height && !l.in_pool; }; | ||
std::sort(sorted_tx_entries.tx_entries.begin(), sorted_tx_entries.tx_entries.end(), cmp_tx_entry); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will only sort partially, if two of the txes are in the same block (or are both in the txpool). Do you care ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any code that relies on processing txs in the order they appear in a block (or in the pool), just by "height order." So this should suffice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could screw up cold signing if the m_transfers vector is ordered differently between hot and cold wallets (inputs are refered to by index IIRC). Possibly same for multisig.
All theoretical though. But having m_transfers shuffle itself a bit seems like the kind of bad idea that comes back to bite us later. I suppose it's nothing that can't be fixed later if it does come back though so not a show stopper I suppose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I missed that. Re-ordering txs like this can cause importing key images from cold wallet to hot wallet to fail (I haven't checked pool/multisig behavior).
This is unfortunate. I don't see a nice way to handle this here without the daemon returning additional data that enables the client to sort txs in the same block (or client re-fetches entire blocks). Checking output_indices
seems it would only work for RCT txs, not pre-RCT. Do you see a nice way to handle this?
EDIT: probably what you were thinking: if a tx is re-scanned, I could be sure to keep its position in m_transfers
static. Still thinking on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Multisig has the same issue, you were correct there too (get_multisig_composite_key_image
relies on m_transfers
to be in order).
For a 2/2 multisig, assume wallet A has scanned up to chain tip normally, and wallet B restores at a height > restore height and then uses scan_tx([tx2, tx1])
to scan txs. If tx2 and tx1 are in the same block and wallet B calls scan_tx
with the txs out of order, then wallet B will be borked.
Only way to prevent wallet B from getting borked here is for both wallets to order the txs in a consistent way, which goes back to my point above:
I don't see a nice way to handle this here without the daemon returning additional data that enables the client to sort txs in the same block (or client re-fetches entire blocks)
Curious if you've got ideas here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think you can. Keeping existing m_transfers order only works if you've not scanned those before (and different multisig participants might end up with different orders even if you keep previous order within a wallet).
However, getting more data from the daemon isn't a problem since you already get the txes, so getting the blocks they're in does not leak any more information, and it should not be too much extra data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so getting the blocks they're in does not leak any more information, and it should not be too much extra data.
Done in the latest
src/wallet/wallet2.cpp
Outdated
const wallet2::payment_container &payments, const serializable_unordered_map<crypto::hash, wallet2::confirmed_transfer_details> &confirmed_txs) | ||
{ | ||
for (const auto &td : transfers) | ||
if (td.m_block_height >= height && expected_txids.find(td.m_txid) == expected_txids.end()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has >= for a function named "above", intended ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to has_unexpected_tx_at_height_or_above
src/wallet/wallet2.cpp
Outdated
|
||
// If the highest scan_tx height > the wallet's known scan height, then the | ||
// wallet scanner should continue scanning from that tx's height and "pretend" | ||
// it scanned all txs up to that height. This gurantees txs are processed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"guarantees"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated as per this suggestion
src/wallet/wallet2.cpp
Outdated
process_scan_txs(txs_to_scan, txs_to_reprocess, tx_hashes_to_reprocess, dbd); | ||
reattach_blockchain(m_blockchain, dbd); | ||
|
||
// If the highest scan_tx height > the wallet's known scan height, then the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this comment mostly, but not entirely, conveys the rationale for skipping forward in the blockchain. I suggest:
If the highest scan_tx height exceeds the wallet's known scan height, then the wallet should skip ahead to the scan_tx's height in order to service the request in a timely manner. Skipping unrequested transactions avoids generating sequences of calls to process_new_transaction which process transactions out-of-order, relative to their order in the blockchain, as the process_new_transaction implementation requires transactions to be processed in blockchain order.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of renaming and otherwise spammy changes in that new patch, but the core seems to do what it says, so approved given I was fine with the previous one except the sorting.
- Detach & re-process txs >= lowest scan height - ensures that if a user calls scan_tx(tx1) after scanning tx2, the wallet correctly processes tx1 and tx2 - if a user provides a tx with a height higher than the wallet's last scanned height, the wallet will scan starting from that tx's height - scan_tx requires trusted daemon iff need to re-process existing txs: in addition to querying a daemon for txids, if a user provides a txid of a tx with height *lower* than any *already* scanned txs in the wallet, then the wallet will also query the daemon for all the *higher* txs as well. This is likely unexpected behavior to a caller, and so to protect a caller from revealing txid's to an untrusted daemon in an unexpected way, require the daemon be trusted.
sanity checked the behaviour of adding missing "latest" / "oldest" / scanned multiple tx's - and 'it works' after squashing still :) |
Issues the PR solves with
scan_tx
scan_tx
on already processed received txs causes duplicates. (scan_tx
causes extra transfers with fee=0 #8531)scan_tx
.get_transfers
will return inaccurate num confirmation data for that tx until the wallet refreshes.scan_tx
with that tx before the wallet refreshes, the tx will be in a state of both "unconfirmed" and "confirmed" until the wallet refreshes.UX changes the PR brings
scan_tx
now requires a trusted daemon edit: if the user attempts to scan a tx that is not their most recent tx. I chose to add this requirement because if a user passes a txid intoscan_tx
that has a height lower than any txs the wallet already processed, then the wallet will re-request all of the wallet's already processed txs from the daemon. This reveals more of a user's txs to the daemon than the user might otherwise expect. Given the way the code is structured, it would seem the simplest solution to this is to require the feature be used with a trusted daemon.scan_tx
, or userescan_blockchain
.Future considerations
scan_tx(tx2)
first beforescan_tx(tx1)
. As is, the wallet is unable to figure out that tx2 spends from tx1 in this case (the user has to re-scan tx2 after tx1). In order to solve this, the wallet would need to query the daemon to see if tx1's associated key image(s) have been spent and in what tx(s).get_transfers
, if a daemon is connected, query the daemon to retrieve the latest height and block reward rather than use the wallet's latest state.scan_tx
with height > the wallet's known scan height, don't callrefresh
insidescan_tx
. In this case,scan_tx
waits until the wallet is sync'd or the user stops the wallet before responding. Usefast_refresh
instead, or just don't do this at all onceget_transfers
uses the daemon's latest height and block reward.