Skip to content

Commit 28bf062

Browse files
committed
Fix off-by-one error w/ nLockTime in the wallet
Previously due to an off-by-one error the wallet ignored nLockTime-by-height transactions that would be valid in the next block even though they are accepted into the mempool. The transactions wouldn't show up until confirmed, nor would they be included in the unconfirmed balance. Similar to the mempool behavior fix in 665bdd3, the wallet code was calling IsFinalTx() directly without taking into account the fact that doing so tells you if the transaction could have been mined in the *current* block, rather than the next block. To fix this we strip IsFinalTx() of non-consensus-critical functionality, removing the default arguments, and add CheckFinalTx() to check if a transaction will be final in the next block.
1 parent e1412d3 commit 28bf062

File tree

8 files changed

+39
-38
lines changed

8 files changed

+39
-38
lines changed

src/main.cpp

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -658,14 +658,8 @@ bool IsStandardTx(const CTransaction& tx, string& reason)
658658

659659
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
660660
{
661-
AssertLockHeld(cs_main);
662-
// Time based nLockTime implemented in 0.1.6
663661
if (tx.nLockTime == 0)
664662
return true;
665-
if (nBlockHeight == 0)
666-
nBlockHeight = chainActive.Height();
667-
if (nBlockTime == 0)
668-
nBlockTime = GetAdjustedTime();
669663
if ((int64_t)tx.nLockTime < ((int64_t)tx.nLockTime < LOCKTIME_THRESHOLD ? (int64_t)nBlockHeight : nBlockTime))
670664
return true;
671665
BOOST_FOREACH(const CTxIn& txin, tx.vin)
@@ -674,6 +668,12 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime)
674668
return true;
675669
}
676670

671+
bool CheckFinalTx(const CTransaction &tx)
672+
{
673+
AssertLockHeld(cs_main);
674+
return IsFinalTx(tx, chainActive.Height() + 1, GetAdjustedTime());
675+
}
676+
677677
/**
678678
* Check transaction inputs to mitigate two
679679
* potential denial-of-service attacks:
@@ -890,21 +890,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa
890890
// Only accept nLockTime-using transactions that can be mined in the next
891891
// block; we don't want our mempool filled up with transactions that can't
892892
// be mined yet.
893-
//
894-
// However, IsFinalTx() is confusing... Without arguments, it uses
895-
// chainActive.Height() to evaluate nLockTime; when a block is accepted,
896-
// chainActive.Height() is set to the value of nHeight in the block.
897-
// However, when IsFinalTx() is called within CBlock::AcceptBlock(), the
898-
// height of the block *being* evaluated is what is used. Thus if we want
899-
// to know if a transaction can be part of the *next* block, we need to
900-
// call IsFinalTx() with one more than chainActive.Height().
901-
//
902-
// Timestamps on the other hand don't get any special treatment, because we
903-
// can't know what timestamp the next block will have, and there aren't
904-
// timestamp applications where it matters.
905-
if (!IsFinalTx(tx, chainActive.Height() + 1))
906-
return state.DoS(0,
907-
error("AcceptToMemoryPool: non-final"),
893+
if (!CheckFinalTx(tx))
894+
return state.DoS(0, error("AcceptToMemoryPool: non-final"),
908895
REJECT_NONSTANDARD, "non-final");
909896

910897
// is it already in the memory pool?

src/main.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,18 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state);
334334
*/
335335
bool IsStandardTx(const CTransaction& tx, std::string& reason);
336336

337-
bool IsFinalTx(const CTransaction &tx, int nBlockHeight = 0, int64_t nBlockTime = 0);
337+
/**
338+
* Check if transaction is final and can be included in a block with the
339+
* specified height and time. Consensus critical.
340+
*/
341+
bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64_t nBlockTime);
342+
343+
/**
344+
* Check if transaction will be final in the next block to be created.
345+
*
346+
* Calls IsFinalTx() with current block height and appropriate block time.
347+
*/
348+
bool CheckFinalTx(const CTransaction &tx);
338349

