Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Closes #906
Copied from the issue discussion...
So I think I see how a race condition could exist here, and actually it looks like it could happen with
createTX
accidentally using spent coins, the same way in this casecreateTX
is accidentally using locked coins. The reason I think is because there is no shared lock in the wallet around the two functions that read and write coins intxdb
.When you create a TX (with
smart: false
) ,wallet.fund()
callstxdb.getCoins()
and then filters them withtxdb.filterUnlocked()
-- with a "fund" lock:bcoin/lib/wallet/wallet.js
Lines 1093 to 1100 in 3623d04
If at that same moment, a transaction is broadcast or received by the wallet, the walletDB will call
txdb.insert()
-- with a "write" lock:bcoin/lib/wallet/wallet.js
Lines 1889 to 1896 in 3623d04
Inside
txdb.insert()
a bunch of stuff happens but especially these two things:coins are marked as spent and removed from the credits bucket in the DB
coins are removed from the "locked coins" list
So...
Just looking at the above, I actually kinda wonder why we haven't seen a bug where
wallet.fund()
tries to use coins that, at that same moment, are being marked as spent bytxdb.insert()
. Maybe LevelDB is preventing that from happening concurrently. In which case it might make sense that we see this behavior around locked coins instead (which is just aBufferSet
in memory, no special handling or wrappers).I wonder if @nodar-chkuaselidze has any insight here... could be the solution is to add an extra lock to
fund()
: