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)){
0 if(MoneyRange(i.second)) {
clang-format
and run the https://github.com/bitcoin/bitcoin/tree/master/contrib/devtools#clang-format-diffpy script to preempt whitespace nitpicking.
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
02020-06-27T14:19:55Z Cant load mempool, something went wrong, initiating shutdown.
12020-06-27T14:19:55Z Bound to [::]:18444
22020-06-27T14:19:55Z Bound to 0.0.0.0:18444
32020-06-27T14:19:55Z init message: Loading P2P addresses...
42020-06-27T14:19:55Z Loaded 0 addresses from peers.dat 1ms
52020-06-27T14:19:55Z init message: Starting network threads...
62020-06-27T14:19:55Z net thread start
72020-06-27T14:19:55Z dnsseed thread start
82020-06-27T14:19:55Z 0 addresses found from DNS seeds
92020-06-27T14:19:55Z dnsseed thread exit
102020-06-27T14:19:55Z init message: Done loading
112020-06-27T14:19:55Z addcon thread start
122020-06-27T14:19:55Z opencon thread start
132020-06-27T14:19:55Z torcontrol thread start
142020-06-27T14:19:55Z msghand thread start
152020-06-27T14:19:55Z tor: Thread interrupt
162020-06-27T14:19:55Z opencon thread exit
172020-06-27T14:19:55Z Shutdown: In progress...
182020-06-27T14:19:55Z torcontrol thread exit
192020-06-27T14:19:55Z addcon thread exit
202020-06-27T14:19:55Z net thread exit
212020-06-27T14:19:55Z msghand thread exit
222020-06-27T14:19:55Z scheduler thread exit
232020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) started
242020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) completed (0.00s)
252020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) started
262020-06-27T14:19:55Z FlushStateToDisk: write coins cache to disk (0 coins, 0kB) completed (0.00s)
272020-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!
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!
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
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 {
0 }else {
1 } else {
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)) {
0 if(!MoneyRange(i.second)) {
1 if (!MoneyRange(i.second)) {
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”? :)
@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 …
0if (!MoneyRange(amountdelta)) {
1 return false;
2}
3if (amountdelta) {
4 pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
5}
… and …
0if (!MoneyRange(i.second)) {
1 return false;
2}
3pool.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)
@practicalswift If i understand you correctly this is what you are suggesting?
0CAmount amountdelta = nFeeDelta;
1 if (!MoneyRange(amountdelta)) {
2 return false;
3 }
4 if (amountdelta) {
5 pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
6 }
and
0for (const auto& i : mapDeltas) {
1 if (!MoneyRange(i.second)) {
2 return false;
3 }
4 if (i.second) {
5 pool.PrioritiseTransaction(i.first, i.second);
6 }
7 }
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
?
ACK XXX
Update: Negative deltas are allowed!
Restarted Travis which failed spuriously :)
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+ }
MoneyRange
… Better use a numeric_limit max
Before I push the commit does this look ok?
0CAmount amountdelta = nFeeDelta;
1 if (amountdelta > numeric_limits<int64_t>::max() || amountdelta < numeric_limits<int64_t>::min()) {
2 return false;
3 }
@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()]
) :)
@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:
0NODISCARD bool IsValidFeeDelta(const CAmount fee_delta) {
1 return fee_delta >= -MAX_MONEY && fee_delta <= MAX_MONEY;
2}
🐙 This pull request conflicts with the target branch and needs rebase.
Want to unsubscribe from rebase notifications on this pull request? Just convert this pull request to a “draft”.
5052 if (amountdelta) {
5053 pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
5054 }
5055 TxValidationState state;
5056- if (nTime + nExpiryTimeout > nNow) {
5057+ if (nTime > nNow - nExpiryTimeout) {