This fixes some undefined behavior observed in the issue #19728.
The bugs can be reproduced by using corpus added here and the harness added in PR#19259
5086 | @@ -5087,7 +5087,9 @@ bool LoadMempool(CTxMemPool& pool) 5087 | file >> mapDeltas; 5088 | 5089 | for (const auto& i : mapDeltas) { 5090 | - pool.PrioritiseTransaction(i.first, i.second); 5091 | + if(MoneyRange(i.second)){
if(MoneyRange(i.second)) {
Noted..
You may install clang-format and run the https://github.com/bitcoin/bitcoin/tree/master/contrib/devtools#clang-format-diffpy script to preempt whitespace nitpicking.
Concept ACK, will review
Concept ACK: thanks for fixing! :)
In case of CAmount:s outside of the money range: should we perhaps let LoadMempool return false on those to indicate that something went (very) wrong (instead of just skipping PrioritiseTransaction)?
Update: Negative deltas are allowed!
@practicalswift I was thinking along the same line. Also, It seems better to initiate shutdown once something like this happens. As just returning false in LoadMempool doesn't seem to be triggering any catastrophe, and I am not sure what the node is doing about its mempool when loading from disk fails. The node seems to keep on running with just a failure message, My guess is its creates a fresh mempool and override the data in the disk. Which can seem fine, unless someone specifically wants a particular mempool and if the disk data is corrupted they might not notice that something is wrong.
So I made it to initiate shutdown if loadmempool fails. Not sure if that's strictly necessary, looking for suggestions here before I update the PR.
The log
2020-06-27T14:19:55Z Cant load mempool, something went wrong, initiating shutdown.
2020-06-27T14:19:55Z Bound to [::]:18444
2020-06-27T14:19:55Z Bound to 0.0.0.0:18444
2020-06-27T14:19:55Z init message: Loading P2P addresses...
2020-06-27T14:19:55Z Loaded 0 addresses from peers.dat 1ms
2020-06-27T14:19:55Z init message: Starting network threads...
2020-06-27T14:19:55Z net thread start
2020-06-27T14:19:55Z dnsseed thread start
2020-06-27T14:19:55Z 0 addresses found from DNS seeds
2020-06-27T14:19:55Z dnsseed thread exit
2020-06-27T14:19:55Z init message: Done loading
2020-06-27T14:19:55Z addcon thread start
2020-06-27T14:19:55Z opencon thread start
2020-06-27T14:19:55Z torcontrol thread start
2020-06-27T14:19:55Z msghand thread start
2020-06-27T14:19:55Z tor: Thread interrupt
2020-06-27T14:19:55Z opencon thread exit
2020-06-27T14:19:55Z Shutdown: In progress...
2020-06-27T14:19:55Z torcontrol thread exit
2020-06-27T14:19:55Z addcon thread exit
2020-06-27T14:19:55Z net thread exit
2020-06-27T14:19:55Z msghand thread exit
2020-06-27T14:19:55Z scheduler thread exit
2020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) started
2020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) completed (0.00s)
2020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) started
2020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) completed (0.00s)
2020-06-27T14:19:55Z Shutdown: done
Also please give suggestions for a better error message.
@rajarshimaitra This is such an unlikely error that I don't think we need to add any special logic beyond returning false in case of !MoneyRange(…) :)
Update: Negative deltas are allowed!
@practicalswift updated to return failure instead of skipping PrioritiseTransaction.
@rajarshimaitra Great! What about doing the same thing for the other PrioritiseTransaction call -- that is return false in the case of !MoneyRange(i.second) :)
Update: Negative deltas are allowed!
@practicalswift silly me. Should have done that. will push the same.
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
No conflicts as of last run.
5054 | @@ -5055,11 +5055,13 @@ bool LoadMempool(CTxMemPool& pool) 5055 | file >> nFeeDelta; 5056 | 5057 | CAmount amountdelta = nFeeDelta; 5058 | - if (amountdelta) { 5059 | + if (!MoneyRange(amountdelta)) { 5060 | + return false; 5061 | + }else {
}else {
} else {
Done
5088 | @@ -5087,7 +5089,11 @@ bool LoadMempool(CTxMemPool& pool) 5089 | file >> mapDeltas; 5090 | 5091 | for (const auto& i : mapDeltas) { 5092 | - pool.PrioritiseTransaction(i.first, i.second); 5093 | + if(!MoneyRange(i.second)) {
if(!MoneyRange(i.second)) {
if (!MoneyRange(i.second)) {
Done
ACK modulo nits
Could perhaps use a more descriptive PR title and commit message: what about something along the lines of "Fix UBSan warnings triggered when loading corrupt mempool.dat files"? :)
@practicalswift addressed all your comments and rebased.
@rajarshimaitra I think you still want if (amountdelta) { before doing pool.PrioritiseTransaction(tx->GetHash(), amountdelta); in order to not change the existing logic.
I suggest leaving the existing PrioritiseTransaction logic intact in both cases, and simply add two stand-alone MoneyRange checks:
Update: Negative deltas allowed.
Something along the lines of …
if (!MoneyRange(amountdelta)) {
return false;
}
if (amountdelta) {
pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
}
... and ...
if (!MoneyRange(i.second)) {
return false;
}
pool.PrioritiseTransaction(i.first, i.second);
Also, would you mind also updating the PR title to the more descriptive commit message? :)~~
5079 | @@ -5078,7 +5080,11 @@ bool LoadMempool(CTxMemPool& pool) 5080 | file >> mapDeltas; 5081 | 5082 | for (const auto& i : mapDeltas) { 5083 | - pool.PrioritiseTransaction(i.first, i.second); 5084 | + if (!MoneyRange(i.second)) { 5085 | + return false; 5086 | + } else {
nit: No need to change indentation on early-return. (same above)
Sorry, I didn't get it. Can you explain a bit what are you suggesting here?
This was fixed in your last push AFAICT :)
@practicalswift If i understand you correctly this is what you are suggesting?
CAmount amountdelta = nFeeDelta;
if (!MoneyRange(amountdelta)) {
return false;
}
if (amountdelta) {
pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
}
and
for (const auto& i : mapDeltas) {
if (!MoneyRange(i.second)) {
return false;
}
if (i.second) {
pool.PrioritiseTransaction(i.first, i.second);
}
}
Let me know if this seems correct.
@rajarshimaitra Yes for the first case, but for the second case pool.PrioritiseTransaction(i.first, i.second); should be done unconditionally (skip if (i.second)) in order to not change the existing logic :)
Update: Negative deltas are allowed!
Also would you mind changing the PR title to Fix UBSan warnings triggered when loading corrupt mempool.dat files?
Fixed and updated.
ACK XXX
Update: Negative deltas are allowed!
Restarted Travis which failed spuriously :)
Code review ACK 2cc2cbde13072a2d244912aba7df8a97c81646a1
5045 | @@ -5046,11 +5046,14 @@ bool LoadMempool(CTxMemPool& pool) 5046 | file >> nFeeDelta; 5047 | 5048 | CAmount amountdelta = nFeeDelta; 5049 | + if (!MoneyRange(amountdelta)) { 5050 | + return false; 5051 | + }
There is no requirement for deltas to be in MoneyRange... Better use a numeric_limit max
Before I push the commit does this look ok?
CAmount amountdelta = nFeeDelta;
if (amountdelta > numeric_limits<int64_t>::max() || amountdelta < numeric_limits<int64_t>::min()) {
return false;
}
@luke-jr Oh, crap thanks for alerting about that! I naïvely assumed that the bounds of CAmount were defined by MoneyRange(…), but I now understand that this is a case where negative amounts are allowed for a CAmount. I assume the valid range here is [-MAX_MONEY, MAX_MONEY] then?
Should CAmount perhaps be reserved for cases where MoneyRange(…) defines the bounds?
@rajarshimaitra I don't think that is what @luke-jr is suggesting: note that CAmount is int64_t so amountdelta is guaranteed to be in the range you're checking for in the suggested diff ([std:: numeric_limits<int64_t>::min(), std:: numeric_limits<int64_t>::max()]) :)
Oh, right. Well, this sounds quite deep for my grasp and I wouldn't know what exactly to do. Do suggest and will update the PR accordingly.
@rajarshimaitra To allow for negative deltas: what about replacing the MoneyRange(…) usages in this PR with IsValidFeeDelta(…) which could be something along the lines of:
NODISCARD bool IsValidFeeDelta(const CAmount fee_delta) {
return fee_delta >= -MAX_MONEY && fee_delta <= MAX_MONEY;
}
<!--cf906140f33d8803c4a75a2196329ecb-->
🐙 This pull request conflicts with the target branch and needs rebase.
<sub>Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a "draft".</sub>
5052 | if (amountdelta) {
5053 | pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
5054 | }
5055 | TxValidationState state;
5056 | - if (nTime + nExpiryTimeout > nNow) {
5057 | + if (nTime > nNow - nExpiryTimeout) {
Already in ee11a412a537f62aa46e8862678ce2069a2df5b7
Part of this patch is already in ee11a412a537f62aa46e8862678ce2069a2df5b7. The other part I am not sure it fixes the issue. See also #20383 (comment)
Closing due to inactivity