Skip to content

Commit 02e1d8d

Browse files
committed
Merge bitcoin/bitcoin#24083: Revert "Add to spends only transcations from me"
3ee6d07 test: add more wallet conflicts assertions (S3RK) 3b98bf9 Revert "Add to spends only transcations from me" (S3RK) Pull request description: This reverts commit d045664 from #22929. This commit was based on invalid assumption that `mapTxSpends` should contain only outgoing txs and broke wallet conflicts feature. ACKs for top commit: achow101: ACK 3ee6d07 Tree-SHA512: bf5a77ced6bac57d5eb85771d9189c53e1edc295d179ed5a1bdce18e365794a9101b4cecf35387b27f67260db3b47f7214e7876e490494529b748cceeb95632d
2 parents 133f73e + 3ee6d07 commit 02e1d8d

File tree

3 files changed

+72
-58
lines changed

3 files changed

+72
-58
lines changed

src/wallet/test/wallet_tests.cpp

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -825,35 +825,30 @@ BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup)
825825
context.args = &gArgs;
826826
context.chain = m_node.chain.get();
827827
auto wallet = TestLoadWallet(context);
828-
AddKey(*wallet, coinbaseKey);
828+
CKey key;
829+
key.MakeNewKey(true);
830+
AddKey(*wallet, key);
829831

830-
// rescan to ensure coinbase transactions from test fixture are picked up by the wallet
831-
{
832-
WalletRescanReserver reserver(*wallet);
833-
reserver.reserve();
834-
wallet->ScanForWalletTransactions(m_node.chain->getBlockHash(0), 0, /* max height= */ {}, reserver, /* update= */ true);
835-
}
836-
// create one more block to get the first block coinbase to maturity
832+
std::string error;
837833
m_coinbase_txns.push_back(CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())).vtx[0]);
838-
// spend first coinbase tx
839-
auto spend_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
840-
CreateAndProcessBlock({spend_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
834+
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey, GetScriptForRawPubKey(key.GetPubKey()));
835+
CreateAndProcessBlock({block_tx}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
841836

842837
SyncWithValidationInterfaceQueue();
843838

844839
{
845-
auto spend_tx_hash = spend_tx.GetHash();
840+
auto block_hash = block_tx.GetHash();
846841
auto prev_hash = m_coinbase_txns[0]->GetHash();
847842

848843
LOCK(wallet->cs_wallet);
849844
BOOST_CHECK(wallet->HasWalletSpend(prev_hash));
850-
BOOST_CHECK_EQUAL(wallet->mapWallet.count(spend_tx_hash), 1u);
845+
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 1u);
851846

852-
std::vector<uint256> vHashIn{spend_tx_hash}, vHashOut;
847+
std::vector<uint256> vHashIn{ block_hash }, vHashOut;
853848
BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vHashIn, vHashOut), DBErrors::LOAD_OK);
854849

855850
BOOST_CHECK(!wallet->HasWalletSpend(prev_hash));
856-
BOOST_CHECK_EQUAL(wallet->mapWallet.count(spend_tx_hash), 0u);
851+
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_hash), 0u);
857852
}
858853

859854
TestUnloadWallet(std::move(wallet));

src/wallet/wallet.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -952,9 +952,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const TxState& state, const
952952
wtx.nOrderPos = IncOrderPosNext(&batch);
953953
wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
954954
wtx.nTimeSmart = ComputeTimeSmart(wtx, rescanning_old_block);
955-
if (IsFromMe(*tx.get())) {
956-
AddToSpends(hash);
957-
}
955+
AddToSpends(hash, &batch);
958956
}
959957

960958
if (!fInsertedNew)

test/functional/wallet_abandonconflict.py

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,70 +29,76 @@ def skip_test_if_missing_module(self):
2929
self.skip_if_no_wallet()
3030

3131
def run_test(self):
32+
# create two wallets to tests conflicts from both sender's and receiver's sides
33+
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
34+
self.nodes[0].createwallet(wallet_name="bob")
35+
bob = self.nodes[0].get_wallet_rpc("bob")
36+
3237
self.generate(self.nodes[1], COINBASE_MATURITY)
33-
balance = self.nodes[0].getbalance()
34-
txA = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
35-
txB = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
36-
txC = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
38+
balance = alice.getbalance()
39+
txA = alice.sendtoaddress(alice.getnewaddress(), Decimal("10"))
40+
txB = alice.sendtoaddress(alice.getnewaddress(), Decimal("10"))
41+
txC = alice.sendtoaddress(alice.getnewaddress(), Decimal("10"))
3742
self.sync_mempools()
3843
self.generate(self.nodes[1], 1)
3944

4045
# Can not abandon non-wallet transaction
41-
assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: self.nodes[0].abandontransaction(txid='ff' * 32))
46+
assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: alice.abandontransaction(txid='ff' * 32))
4247
# Can not abandon confirmed transaction
43-
assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: self.nodes[0].abandontransaction(txid=txA))
48+
assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: alice.abandontransaction(txid=txA))
4449

