Skip to content

Commit d925643

Browse files
committed
Update file to bitcoin closest sync hash
1 parent 75bd399 commit d925643

File tree

2 files changed

+294
-12
lines changed

2 files changed

+294
-12
lines changed

test/functional/wallet_conflicts.py

+286
Original file line numberDiff line numberDiff line change
@@ -136,5 +136,291 @@ def test_block_conflicts(self):
136136
assert_equal(former_conflicted["confirmations"], 1)
137137
assert_equal(former_conflicted["blockheight"], 217)
138138

139+
def test_mempool_conflict(self):
140+
self.nodes[0].createwallet("alice")
141+
alice = self.nodes[0].get_wallet_rpc("alice")
142+
143+
bob = self.nodes[1]
144+
145+
self.nodes[2].send(outputs=[{alice.getnewaddress() : 25} for _ in range(3)])
146+
self.generate(self.nodes[2], 1)
147+
148+
self.log.info("Test a scenario where a transaction has a mempool conflict")
149+
150+
unspents = alice.listunspent()
151+
assert_equal(len(unspents), 3)
152+
assert all([tx["amount"] == 25 for tx in unspents])
153+
154+
# tx1 spends unspent[0] and unspent[1]
155+
raw_tx = alice.createrawtransaction(inputs=[unspents[0], unspents[1]], outputs=[{bob.getnewaddress() : 49.9999}])
156+
tx1 = alice.signrawtransactionwithwallet(raw_tx)['hex']
157+
158+
# tx2 spends unspent[1] and unspent[2], conflicts with tx1
159+
raw_tx = alice.createrawtransaction(inputs=[unspents[1], unspents[2]], outputs=[{bob.getnewaddress() : 49.99}])
160+
tx2 = alice.signrawtransactionwithwallet(raw_tx)['hex']
161+
162+
# tx3 spends unspent[2], conflicts with tx2
163+
raw_tx = alice.createrawtransaction(inputs=[unspents[2]], outputs=[{bob.getnewaddress() : 24.9899}])
164+
tx3 = alice.signrawtransactionwithwallet(raw_tx)['hex']
165+
166+
# broadcast tx1
167+
tx1_txid = alice.sendrawtransaction(tx1)
168+
169+
assert_equal(alice.listunspent(), [unspents[2]])
170+
assert_equal(alice.getbalance(), 25)
171+
172+
# broadcast tx2, replaces tx1 in mempool
173+
tx2_txid = alice.sendrawtransaction(tx2)
174+
175+
# Check that unspent[0] is now available because the transaction spending it has been replaced in the mempool
176+
assert_equal(alice.listunspent(), [unspents[0]])
177+
assert_equal(alice.getbalance(), 25)
178+
179+
assert_equal(alice.gettransaction(tx1_txid)["mempoolconflicts"], [tx2_txid])
180+
181+
self.log.info("Test scenario where a mempool conflict is removed")
182+
183+
# broadcast tx3, replaces tx2 in mempool
184+
# Now that tx1's conflict has been removed, tx1 is now
185+
# not conflicted, and instead is inactive until it is
186+
# rebroadcasted. Now unspent[0] is not available, because
187+
# tx1 is no longer conflicted.
188+
alice.sendrawtransaction(tx3)
189+
190+
assert_equal(alice.gettransaction(tx1_txid)["mempoolconflicts"], [])
191+
assert tx1_txid not in self.nodes[0].getrawmempool()
192+
193+
# now all of alice's outputs should be considered spent
194+
# unspent[0]: spent by inactive tx1
195+
# unspent[1]: spent by inactive tx1
196+
# unspent[2]: spent by active tx3
197+
assert_equal(alice.listunspent(), [])
198+
assert_equal(alice.getbalance(), 0)
199+
200+
# Clean up for next test
201+
bob.sendall([self.nodes[2].getnewaddress()])
202+
self.generate(self.nodes[2], 1)
203+
204+
alice.unloadwallet()
205+
206+
def test_mempool_and_block_conflicts(self):
207+
self.nodes[0].createwallet("alice_2")
208+
alice = self.nodes[0].get_wallet_rpc("alice_2")
209+
bob = self.nodes[1]
210+
211+
self.nodes[2].send(outputs=[{alice.getnewaddress() : 25} for _ in range(3)])
212+
self.generate(self.nodes[2], 1)
213+
214+
self.log.info("Test a scenario where a transaction has both a block conflict and a mempool conflict")
215+
unspents = [{"txid" : element["txid"], "vout" : element["vout"]} for element in alice.listunspent()]
216+
217+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], 0)
218+
219+
# alice and bob nodes are disconnected so that transactions can be
220+
# created by alice, but broadcasted from bob so that alice's wallet
221+
# doesn't know about them
222+
self.disconnect_nodes(0, 1)
223+
224+
# Sends funds to bob
225+
raw_tx = alice.createrawtransaction(inputs=[unspents[0]], outputs=[{bob.getnewaddress() : 24.99999}])
226+
raw_tx1 = alice.signrawtransactionwithwallet(raw_tx)['hex']
227+
tx1_txid = bob.sendrawtransaction(raw_tx1) # broadcast original tx spending unspents[0] only to bob
228+
229+
# create a conflict to previous tx (also spends unspents[0]), but don't broadcast, sends funds back to alice
230+
raw_tx = alice.createrawtransaction(inputs=[unspents[0], unspents[2]], outputs=[{alice.getnewaddress() : 49.999}])
231+
tx1_conflict = alice.signrawtransactionwithwallet(raw_tx)['hex']
232+
233+
# Sends funds to bob
234+
raw_tx = alice.createrawtransaction(inputs=[unspents[1]], outputs=[{bob.getnewaddress() : 24.9999}])
235+
raw_tx2 = alice.signrawtransactionwithwallet(raw_tx)['hex']
236+
tx2_txid = bob.sendrawtransaction(raw_tx2) # broadcast another original tx spending unspents[1] only to bob
237+
238+
# create a conflict to previous tx (also spends unspents[1]), but don't broadcast, sends funds to alice
239+
raw_tx = alice.createrawtransaction(inputs=[unspents[1]], outputs=[{alice.getnewaddress() : 24.9999}])
240+
tx2_conflict = alice.signrawtransactionwithwallet(raw_tx)['hex']
241+
242+
bob_unspents = [{"txid" : element, "vout" : 0} for element in [tx1_txid, tx2_txid]]
243+
244+
# tx1 and tx2 are now in bob's mempool, and they are unconflicted, so bob has these funds
245+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], Decimal("49.99989000"))
246+
247+
# spend both of bob's unspents, child tx of tx1 and tx2
248+
raw_tx = bob.createrawtransaction(inputs=[bob_unspents[0], bob_unspents[1]], outputs=[{bob.getnewaddress() : 49.999}])
249+
raw_tx3 = bob.signrawtransactionwithwallet(raw_tx)['hex']
250+
tx3_txid = bob.sendrawtransaction(raw_tx3) # broadcast tx only to bob
251+
252+
# alice knows about 0 txs, bob knows about 3
253+
assert_equal(len(alice.getrawmempool()), 0)
254+
assert_equal(len(bob.getrawmempool()), 3)
255+
256+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], Decimal("49.99900000"))
257+
258+
# bob broadcasts tx_1 conflict
259+
tx1_conflict_txid = bob.sendrawtransaction(tx1_conflict)
260+
assert_equal(len(alice.getrawmempool()), 0)
261+
assert_equal(len(bob.getrawmempool()), 2) # tx1_conflict kicks out both tx1, and its child tx3
262+
263+
assert tx2_txid in bob.getrawmempool()
264+
assert tx1_conflict_txid in bob.getrawmempool()
265+
266+
assert_equal(bob.gettransaction(tx1_txid)["mempoolconflicts"], [tx1_conflict_txid])
267+
assert_equal(bob.gettransaction(tx2_txid)["mempoolconflicts"], [])
268+
assert_equal(bob.gettransaction(tx3_txid)["mempoolconflicts"], [tx1_conflict_txid])
269+
270+
# check that tx3 is now conflicted, so the output from tx2 can now be spent
271+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], Decimal("24.99990000"))
272+
273+
# we will be disconnecting this block in the future
274+
alice.sendrawtransaction(tx2_conflict)
275+
assert_equal(len(alice.getrawmempool()), 1) # currently alice's mempool is only aware of tx2_conflict
276+
# 11 blocks are mined so that when they are invalidated, tx_2
277+
# does not get put back into the mempool
278+
blk = self.generate(self.nodes[0], 11, sync_fun=self.no_op)[0]
279+
assert_equal(len(alice.getrawmempool()), 0) # tx2_conflict is now mined
280+
281+
self.connect_nodes(0, 1)
282+
self.sync_blocks()
283+
assert_equal(alice.getbestblockhash(), bob.getbestblockhash())
284+
285+
# now that tx2 has a block conflict, tx1_conflict should be the only tx in bob's mempool
286+
assert tx1_conflict_txid in bob.getrawmempool()
287+
assert_equal(len(bob.getrawmempool()), 1)
288+
289+
# tx3 should now also be block-conflicted by tx2_conflict
290+
assert_equal(bob.gettransaction(tx3_txid)["confirmations"], -11)
291+
# bob has no pending funds, since tx1, tx2, and tx3 are all conflicted
292+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], 0)
293+
bob.invalidateblock(blk) # remove tx2_conflict
294+
# bob should still have no pending funds because tx1 and tx3 are still conflicted, and tx2 has not been re-broadcast
295+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], 0)
296+
assert_equal(len(bob.getrawmempool()), 1)
297+
# check that tx3 is no longer block-conflicted
298+
assert_equal(bob.gettransaction(tx3_txid)["confirmations"], 0)
299+
300+
bob.sendrawtransaction(raw_tx2)
301+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], Decimal("24.99990000"))
302+
303+
# create a conflict to previous tx (also spends unspents[2]), but don't broadcast, sends funds back to alice
304+
raw_tx = alice.createrawtransaction(inputs=[unspents[2]], outputs=[{alice.getnewaddress() : 24.99}])
305+
tx1_conflict_conflict = alice.signrawtransactionwithwallet(raw_tx)['hex']
306+
307+
bob.sendrawtransaction(tx1_conflict_conflict) # kick tx1_conflict out of the mempool
308+
bob.sendrawtransaction(raw_tx1) #re-broadcast tx1 because it is no longer conflicted
309+
310+
# Now bob has no pending funds because tx1 and tx2 are spent by tx3, which hasn't been re-broadcast yet
311+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], 0)
312+
313+
bob.sendrawtransaction(raw_tx3)
314+
assert_equal(len(bob.getrawmempool()), 4) # The mempool contains: tx1, tx2, tx1_conflict_conflict, tx3
315+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], Decimal("49.99900000"))
316+
317+
# Clean up for next test
318+
bob.reconsiderblock(blk)
319+
assert_equal(alice.getbestblockhash(), bob.getbestblockhash())
320+
self.sync_mempools()
321+
self.generate(self.nodes[2], 1)
322+
323+
alice.unloadwallet()
324+
325+
def test_descendants_with_mempool_conflicts(self):
326+
self.nodes[0].createwallet("alice_3")
327+
alice = self.nodes[0].get_wallet_rpc("alice_3")
328+
329+
self.nodes[2].send(outputs=[{alice.getnewaddress() : 25} for _ in range(2)])
330+
self.generate(self.nodes[2], 1)
331+
332+
self.nodes[1].createwallet("bob_1")
333+
bob = self.nodes[1].get_wallet_rpc("bob_1")
334+
335+
self.nodes[2].createwallet("carol")
336+
carol = self.nodes[2].get_wallet_rpc("carol")
337+
338+
self.log.info("Test a scenario where a transaction's parent has a mempool conflict")
339+
340+
unspents = alice.listunspent()
341+
assert_equal(len(unspents), 2)
342+
assert all([tx["amount"] == 25 for tx in unspents])
343+
344+
assert_equal(alice.getrawmempool(), [])
345+
346+
# Alice spends first utxo to bob in tx1
347+
raw_tx = alice.createrawtransaction(inputs=[unspents[0]], outputs=[{bob.getnewaddress() : 24.9999}])
348+
tx1 = alice.signrawtransactionwithwallet(raw_tx)['hex']
349+
tx1_txid = alice.sendrawtransaction(tx1)
350+
351+
self.sync_mempools()
352+
353+
assert_equal(alice.getbalance(), 25)
354+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], Decimal("24.99990000"))
355+
356+
assert_equal(bob.gettransaction(tx1_txid)["mempoolconflicts"], [])
357+
358+
raw_tx = bob.createrawtransaction(inputs=[bob.listunspent(minconf=0)[0]], outputs=[{carol.getnewaddress() : 24.999}])
359+
# Bob creates a child to tx1
360+
tx1_child = bob.signrawtransactionwithwallet(raw_tx)['hex']
361+
tx1_child_txid = bob.sendrawtransaction(tx1_child)
362+
363+
self.sync_mempools()
364+
365+
# Currently neither tx1 nor tx1_child should have any conflicts
366+
assert_equal(bob.gettransaction(tx1_txid)["mempoolconflicts"], [])
367+
assert_equal(bob.gettransaction(tx1_child_txid)["mempoolconflicts"], [])
368+
assert tx1_txid in bob.getrawmempool()
369+
assert tx1_child_txid in bob.getrawmempool()
370+
assert_equal(len(bob.getrawmempool()), 2)
371+
372+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], 0)
373+
assert_equal(carol.getbalances()["mine"]["untrusted_pending"], Decimal("24.99900000"))
374+
375+
# Alice spends first unspent again, conflicting with tx1
376+
raw_tx = alice.createrawtransaction(inputs=[unspents[0], unspents[1]], outputs=[{carol.getnewaddress() : 49.99}])
377+
tx1_conflict = alice.signrawtransactionwithwallet(raw_tx)['hex']
378+
tx1_conflict_txid = alice.sendrawtransaction(tx1_conflict)
379+
380+
self.sync_mempools()
381+
382+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], 0)
383+
assert_equal(carol.getbalances()["mine"]["untrusted_pending"], Decimal("49.99000000"))
384+
385+
assert tx1_txid not in bob.getrawmempool()
386+
assert tx1_child_txid not in bob.getrawmempool()
387+
assert tx1_conflict_txid in bob.getrawmempool()
388+
assert_equal(len(bob.getrawmempool()), 1)
389+
390+
# Now both tx1 and tx1_child are conflicted by tx1_conflict
391+
assert_equal(bob.gettransaction(tx1_txid)["mempoolconflicts"], [tx1_conflict_txid])
392+
assert_equal(bob.gettransaction(tx1_child_txid)["mempoolconflicts"], [tx1_conflict_txid])
393+
394+
# Now create a conflict to tx1_conflict, so that it gets kicked out of the mempool
395+
raw_tx = alice.createrawtransaction(inputs=[unspents[1]], outputs=[{carol.getnewaddress() : 24.9895}])
396+
tx1_conflict_conflict = alice.signrawtransactionwithwallet(raw_tx)['hex']
397+
tx1_conflict_conflict_txid = alice.sendrawtransaction(tx1_conflict_conflict)
398+
399+
self.sync_mempools()
400+
401+
# Now that tx1_conflict has been removed, both tx1 and tx1_child
402+
assert_equal(bob.gettransaction(tx1_txid)["mempoolconflicts"], [])
403+
assert_equal(bob.gettransaction(tx1_child_txid)["mempoolconflicts"], [])
404+
405+
# Both tx1 and tx1_child are still not in the mempool because they have not be re-broadcasted
406+
assert tx1_txid not in bob.getrawmempool()
407+
assert tx1_child_txid not in bob.getrawmempool()
408+
assert tx1_conflict_txid not in bob.getrawmempool()
409+
assert tx1_conflict_conflict_txid in bob.getrawmempool()
410+
assert_equal(len(bob.getrawmempool()), 1)
411+
412+
assert_equal(alice.getbalance(), 0)
413+
assert_equal(bob.getbalances()["mine"]["untrusted_pending"], 0)
414+
assert_equal(carol.getbalances()["mine"]["untrusted_pending"], Decimal("24.98950000"))
415+
416+
# Both tx1 and tx1_child can now be re-broadcasted
417+
bob.sendrawtransaction(tx1)
418+
bob.sendrawtransaction(tx1_child)
419+
assert_equal(len(bob.getrawmempool()), 3)
420+
421+
alice.unloadwallet()
422+
bob.unloadwallet()
423+
carol.unloadwallet()
424+
139425
if __name__ == '__main__':
140426
TxConflicts(__file__).main()

