Skip to content

Commit e30b6ea

Browse files
committed
Merge bitcoin/bitcoin#24067: wallet: Actually treat (un)confirmed txs as (un)confirmed
fac8165 Remove unused checkFinalTx (MarcoFalke) fa272ea wallet: Avoid dropping confirmed coins (MarcoFalke) 888841e interfaces: Remove unused is_final (MarcoFalke) dddd05e qt: Treat unconfirmed txs as unconfirmed (MarcoFalke) Pull request description: The wallet has several issues: ## Unconfirmed txs in the GUI The GUI clumsily attempts to guess if unconfirmed txs are locked until a future time. This is currently based on the locktime only, not nSequence, thus wrong. Fix this by removing the clumsy code and treat all unconfirmed txs as unconfirmed. The GUI already prints whether a tx is in the mempool, in which case the user knows that the tx wasn't locked until a future time. If the tx is not in the mempool, it might be better to report the exact reject reason from the mempool instead of using incorrect heuristics. ## Confirmed txs in the wallet The wallet drops coins that it incorrectly assumes to be locked until a future time, even if they are already confirmed in the chain. This is because the wallet is using the wrong time (adjusted network time) instead of MTP, due to the `-1` default argument of `CheckFinalTx`. The issues are fixed in separate commits and there is even a test. ACKs for top commit: achow101: ACK fac8165 prayank23: reACK bitcoin/bitcoin@fac8165 glozow: code review ACK fac8165, I understand now how this fixes both issues. Tree-SHA512: 210afb855f4c6d903fee49eba6b1a9735d699cf0168b669eabb38178e53b3a522258b7cc669f52489c6cd3e38bf358afde12eef3ba2e2f2ffaeb06b8f652ccd0
2 parents 39d9bbe + fac8165 commit e30b6ea

15 files changed

+56
-56
lines changed

src/interfaces/chain.h

-3
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,6 @@ class Chain
116116
//! or one of its ancestors.
117117
virtual std::optional<int> findLocatorFork(const CBlockLocator& locator) = 0;
118118

119-
//! Check if transaction will be final given chain height current time.
120-
virtual bool checkFinalTx(const CTransaction& tx) = 0;
121-
122119
//! Return whether node has the block and optionally return block metadata
123120
//! or contents.
124121
virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0;

src/interfaces/wallet.h

-1
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,6 @@ struct WalletTxStatus
405405
int depth_in_main_chain;
406406
unsigned int time_received;
407407
uint32_t lock_time;
408-
bool is_final;
409408
bool is_trusted;
410409
bool is_abandoned;
411410
bool is_coinbase;

src/node/interfaces.cpp