45-
newbalance = self.nodes[0].getbalance()
50+
newbalance = alice.getbalance()
4651
assert balance - newbalance < Decimal("0.001") #no more than fees lost
4752
balance = newbalance
4853

4954
# Disconnect nodes so node0's transactions don't get into node1's mempool
5055
self.disconnect_nodes(0, 1)
5156

5257
# Identify the 10btc outputs
53-
nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10"))
54-
nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10"))
55-
nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10"))
58+
nA = next(tx_out["vout"] for tx_out in alice.gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10"))
59+
nB = next(tx_out["vout"] for tx_out in alice.gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10"))
60+
nC = next(tx_out["vout"] for tx_out in alice.gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10"))
5661

5762
inputs = []
5863
# spend 10btc outputs from txA and txB
5964
inputs.append({"txid": txA, "vout": nA})
6065
inputs.append({"txid": txB, "vout": nB})
6166
outputs = {}
6267

63-
outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998")
64-
outputs[self.nodes[1].getnewaddress()] = Decimal("5")
65-
signed = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs))
68+
outputs[alice.getnewaddress()] = Decimal("14.99998")
69+
outputs[bob.getnewaddress()] = Decimal("5")
70+
signed = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs))
6671
txAB1 = self.nodes[0].sendrawtransaction(signed["hex"])
6772

6873
# Identify the 14.99998btc output
69-
nAB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998"))
74+
nAB = next(tx_out["vout"] for tx_out in alice.gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998"))
7075

7176
#Create a child tx spending AB1 and C
7277
inputs = []
7378
inputs.append({"txid": txAB1, "vout": nAB})
7479
inputs.append({"txid": txC, "vout": nC})
7580
outputs = {}
76-
outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996")
77-
signed2 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs))
81+
outputs[alice.getnewaddress()] = Decimal("24.9996")
82+
signed2 = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs))
7883
txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"])
7984

8085
# Create a child tx spending ABC2
8186
signed3_change = Decimal("24.999")
8287
inputs = [{"txid": txABC2, "vout": 0}]
83-
outputs = {self.nodes[0].getnewaddress(): signed3_change}
84-
signed3 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs))
88+
outputs = {alice.getnewaddress(): signed3_change}
89+
signed3 = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs))
8590
# note tx is never directly referenced, only abandoned as a child of the above
8691
self.nodes[0].sendrawtransaction(signed3["hex"])
8792

8893
# In mempool txs from self should increase balance from change
89-
newbalance = self.nodes[0].getbalance()
94+
newbalance = alice.getbalance()
9095
assert_equal(newbalance, balance - Decimal("30") + signed3_change)
9196
balance = newbalance
9297

9398
# Restart the node with a higher min relay fee so the parent tx is no longer in mempool
9499
# TODO: redo with eviction
95100
self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
101+
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
96102
assert self.nodes[0].getmempoolinfo()['loaded']
97103

98104
# Verify txs no longer in either node's mempool
@@ -101,25 +107,25 @@ def run_test(self):
101107

102108
# Not in mempool txs from self should only reduce balance
103109
# inputs are still spent, but change not received
104-
newbalance = self.nodes[0].getbalance()
110+
newbalance = alice.getbalance()
105111
assert_equal(newbalance, balance - signed3_change)
106112
# Unconfirmed received funds that are not in mempool, also shouldn't show
107113
# up in unconfirmed balance
108-
balances = self.nodes[0].getbalances()['mine']
114+
balances = alice.getbalances()['mine']
109115
assert_equal(balances['untrusted_pending'] + balances['trusted'], newbalance)
110116
# Also shouldn't show up in listunspent
111-
assert not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)]
117+
assert not txABC2 in [utxo["txid"] for utxo in alice.listunspent(0)]
112118
balance = newbalance
113119

114120
# Abandon original transaction and verify inputs are available again
115121
# including that the child tx was also abandoned
116-
self.nodes[0].abandontransaction(txAB1)
117-
newbalance = self.nodes[0].getbalance()
122+
alice.abandontransaction(txAB1)
123+
newbalance = alice.getbalance()
118124
assert_equal(newbalance, balance + Decimal("30"))
119125
balance = newbalance
120126

121127
self.log.info("Check abandoned transactions in listsinceblock")
122-
listsinceblock = self.nodes[0].listsinceblock()
128+
listsinceblock = alice.listsinceblock()
123129
txAB1_listsinceblock = [d for d in listsinceblock['transactions'] if d['txid'] == txAB1 and d['category'] == 'send']
124130
for tx in txAB1_listsinceblock:
125131
assert_equal(tx['abandoned'], True)
@@ -128,49 +134,53 @@ def run_test(self):
128134

129135
# Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned
130136
self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"])
137+
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
131138
assert self.nodes[0].getmempoolinfo()['loaded']
132139