test/functional/wallet_multisig_descriptor_psbt.py

+8-12
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ def skip_test_if_missing_module(self):
3030
self.skip_if_no_sqlite()
3131

3232
@staticmethod
33-
def _get_xpub(wallet):
33+
def _get_xpub(wallet, internal):
3434
"""Extract the wallet's xpubs using `listdescriptors` and pick the one from the `pkh` descriptor since it's least likely to be accidentally reused (legacy addresses)."""
35-
descriptor = next(filter(lambda d: d["desc"].startswith("pkh"), wallet.listdescriptors()["descriptors"]))
36-
return descriptor["desc"].split("]")[-1].split("/")[0]
35+
pkh_descriptor = next(filter(lambda d: d["desc"].startswith("pkh(") and d["internal"] == internal, wallet.listdescriptors()["descriptors"]))
36+
# Keep all key origin information (master key fingerprint and all derivation steps) for proper support of hardware devices
37+
# See section 'Key origin identification' in 'doc/descriptors.md' for more details...
38+
return pkh_descriptor["desc"].split("pkh(")[1].split(")")[0]
3739

3840
@staticmethod
3941
def _check_psbt(psbt, to, value, multisig):
@@ -47,14 +49,8 @@ def _check_psbt(psbt, to, value, multisig):
4749
amount += vout["value"]
4850
assert_approx(amount, float(value), vspan=0.001)
4951