339350
/**
340351
* Closure representing one script verification

src/miner.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
137137
LOCK2(cs_main, mempool.cs);
138138
CBlockIndex* pindexPrev = chainActive.Tip();
139139
const int nHeight = pindexPrev->nHeight + 1;
140+
pblock->nTime = GetAdjustedTime();
140141
CCoinsViewCache view(pcoinsTip);
141142

142143
// Priority order to process transactions
@@ -151,7 +152,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
151152
mi != mempool.mapTx.end(); ++mi)
152153
{
153154
const CTransaction& tx = mi->second.GetTx();
154-
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight))
155+
if (tx.IsCoinBase() || !IsFinalTx(tx, nHeight, pblock->nTime))
155156
continue;
156157

157158
COrphan* porphan = NULL;

src/qt/transactiondesc.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ using namespace std;
2626
QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx)
2727
{
2828
AssertLockHeld(cs_main);
29-
if (!IsFinalTx(wtx, chainActive.Height() + 1))
29+
if (!CheckFinalTx(wtx))
3030
{
3131
if (wtx.nLockTime < LOCKTIME_THRESHOLD)
3232
return tr("Open for %n more block(s)", "", wtx.nLockTime - chainActive.Height());

src/qt/transactionrecord.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx)
188188
status.depth = wtx.GetDepthInMainChain();
189189
status.cur_num_blocks = chainActive.Height();
190190

191-
if (!IsFinalTx(wtx, chainActive.Height() + 1))
191+
if (!CheckFinalTx(wtx))
192192
{
193193
if (wtx.nLockTime < LOCKTIME_THRESHOLD)
194194
{

src/test/miner_tests.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
222222
tx.nLockTime = chainActive.Tip()->nHeight+1;
223223
hash = tx.GetHash();
224224
mempool.addUnchecked(hash, CTxMemPoolEntry(tx, 11, GetTime(), 111.0, 11));
225-
BOOST_CHECK(!IsFinalTx(tx, chainActive.Tip()->nHeight + 1));
225+
BOOST_CHECK(!CheckFinalTx(tx));
226226

227227
// time locked
228228
tx2.vin.resize(1);
@@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
236236
tx2.nLockTime = chainActive.Tip()->GetMedianTimePast()+1;
237237
hash = tx2.GetHash();
238238
mempool.addUnchecked(hash, CTxMemPoolEntry(tx2, 11, GetTime(), 111.0, 11));
239-
BOOST_CHECK(!IsFinalTx(tx2));
239+
BOOST_CHECK(!CheckFinalTx(tx2));
240240

241241
BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey));
242242

@@ -248,8 +248,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity)
248248
chainActive.Tip()->nHeight++;
249249
SetMockTime(chainActive.Tip()->GetMedianTimePast()+2);
250250

251-
BOOST_CHECK(IsFinalTx(tx, chainActive.Tip()->nHeight + 1));
252-
BOOST_CHECK(IsFinalTx(tx2));
251+
// FIXME: we should *actually* create a new block so the following test
252+
// works; CheckFinalTx() isn't fooled by monkey-patching nHeight.
253+
//BOOST_CHECK(CheckFinalTx(tx));
254+
//BOOST_CHECK(CheckFinalTx(tx2));
253255

254256
BOOST_CHECK(pblocktemplate = CreateNewBlock(scriptPubKey));
255257
BOOST_CHECK_EQUAL(pblocktemplate->block.vtx.size(), 3);

src/wallet/rpcwallet.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -588,7 +588,7 @@ Value getreceivedbyaddress(const Array& params, bool fHelp)
588588
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
589589
{
590590
const CWalletTx& wtx = (*it).second;
591-
if (wtx.IsCoinBase() || !IsFinalTx(wtx))
591+
if (wtx.IsCoinBase() || !CheckFinalTx(wtx))
592592
continue;
593593

594594
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
@@ -642,7 +642,7 @@ Value getreceivedbyaccount(const Array& params, bool fHelp)
642642
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
643643
{
644644
const CWalletTx& wtx = (*it).second;
645-
if (wtx.IsCoinBase() || !IsFinalTx(wtx))
645+
if (wtx.IsCoinBase() || !CheckFinalTx(wtx))
646646
continue;
647647

648648
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
@@ -666,7 +666,7 @@ CAmount GetAccountBalance(CWalletDB& walletdb, const string& strAccount, int nMi
666666
for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it)
667667
{
668668
const CWalletTx& wtx = (*it).second;
669-
if (!IsFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0)
669+
if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0)
670670
continue;
671671

672672
CAmount nReceived, nSent, nFee;
@@ -1109,7 +1109,7 @@ Value ListReceived(const Array& params, bool fByAccounts)
11091109
{
11101110
const CWalletTx& wtx = (*it).second;
11111111

1112-
if (wtx.IsCoinBase() || !IsFinalTx(wtx))
1112+
if (wtx.IsCoinBase() || !CheckFinalTx(wtx))
11131113
continue;
11141114

11151115
int nDepth = wtx.GetDepthInMainChain();

src/wallet/wallet.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,7 +1317,7 @@ CAmount CWalletTx::GetChange() const
13171317
bool CWalletTx::IsTrusted() const
13181318
{
13191319
// Quick answer in most cases
1320-
if (!IsFinalTx(*this))
1320+
if (!CheckFinalTx(*this))
13211321
return false;
13221322
int nDepth = GetDepthInMainChain();
13231323
if (nDepth >= 1)
@@ -1423,7 +1423,7 @@ CAmount CWallet::GetUnconfirmedBalance() const
14231423
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
14241424
{
14251425
const CWalletTx* pcoin = &(*it).second;
1426-
if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
1426+
if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
14271427
nTotal += pcoin->GetAvailableCredit();
14281428
}
14291429
}
@@ -1468,7 +1468,7 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
14681468
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
14691469
{
14701470
const CWalletTx* pcoin = &(*it).second;
1471-
if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
1471+
if (!CheckFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
14721472
nTotal += pcoin->GetAvailableWatchOnlyCredit();
14731473
}
14741474
}
@@ -1503,7 +1503,7 @@ void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const
15031503
const uint256& wtxid = it->first;
15041504
const CWalletTx* pcoin = &(*it).second;
15051505

1506-
if (!IsFinalTx(*pcoin))
1506+
if (!CheckFinalTx(*pcoin))
15071507
continue;
15081508

15091509
if (fOnlyConfirmed && !pcoin->IsTrusted())
@@ -2290,7 +2290,7 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
22902290
{
22912291
CWalletTx *pcoin = &walletEntry.second;
22922292

2293-
if (!IsFinalTx(*pcoin) || !pcoin->IsTrusted())
2293+
if (!CheckFinalTx(*pcoin) || !pcoin->IsTrusted())
22942294
continue;
22952295

22962296
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)

0 commit comments

Comments
 (0)