-5
Original file line numberDiff line numberDiff line change
@@ -486,11 +486,6 @@ class ChainImpl : public Chain
486486
const CChain& active = Assert(m_node.chainman)->ActiveChain();
487487
return active.GetLocator();
488488
}
489-
bool checkFinalTx(const CTransaction& tx) override
490-
{
491-
LOCK(cs_main);
492-
return CheckFinalTx(chainman().ActiveChain().Tip(), tx);
493-
}
494489
std::optional<int> findLocatorFork(const CBlockLocator& locator) override
495490
{
496491
LOCK(cs_main);

src/qt/guiconstants.h

-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ static const bool DEFAULT_SPLASHSCREEN = true;
3333
#define COLOR_NEGATIVE QColor(255, 0, 0)
3434
/* Transaction list -- bare address (without label) */
3535
#define COLOR_BAREADDRESS QColor(140, 140, 140)
36-
/* Transaction list -- TX status decoration - open until date */
37-
#define COLOR_TX_STATUS_OPENUNTILDATE QColor(64, 64, 255)
3836
/* Transaction list -- TX status decoration - danger, tx needs attention */
3937
#define COLOR_TX_STATUS_DANGER QColor(200, 100, 100)
4038
/* Transaction list -- TX status decoration - default color */

src/qt/transactiondesc.cpp

-9
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include <interfaces/wallet.h>
1919
#include <key_io.h>
2020
#include <policy/policy.h>
21-
#include <script/script.h>
2221
#include <util/system.h>
2322
#include <validation.h>
2423
#include <wallet/ismine.h>
@@ -35,14 +34,6 @@ using wallet::isminetype;
3534

3635
QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks)
3736
{
38-
if (!status.is_final)
39-
{
40-
if (wtx.tx->nLockTime < LOCKTIME_THRESHOLD)
41-
return tr("Open for %n more block(s)", "", wtx.tx->nLockTime - numBlocks);
42-
else
43-
return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.tx->nLockTime));
44-
}
45-
else
4637
{
4738
int nDepth = status.depth_in_main_chain;
4839
if (nDepth < 0) {

src/qt/transactionrecord.cpp

+1-14
Original file line numberDiff line numberDiff line change
@@ -179,21 +179,8 @@ void TransactionRecord::updateStatus(const interfaces::WalletTxStatus& wtx, cons
179179
status.depth = wtx.depth_in_main_chain;
180180
status.m_cur_block_hash = block_hash;
181181

182-
const bool up_to_date = ((int64_t)QDateTime::currentMSecsSinceEpoch() / 1000 - block_time < MAX_BLOCK_TIME_GAP);
183-
if (up_to_date && !wtx.is_final) {
184-
if (wtx.lock_time < LOCKTIME_THRESHOLD) {
185-
status.status = TransactionStatus::OpenUntilBlock;
186-
status.open_for = wtx.lock_time - numBlocks;
187-
}
188-
else
189-
{
190-
status.status = TransactionStatus::OpenUntilDate;
191-
status.open_for = wtx.lock_time;
192-
}
193-
}
194182
// For generated transactions, determine maturity
195-
else if(type == TransactionRecord::Generated)
196-
{
183+
if (type == TransactionRecord::Generated) {
197184
if (wtx.blocks_to_maturity > 0)
198185
{
199186
status.status = TransactionStatus::Immature;

src/qt/transactionrecord.h

-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ class TransactionStatus
3030
enum Status {
3131
Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/
3232
/// Normal (sent/received) transactions
33-
OpenUntilDate, /**< Transaction not yet final, waiting for date */
34-
OpenUntilBlock, /**< Transaction not yet final, waiting for block */
3533
Unconfirmed, /**< Not yet mined into a block **/
3634
Confirming, /**< Confirmed, but waiting for the recommended number of confirmations **/
3735
Conflicted, /**< Conflicts with other transaction or mempool **/

src/qt/transactiontablemodel.cpp

-9
Original file line numberDiff line numberDiff line change
@@ -316,12 +316,6 @@ QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) cons
316316

317317
switch(wtx->status.status)
318318
{
319-
case TransactionStatus::OpenUntilBlock:
320-
status = tr("Open for %n more block(s)","",wtx->status.open_for);
321-
break;
322-
case TransactionStatus::OpenUntilDate:
323-
status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
324-
break;
325319
case TransactionStatus::Unconfirmed:
326320
status = tr("Unconfirmed");
327321
break;
@@ -475,9 +469,6 @@ QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx)
475469
{
476470
switch(wtx->status.status)
477471
{
478-
case TransactionStatus::OpenUntilBlock:
479-
case TransactionStatus::OpenUntilDate:
480-
return COLOR_TX_STATUS_OPENUNTILDATE;
481472
case TransactionStatus::Unconfirmed:
482473
return QIcon(":/icons/transaction_0");
483474
case TransactionStatus::Abandoned:

src/wallet/interfaces.cpp

-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
9090
result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx);
9191
result.time_received = wtx.nTimeReceived;
9292
result.lock_time = wtx.tx->nLockTime;
93-
result.is_final = wallet.chain().checkFinalTx(*wtx.tx);
9493
result.is_trusted = CachedTxIsTrusted(wallet, wtx);
9594
result.is_abandoned = wtx.isAbandoned();
9695
result.is_coinbase = wtx.IsCoinBase();

src/wallet/receive.cpp

-2
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,6 @@ bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminef
279279
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents)
280280
{
281281
AssertLockHeld(wallet.cs_wallet);
282-
// Quick answer in most cases
283-
if (!wallet.chain().checkFinalTx(*wtx.tx)) return false;
284282
int nDepth = wallet.GetTxDepthInMainChain(wtx);
285283
if (nDepth >= 1) return true;
286284
if (nDepth < 0) return false;

src/wallet/rpc/coins.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
6060
if (depth < min_depth
6161
// Coinbase with less than 1 confirmation is no longer in the main chain
6262
|| (wtx.IsCoinBase() && (depth < 1 || !include_coinbase))
63-
|| (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)
64-
|| !wallet.chain().checkFinalTx(*wtx.tx)) {
63+
|| (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
64+
{
6565
continue;
6666
}
6767

src/wallet/rpc/transactions.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
114114

115115
// Coinbase with less than 1 confirmation is no longer in the main chain
116116
if ((wtx.IsCoinBase() && (nDepth < 1 || !include_coinbase))
117-
|| (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)
118-
|| !wallet.chain().checkFinalTx(*wtx.tx)) {
117+
|| (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
118+
{
119119
continue;
120120
}
121121

src/wallet/spend.cpp

-4
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const C
105105
const uint256& wtxid = entry.first;
106106
const CWalletTx& wtx = entry.second;
107107

108-
if (!wallet.chain().checkFinalTx(*wtx.tx)) {
109-
continue;
110-
}
111-
112108
if (wallet.IsTxImmatureCoinBase(wtx))
113109
continue;
114110

test/functional/test_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@
308308
'feature_coinstatsindex.py --legacy-wallet',
309309
'feature_coinstatsindex.py --descriptors',
310310
'wallet_orphanedreward.py',
311+
'wallet_timelock.py',
311312
'p2p_node_network_limited.py',
312313
'p2p_permissions.py',
313314
'feature_blocksdir.py',

test/functional/wallet_timelock.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2022 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
6+
from test_framework.test_framework import BitcoinTestFramework
7+
from test_framework.util import assert_equal
8+
9+
10+
class WalletLocktimeTest(BitcoinTestFramework):
11+
def set_test_params(self):
12+
self.num_nodes = 1
13+
14+
def skip_test_if_missing_module(self):
15+
self.skip_if_no_wallet()
16+
17+
def run_test(self):
18+
node = self.nodes[0]
19+
20+
mtp_tip = node.getblockheader(node.getbestblockhash())["mediantime"]
21+
22+
self.log.info("Get new address with label")
23+
label = "timelock⌛🔓"
24+
address = node.getnewaddress(label=label)
25+
26+
self.log.info("Send to new address with locktime")
27+
node.send(
28+
outputs={address: 5},
29+
options={"locktime": mtp_tip - 1},
30+
)
31+
self.generate(node, 1)
32+
33+
self.log.info("Check that clock can not change finality of confirmed txs")
34+
amount_before_ad = node.getreceivedbyaddress(address)
35+
amount_before_lb = node.getreceivedbylabel(label)
36+
list_before_ad = node.listreceivedbyaddress(address_filter=address)
37+
list_before_lb = node.listreceivedbylabel(include_empty=False)
38+
balance_before = node.getbalances()["mine"]["trusted"]
39+
coin_before = node.listunspent(maxconf=1)
40+
node.setmocktime(mtp_tip - 1)
41+
assert_equal(node.getreceivedbyaddress(address), amount_before_ad)
42+
assert_equal(node.getreceivedbylabel(label), amount_before_lb)
43+
assert_equal(node.listreceivedbyaddress(address_filter=address), list_before_ad)
44+
assert_equal(node.listreceivedbylabel(include_empty=False), list_before_lb)
45+
assert_equal(node.getbalances()["mine"]["trusted"], balance_before)
46+
assert_equal(node.listunspent(maxconf=1), coin_before)
47+
48+
49+
if __name__ == "__main__":
50+
WalletLocktimeTest().main()

0 commit comments

Comments
 (0)