Calculating a balance
For balance calculation we iterate mapWallet
and add values to a Balance
struct.
struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain
CAmount m_watchonly_trusted{0};
CAmount m_watchonly_untrusted_pending{0};
CAmount m_watchonly_immature{0};
};
We do some caching during iteration so that we avoid re-calculating the same values for multiple transactions.
Calculating the above requires using TxSpends
and IsMine
.
When a new transaction involving the wallet takes place, really what happens is that it’s marked as DIRTY
, which deletes the cached entry for the parent transaction. This means that the next time GetBalance()
is called, debit
is recalculated correctly. This Bitcoin Core PR review club goes into more detail on coins being marked as DIRTY
and FRESH
in the cache.
TxSpends
is calculated by looking at the outpoints in the transaction itself.
COutput
s are ephemeral — we create them, perform another operation with them and discard them. They are stored in availableCoins
which is recreated when calling functions such as GetAvailableBalance()
, ListCoins()
and CreateTransactionInternal()
.
In a spending transaction all inputs have their corresponding OutPoints
, and we map these to spending transactions in TxSpends
.
We assume anything (i.e. transactions) that reach the wallet have already been validated by the node and we therefore blindly assume that it is valid in wallet code. |
If a transaction is our own we check for validity with testMempoolAccept
before submitting to the P2P network.
IsMine
For DSPKM running IsMine()
is really simple: descriptors generate a list of ScriptPubKeys, and, if the SPK we are interested in is in the list, then it’s ours.
IsMine
returns an enum. This is used as a return value, a filter and set of flags simultaneously. There is more background on the general IsMine
semantics in the v0.21.0 release notes.
LSPKM can have watch-only and spendable flags set at the same time, but DSPKM is either or, because descriptor wallets do not allow mixtures of spendable and watch-only keys in the same SPKM. Because Legacy wallets are all key-based, we will need to see if a script could have been generated by one of our keys; what type of script it is; and if we have a (private) key for it.
For Legacy watch-only wallets we simply check "do we have this script stored as a script?" (where CScripts
in the database are our watch-only scripts)". If we don’t have a CKey
for a script but it exists in mapScripts
then it’s implicitly watch-only.
A problem with this current method of IsMine
for legacy wallets is that it’s tough to figure out what your wallet considers "Mine" — it’s probably a finite set, but maybe not…
Another consideration is that the LSPKM IsMine
includes P2PK outputs — which don’t have addresses! This un-enumerability can be an issue in migration of Legacy to Descriptor wallets.
There is also the possibility that someone can mutate address to different address type and you will still see it as IsMine
. E.g. mutate P2PK into P2PKH address and wallet will still detect.
With descriptors we only look for scripts explicitly. With descriptor wallets IsMine
might not recognise script hashes from scripts, because it was not told to watch for them and consider them as belonging to it.
We use the IsMine
filters in many places, primarily to distinguish between spendable and watch-only:
IsMine::All
-
spendable and watch-only (use for legacy wallet)
IsMine::Used
-
not used by
IsMine
, but instead used as a filter for tracking when addresses have been reused.
See the section on how wallets determine whether transactions belong to them using the enum for more in-depth information.
Conflict tracking
Conflict tracking is related to changing the state as the mempool tells us about conflicting transactions.
mapTxSpends
is a multimap which permits having the same COutPoint
mapping to two transactions. (i.e. two transactions spending the same input) This is how we can tell if things are conflicted: look up an outpoint and check to see how many transactions are there, if > 1 then we know that there was a conflict.
If there is a conflict we can look up the wallet transaction and see what state it’s in, and we can be sure about whether it is currently or previously conflicted.
Conflict tracking is particularly relevant for coin selection…