133140
assert_equal(len(self.nodes[0].getrawmempool()), 0)
134-
assert_equal(self.nodes[0].getbalance(), balance)
141+
assert_equal(alice.getbalance(), balance)
135142

136143
# But if it is received again then it is unabandoned
137144
# And since now in mempool, the change is available
138145
# But its child tx remains abandoned
139146
self.nodes[0].sendrawtransaction(signed["hex"])
140-
newbalance = self.nodes[0].getbalance()
147+
newbalance = alice.getbalance()
141148
assert_equal(newbalance, balance - Decimal("20") + Decimal("14.99998"))
142149
balance = newbalance
143150

144151
# Send child tx again so it is unabandoned
145152
self.nodes[0].sendrawtransaction(signed2["hex"])
146-
newbalance = self.nodes[0].getbalance()
153+
newbalance = alice.getbalance()
147154
assert_equal(newbalance, balance - Decimal("10") - Decimal("14.99998") + Decimal("24.9996"))
148155
balance = newbalance
149156

150157
# Remove using high relay fee again
151158
self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"])
159+
alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
152160
assert self.nodes[0].getmempoolinfo()['loaded']
153161
assert_equal(len(self.nodes[0].getrawmempool()), 0)
154-
newbalance = self.nodes[0].getbalance()
162+
newbalance = alice.getbalance()
155163
assert_equal(newbalance, balance - Decimal("24.9996"))
156164
balance = newbalance
157165

158166
self.log.info("Test transactions conflicted by a double spend")
167+
self.nodes[0].loadwallet("bob")
168+
bob = self.nodes[0].get_wallet_rpc("bob")
169+
159170
# Create a double spend of AB1 by spending again from only A's 10 output
160171
# Mine double spend from node 1
161172
inputs = []
162173
inputs.append({"txid": txA, "vout": nA})
163174
outputs = {}
164-
outputs[self.nodes[1].getnewaddress()] = Decimal("9.9999")
165-
tx = self.nodes[0].createrawtransaction(inputs, outputs)
166-
signed = self.nodes[0].signrawtransactionwithwallet(tx)
167-
self.nodes[1].sendrawtransaction(signed["hex"])
168-
self.generate(self.nodes[1], 1, sync_fun=self.no_op)
169-
175+
outputs[self.nodes[1].getnewaddress()] = Decimal("3.9999")
176+
outputs[bob.getnewaddress()] = Decimal("5.9999")
177+
tx = alice.createrawtransaction(inputs, outputs)
178+
signed = alice.signrawtransactionwithwallet(tx)
179+
double_spend_txid = self.nodes[1].sendrawtransaction(signed["hex"])
170180
self.connect_nodes(0, 1)
171-
self.sync_blocks()
181+
self.generate(self.nodes[1], 1)
172182

173-
tx_list = self.nodes[0].listtransactions()
183+
tx_list = alice.listtransactions()
174184

175185
conflicted = [tx for tx in tx_list if tx["confirmations"] < 0]
176186
assert_equal(4, len(conflicted))
@@ -179,7 +189,7 @@ def run_test(self):
179189
assert_equal(2, len(wallet_conflicts))
180190

181191
double_spends = [tx for tx in tx_list if tx["walletconflicts"] and tx["confirmations"] > 0]
182-
assert_equal(1, len(double_spends))
192+
assert_equal(2, len(double_spends)) # one for each output
183193
double_spend = double_spends[0]
184194

185195
# Test the properties of the conflicted transactions, i.e. with confirmations < 0.
@@ -198,16 +208,27 @@ def run_test(self):
198208
assert_equal(double_spend["walletconflicts"], [tx["txid"]])
199209
assert_equal(tx["walletconflicts"], [double_spend["txid"]])
200210

211+
# Test walletconflicts on the receiver's side
212+
txinfo = bob.gettransaction(txAB1)
213+
assert_equal(txinfo['confirmations'], -1)
214+
assert_equal(txinfo['walletconflicts'], [double_spend['txid']])
215+
216+
double_spends = [tx for tx in bob.listtransactions() if tx["walletconflicts"] and tx["confirmations"] > 0]
217+
assert_equal(1, len(double_spends))
218+
double_spend = double_spends[0]
219+
assert_equal(double_spend_txid, double_spend['txid'])
220+
assert_equal(double_spend["walletconflicts"], [txAB1])
221+
201222
# Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted
202-
newbalance = self.nodes[0].getbalance()
223+
newbalance = alice.getbalance()
203224
assert_equal(newbalance, balance + Decimal("20"))
204225
balance = newbalance
205226

206227
# There is currently a minor bug around this and so this test doesn't work. See Issue #7315
207228
# Invalidate the block with the double spend and B's 10 BTC output should no longer be available
208229
# Don't think C's should either
209230
self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash())
210-
newbalance = self.nodes[0].getbalance()
231+
newbalance = alice.getbalance()
211232
#assert_equal(newbalance, balance - Decimal("10"))
212233
self.log.info("If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer")
213234
self.log.info("conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315")

0 commit comments

Comments
 (0)