50-
def participants_create_multisigs(self, xpubs):
52+
def participants_create_multisigs(self, external_xpubs, internal_xpubs):
5153
"""The multisig is created by importing the following descriptors. The resulting wallet is watch-only and every participant can do this."""
52-
# some simple validation
53-
assert_equal(len(xpubs), self.N)
54-
# a sanity-check/assertion, this will throw if the base58 checksum of any of the provided xpubs are invalid
55-
for xpub in xpubs:
56-
base58_to_byte(xpub)
57-
5854
for i, node in enumerate(self.nodes):
5955
node.createwallet(wallet_name=f"{self.name}_{i}", blank=True, descriptors=True, disable_private_keys=True)
6056
multisig = node.get_wallet_rpc(f"{self.name}_{i}")
@@ -93,10 +89,10 @@ def run_test(self):
9389
}
9490

9591
self.log.info("Generate and exchange xpubs...")
96-
xpubs = [self._get_xpub(signer) for signer in participants["signers"]]
92+
external_xpubs, internal_xpubs = [[self._get_xpub(signer, internal) for signer in participants["signers"]] for internal in [False, True]]
9793

9894
self.log.info("Every participant imports the following descriptors to create the watch-only multisig...")
99-
participants["multisigs"] = list(self.participants_create_multisigs(xpubs))
95+
participants["multisigs"] = list(self.participants_create_multisigs(external_xpubs, internal_xpubs))
10096

10197
self.log.info("Check that every participant's multisig generates the same addresses...")
10298
for _ in range(10): # we check that the first 10 generated addresses are the same for all participant's multisigs

0 commit comments

Comments
 (0)