ttest_transactionmonitor.py - electrum-personal-server - Maximally lightweight electrum server for a single user
HTML git clone https://git.parazyd.org/electrum-personal-server
DIR Log
DIR Files
DIR Refs
DIR README
---
ttest_transactionmonitor.py (29287B)
---
1
2 import pytest
3 import logging
4
5 from electrumpersonalserver.server import (
6 DeterministicWallet,
7 TransactionMonitor,
8 JsonRpcError,
9 script_to_scripthash
10 )
11
12 logger = logging.getLogger('ELECTRUMPERSONALSERVER-TEST')
13 logger.setLevel(logging.DEBUG)
14
15 class DummyJsonRpc(object):
16 """
17 Electrum Personal Server gets all its information about the bitcoin network
18 from the json-rpc interface. This dummy interface is used for simulating
19 events in bitcoin
20 """
21 def __init__(self, txlist, utxoset, block_heights):
22 self.txlist = txlist
23 self.utxoset = utxoset
24 self.block_heights = block_heights
25 self.imported_addresses = []
26
27 def call(self, method, params):
28 if method == "listtransactions":
29 count = int(params[1])
30 skip = int(params[2])
31 return self.txlist[skip:skip + count][::-1]
32 elif method == "gettransaction":
33 for t in self.txlist:
34 if t["txid"] == params[0]:
35 return t
36 raise JsonRpcError()
37 elif method == "decoderawtransaction":
38 for t in self.txlist:
39 if t["hex"] == params[0]:
40 return t
41 logger.debug(params[0])
42 assert 0
43 elif method == "gettxout":
44 for u in self.utxoset:
45 if u["txid"] == params[0] and u["vout"] == params[1]:
46 return u
47 logger.debug("txid = " + params[0] + " vout = " + str(params[1]))
48 assert 0
49 elif method == "getblockheader":
50 if params[0] in self.block_heights:
51 return {"height": self.block_heights[params[0]]}
52 logger.debug(params[0])
53 assert 0
54 elif method == "decodescript":
55 return {"addresses": [dummy_spk_to_address(params[0])]}
56 elif method == "importaddress":
57 self.imported_addresses.append(params[0])
58 elif method == "getmempoolentry":
59 for t in self.txlist:
60 if t["txid"] == params[0]:
61 return {"fees": {"base": 0},
62 "ancestorcount":
63 1 if t["vin"][0]["confirmations"] > 0 else 2}
64 logger.debug(params[0])
65 assert 0
66 else:
67 raise ValueError("unknown method in dummy jsonrpc " + method)
68
69 def add_transaction(self, tx):
70 self.txlist = [tx] + self.txlist
71
72 def get_imported_addresses(self):
73 return self.imported_addresses
74
75
76 class DummyDeterministicWallet(DeterministicWallet):
77 """Empty deterministic wallets"""
78 def __init__(self):
79 pass
80
81 def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys):
82 return None #not overrun
83
84 def get_new_scriptpubkeys(self, change, count):
85 pass
86
87
88 def dummy_spk_to_address(spk):
89 ##spk is short for scriptPubKey
90 return spk + "-address"
91
92 deterministic_wallets = [DummyDeterministicWallet()]
93 dummy_id_g = [1000]
94
95 def create_dummy_spk(): #script pub key
96 dummy_id = dummy_id_g[0]
97 dummy_id_g[0] += 1
98 return "deadbeef" + str(dummy_id)
99
100 def create_dummy_funding_tx(confirmations=1, output_spk=None,
101 input_txid="placeholder-unknown-input-txid", coinbase=False,
102 input_confirmations=1):
103 dummy_id = dummy_id_g[0]
104 dummy_id_g[0] += 1
105
106 if output_spk == None:
107 dummy_spk = "deadbeef" + str(dummy_id) #scriptpubkey
108 else:
109 dummy_spk = output_spk
110 dummy_containing_block = "blockhash-placeholder" + str(dummy_id)
111 containing_block_height = dummy_id
112 category = "receive"
113 vin = [{"txid": input_txid, "vout": 0, "value": 1,
114 "confirmations": input_confirmations}]
115 if coinbase:
116 vin = [{"coinbase": "nonce"}]
117 if confirmations < 1:
118 category = "orphan"
119 elif confirmations <= 100:
120 category = "immature"
121 else:
122 category = "generate"
123 dummy_tx = {
124 "txid": "placeholder-test-txid" + str(dummy_id),
125 "vin": vin,
126 "vout": [{"value": 1, "scriptPubKey": {"hex": dummy_spk}}],
127 "address": dummy_spk_to_address(dummy_spk),
128 "category": category,
129 "confirmations": confirmations,
130 "blockhash": dummy_containing_block,
131 "hex": "placeholder-test-txhex" + str(dummy_id)
132 }
133 logger.debug("created dummy tx: " + str(dummy_tx))
134 return dummy_spk, containing_block_height, dummy_tx
135
136 def assert_address_history_tx(address_history, spk, height, txid, subscribed):
137 history_element = address_history[script_to_scripthash(spk)]
138 assert history_element["history"][0]["height"] == height
139 assert history_element["history"][0]["tx_hash"] == txid
140 #fee always zero, its easier to test because otherwise you have
141 # to use Decimal to stop float weirdness
142 if height == 0:
143 assert history_element["history"][0]["fee"] == 0
144 assert history_element["subscribed"] == subscribed
145
146 def test_single_tx():
147 ###single confirmed tx in wallet belonging to us, address history built
148 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx()
149
150 rpc = DummyJsonRpc([dummy_tx], [],
151 {dummy_tx["blockhash"]: containing_block_height})
152 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
153 assert txmonitor.build_address_history([dummy_spk])
154 assert len(txmonitor.address_history) == 1
155 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk,
156 height=containing_block_height, txid=dummy_tx["txid"], subscribed=False)
157
158 def test_two_txes():
159 ###two confirmed txes in wallet belonging to us, addr history built
160 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
161 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx()
162
163 rpc = DummyJsonRpc([dummy_tx1, dummy_tx2], [],
164 {dummy_tx1["blockhash"]: containing_block_height1,
165 dummy_tx2["blockhash"]: containing_block_height2})
166 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
167 assert txmonitor.build_address_history([dummy_spk1, dummy_spk2])
168 assert len(txmonitor.address_history) == 2
169 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
170 height=containing_block_height1, txid=dummy_tx1["txid"],
171 subscribed=False)
172 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2,
173 height=containing_block_height2, txid=dummy_tx2["txid"],
174 subscribed=False)
175
176 def test_coinbase_txs():
177 ###two coinbase txs (mature and immature) in wallet, addr history built
178 ## two more coinbase txs added, addr history updated
179 ## orphaned coinbase txs not added to addr history
180 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx(
181 coinbase=True, confirmations=1)
182 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx(
183 coinbase=True, confirmations=101)
184 dummy_spk3, containing_block_height3, dummy_tx3 = create_dummy_funding_tx(
185 coinbase=True, confirmations=0)
186 dummy_spk4, containing_block_height4, dummy_tx4 = create_dummy_funding_tx(
187 coinbase=True, confirmations=1)
188 dummy_spk5, containing_block_height5, dummy_tx5 = create_dummy_funding_tx(
189 coinbase=True, confirmations=101)
190 dummy_spk6, containing_block_height6, dummy_tx6 = create_dummy_funding_tx(
191 coinbase=True, confirmations=0)
192
193 rpc = DummyJsonRpc([dummy_tx1, dummy_tx2, dummy_tx3], [],
194 {dummy_tx1["blockhash"]: containing_block_height1,
195 dummy_tx2["blockhash"]: containing_block_height2,
196 dummy_tx3["blockhash"]: containing_block_height3,
197 dummy_tx4["blockhash"]: containing_block_height4,
198 dummy_tx5["blockhash"]: containing_block_height5,
199 dummy_tx6["blockhash"]: containing_block_height6})
200 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
201 assert txmonitor.build_address_history([dummy_spk1, dummy_spk2, dummy_spk3,
202 dummy_spk4, dummy_spk5, dummy_spk6])
203 assert len(txmonitor.address_history) == 6
204 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
205 height=containing_block_height1, txid=dummy_tx1["txid"],
206 subscribed=False)
207 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2,
208 height=containing_block_height2, txid=dummy_tx2["txid"],
209 subscribed=False)
210 sh3 = script_to_scripthash(dummy_spk3)
211 assert len(txmonitor.get_electrum_history(sh3)) == 0
212
213 rpc.add_transaction(dummy_tx4)
214 rpc.add_transaction(dummy_tx5)
215 rpc.add_transaction(dummy_tx6)
216 assert len(list(txmonitor.check_for_updated_txes())) == 0
217 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk4,
218 height=containing_block_height4, txid=dummy_tx4["txid"],
219 subscribed=False)
220 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk5,
221 height=containing_block_height5, txid=dummy_tx5["txid"],
222 subscribed=False)
223 sh6 = script_to_scripthash(dummy_spk6)
224 assert len(txmonitor.get_electrum_history(sh6)) == 0
225
226 #test orphan tx is removed from history
227 dummy_tx1["confirmations"] = 0
228 dummy_tx1["category"] = "orphan"
229 assert len(list(txmonitor.check_for_updated_txes())) == 0
230 sh1 = script_to_scripthash(dummy_spk1)
231 assert len(txmonitor.get_electrum_history(sh1)) == 0
232
233 def test_many_txes():
234 ##many txes in wallet and many more added,, intended to test the loop
235 ## in build_addr_history and check_for_new_txes()
236 input_spk, input_block_height1, input_tx = create_dummy_funding_tx()
237 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx(
238 confirmations=0, input_txid=input_tx["vin"][0])
239 sh = script_to_scripthash(dummy_spk)
240
241 #batch size is 1000
242 INITIAL_TX_COUNT = 1100
243 txes = [dummy_tx]
244 #0confirm to avoid having to obtain block hash
245 txes.extend( (create_dummy_funding_tx(output_spk=dummy_spk,
246 input_txid=input_tx["vin"][0], confirmations=0)[2]
247 for i in range(INITIAL_TX_COUNT-1)) )
248 assert len(txes) == INITIAL_TX_COUNT
249
250 rpc = DummyJsonRpc(txes, [dummy_tx["vin"][0]], {})
251 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
252 assert txmonitor.build_address_history([dummy_spk])
253 assert len(txmonitor.address_history) == 1
254 assert len(list(txmonitor.check_for_updated_txes())) == 0
255 assert len(txmonitor.address_history[sh]["history"]) == INITIAL_TX_COUNT
256
257 ADDED_TX_COUNT = 130
258 new_txes = []
259 new_txes.extend( (create_dummy_funding_tx(output_spk=dummy_spk,
260 input_txid=input_tx["vin"][0], confirmations=0)[2]
261 for i in range(ADDED_TX_COUNT)) )
262
263 for tx in new_txes:
264 rpc.add_transaction(tx)
265 assert len(list(txmonitor.check_for_updated_txes())) == 0
266 assert len(txmonitor.address_history[sh]["history"]) == (INITIAL_TX_COUNT
267 + ADDED_TX_COUNT)
268
269 def test_non_subscribed_confirmation():
270 ###one unconfirmed tx in wallet belonging to us, with confirmed inputs,
271 ### addr history built, then tx confirms, not subscribed to address
272 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx(
273 confirmations=0)
274
275 rpc = DummyJsonRpc([dummy_tx], [dummy_tx["vin"][0]],
276 {dummy_tx["blockhash"]: containing_block_height})
277 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
278 assert txmonitor.build_address_history([dummy_spk])
279 assert len(txmonitor.address_history) == 1
280 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk,
281 height=0, txid=dummy_tx["txid"], subscribed=False)
282 assert len(list(txmonitor.check_for_updated_txes())) == 0
283 dummy_tx["confirmations"] = 1 #tx confirms
284 #not subscribed so still only returns an empty list
285 assert len(list(txmonitor.check_for_updated_txes())) == 0
286 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk,
287 height=containing_block_height, txid=dummy_tx["txid"], subscribed=False)
288
289 def test_tx_arrival_then_confirmation():
290 ###build empty address history, subscribe one address
291 ### an unconfirmed tx appears, then confirms
292 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx(
293 confirmations=0)
294
295 rpc = DummyJsonRpc([], [dummy_tx["vin"][0]], {dummy_tx["blockhash"]:
296 containing_block_height})
297 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
298 assert txmonitor.build_address_history([dummy_spk])
299 assert len(txmonitor.address_history) == 1
300 sh = script_to_scripthash(dummy_spk)
301 assert len(txmonitor.get_electrum_history(sh)) == 0
302 txmonitor.subscribe_address(sh)
303 # unconfirm transaction appears
304 assert len(list(txmonitor.check_for_updated_txes())) == 0
305 rpc.add_transaction(dummy_tx)
306 assert len(list(txmonitor.check_for_updated_txes())) == 1
307 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk,
308 height=0, txid=dummy_tx["txid"], subscribed=True)
309 # transaction confirms
310 dummy_tx["confirmations"] = 1
311 assert len(list(txmonitor.check_for_updated_txes())) == 1
312 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk,
313 height=containing_block_height, txid=dummy_tx["txid"], subscribed=True)
314
315 def test_unrelated_tx():
316 ###transaction that has nothing to do with our wallet
317 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx(
318 confirmations=0)
319 our_dummy_spk = create_dummy_spk()
320
321 rpc = DummyJsonRpc([dummy_tx], [], {dummy_tx["blockhash"]:
322 containing_block_height})
323 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
324 assert txmonitor.build_address_history([our_dummy_spk])
325 assert len(txmonitor.address_history) == 1
326 assert len(txmonitor.get_electrum_history(script_to_scripthash(
327 our_dummy_spk))) == 0
328
329 def test_duplicate_txid():
330 ###two txes with the same txid, built history
331 dummy_spk, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
332 dummy_spk, containing_block_height2, dummy_tx2 = create_dummy_funding_tx(
333 output_spk=dummy_spk)
334 dummy_spk, containing_block_height3, dummy_tx3 = create_dummy_funding_tx(
335 output_spk=dummy_spk)
336 dummy_tx2["txid"] = dummy_tx1["txid"]
337 dummy_tx3["txid"] = dummy_tx1["txid"]
338 sh = script_to_scripthash(dummy_spk)
339 rpc = DummyJsonRpc([dummy_tx1, dummy_tx2], [], {dummy_tx1["blockhash"]:
340 containing_block_height1, dummy_tx2["blockhash"]:
341 containing_block_height2, dummy_tx3["blockhash"]:
342 containing_block_height3})
343 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
344 assert txmonitor.build_address_history([dummy_spk])
345 assert len(txmonitor.get_electrum_history(sh)) == 1
346 txmonitor.subscribe_address(sh)
347 assert txmonitor.get_electrum_history(sh)[0]["tx_hash"] == dummy_tx1["txid"]
348 rpc.add_transaction(dummy_tx3)
349 assert len(list(txmonitor.check_for_updated_txes())) == 1
350 assert len(txmonitor.get_electrum_history(sh)) == 1
351 assert txmonitor.get_electrum_history(sh)[0]["tx_hash"] == dummy_tx1["txid"]
352
353 def test_address_reuse():
354 ###transaction which arrives to an address which already has a tx on it
355 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
356 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx(
357 output_spk=dummy_spk1)
358
359 rpc = DummyJsonRpc([dummy_tx1], [], {dummy_tx1["blockhash"]:
360 containing_block_height1, dummy_tx2["blockhash"]:
361 containing_block_height2})
362 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
363 assert txmonitor.build_address_history([dummy_spk1])
364 sh = script_to_scripthash(dummy_spk1)
365 assert len(txmonitor.get_electrum_history(sh)) == 1
366 rpc.add_transaction(dummy_tx2)
367 assert len(txmonitor.get_electrum_history(sh)) == 1
368 txmonitor.check_for_updated_txes()
369 assert len(txmonitor.get_electrum_history(sh)) == 2
370
371 def test_from_address():
372 ###transaction spending FROM one of our addresses
373 dummy_spk1, containing_block_height1, input_tx = create_dummy_funding_tx()
374 dummy_spk2, containing_block_height2, spending_tx = create_dummy_funding_tx(
375 input_txid=input_tx["txid"])
376
377 rpc = DummyJsonRpc([input_tx, spending_tx], [],
378 {input_tx["blockhash"]: containing_block_height1,
379 spending_tx["blockhash"]: containing_block_height2})
380 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
381 assert txmonitor.build_address_history([dummy_spk1])
382 sh = script_to_scripthash(dummy_spk1)
383 assert len(txmonitor.get_electrum_history(sh)) == 2
384
385 def test_tx_within_wallet():
386 ###transaction from one address to the other, both addresses in wallet
387 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
388 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx(
389 input_txid=dummy_tx1["txid"])
390
391 rpc = DummyJsonRpc([dummy_tx1, dummy_tx2], [],
392 {dummy_tx1["blockhash"]: containing_block_height1,
393 dummy_tx2["blockhash"]: containing_block_height2})
394 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
395 assert txmonitor.build_address_history([dummy_spk1, dummy_spk2])
396 assert len(txmonitor.get_electrum_history(script_to_scripthash(
397 dummy_spk1))) == 2
398 assert len(txmonitor.get_electrum_history(script_to_scripthash(
399 dummy_spk2))) == 1
400
401 def test_tx_with_unconfirmed_input():
402 ###unconfirmed tx arrives with unconfirmed input, which then both confirm
403
404 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx(
405 confirmations=0)
406 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx(
407 confirmations=0, input_txid=dummy_tx1["txid"], input_confirmations=0)
408
409 rpc = DummyJsonRpc([], [dummy_tx1["vin"][0], dummy_tx2["vin"][0]],
410 {dummy_tx1["blockhash"]: containing_block_height1,
411 dummy_tx2["blockhash"]: containing_block_height2})
412 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
413
414 assert txmonitor.build_address_history([dummy_spk1, dummy_spk2])
415 assert len(txmonitor.address_history) == 2
416
417 sh1 = script_to_scripthash(dummy_spk1)
418 sh2 = script_to_scripthash(dummy_spk2)
419 assert len(txmonitor.get_electrum_history(sh1)) == 0
420 assert len(txmonitor.get_electrum_history(sh2)) == 0
421 txmonitor.subscribe_address(sh1)
422 txmonitor.subscribe_address(sh2)
423
424 #the unconfirmed transactions appear
425 assert len(list(txmonitor.check_for_updated_txes())) == 0
426 rpc.add_transaction(dummy_tx1)
427 rpc.add_transaction(dummy_tx2)
428 assert len(list(txmonitor.check_for_updated_txes())) == 2
429 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
430 height=0, txid=dummy_tx1["txid"], subscribed=True)
431 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2,
432 height=-1, txid=dummy_tx2["txid"], subscribed=True)
433
434 #the transactions confirm
435 dummy_tx1["confirmations"] = 1
436 dummy_tx2["confirmations"] = 1
437
438 assert len(list(txmonitor.check_for_updated_txes())) == 2
439 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
440 height=containing_block_height1, txid=dummy_tx1["txid"],
441 subscribed=True)
442 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2,
443 height=containing_block_height2, txid=dummy_tx2["txid"],
444 subscribed=True)
445
446 def test_overrun_gap_limit():
447 ###overrun gap limit so import address is needed
448 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx()
449 dummy_spk_imported = create_dummy_spk()
450
451 class DummyImportDeterministicWallet(DeterministicWallet):
452 def __init__(self):
453 pass
454
455 def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys):
456 return {0: 1} #overrun by one
457
458 def get_new_scriptpubkeys(self, change, count):
459 return [dummy_spk_imported]
460
461 rpc = DummyJsonRpc([], [], {dummy_tx["blockhash"]: containing_block_height})
462 txmonitor = TransactionMonitor(rpc, [DummyImportDeterministicWallet()],
463 logger)
464 assert txmonitor.build_address_history([dummy_spk])
465 assert len(txmonitor.address_history) == 1
466 assert len(list(txmonitor.check_for_updated_txes())) == 0
467 assert len(txmonitor.get_electrum_history(script_to_scripthash(
468 dummy_spk))) == 0
469 rpc.add_transaction(dummy_tx)
470 assert len(list(txmonitor.check_for_updated_txes())) == 0
471 assert len(txmonitor.get_electrum_history(script_to_scripthash(
472 dummy_spk))) == 1
473 assert len(txmonitor.get_electrum_history(script_to_scripthash(
474 dummy_spk_imported))) == 0
475 assert len(rpc.get_imported_addresses()) == 1
476 assert rpc.get_imported_addresses()[0] == dummy_spk_to_address(
477 dummy_spk_imported)
478
479 def test_conflicted_tx():
480 ###conflicted transaction should get rejected
481 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx(
482 confirmations=-1)
483 rpc = DummyJsonRpc([dummy_tx], [], {})
484 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
485 sh = script_to_scripthash(dummy_spk)
486
487 assert txmonitor.build_address_history([dummy_spk])
488 assert len(txmonitor.address_history) == 1
489 #shouldnt show up after build history because conflicted
490 assert len(txmonitor.get_electrum_history(sh)) == 0
491
492 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx(
493 confirmations=-1, output_spk=dummy_spk)
494 rpc.add_transaction(dummy_tx)
495 assert len(list(txmonitor.check_for_updated_txes())) == 0
496 #incoming tx is not added either
497 assert len(txmonitor.get_electrum_history(sh)) == 0
498
499 def test_reorg_finney_attack():
500 ###an unconfirmed tx being broadcast, another conflicting tx being
501 ### confirmed, the first tx gets conflicted status
502
503 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx(
504 confirmations=0)
505 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx(
506 confirmations=0, input_txid=dummy_tx1["vin"][0])
507 #two unconfirmed txes spending the same input, so they are in conflict
508
509 rpc = DummyJsonRpc([dummy_tx1], [dummy_tx1["vin"][0]],
510 {dummy_tx1["blockhash"]: containing_block_height1,
511 dummy_tx2["blockhash"]: containing_block_height2})
512 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
513 assert txmonitor.build_address_history([dummy_spk1, dummy_spk2])
514 assert len(txmonitor.address_history) == 2
515 sh1 = script_to_scripthash(dummy_spk1)
516 sh2 = script_to_scripthash(dummy_spk2)
517 assert len(txmonitor.get_electrum_history(sh1)) == 1
518 assert len(txmonitor.get_electrum_history(sh2)) == 0
519 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
520 height=0, txid=dummy_tx1["txid"], subscribed=False)
521 # a conflicting transaction confirms
522 rpc.add_transaction(dummy_tx2)
523 dummy_tx1["confirmations"] = -1
524 dummy_tx2["confirmations"] = 1
525 assert len(list(txmonitor.check_for_updated_txes())) == 0
526 assert len(txmonitor.get_electrum_history(sh1)) == 0
527 assert len(txmonitor.get_electrum_history(sh2)) == 1
528 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2,
529 height=containing_block_height2, txid=dummy_tx2["txid"],
530 subscribed=False)
531
532 def test_reorg_race_attack():
533 #a tx is confirmed, a chain reorganization happens and that tx is replaced
534 # by another tx spending the same input, the original tx is now conflicted
535 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
536 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx(
537 input_txid=dummy_tx1["vin"][0])
538
539 rpc = DummyJsonRpc([dummy_tx1], [],
540 {dummy_tx1["blockhash"]: containing_block_height1,
541 dummy_tx2["blockhash"]: containing_block_height2})
542 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
543 assert txmonitor.build_address_history([dummy_spk1, dummy_spk2])
544 assert len(txmonitor.address_history) == 2
545 sh1 = script_to_scripthash(dummy_spk1)
546 sh2 = script_to_scripthash(dummy_spk2)
547 assert len(txmonitor.get_electrum_history(sh1)) == 1
548 assert len(txmonitor.get_electrum_history(sh2)) == 0
549 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
550 height=containing_block_height1, txid=dummy_tx1["txid"],
551 subscribed=False)
552 #race attack happens
553 #dummy_tx1 goes to -1 confirmations, dummy_tx2 gets confirmed
554 rpc.add_transaction(dummy_tx2)
555 dummy_tx1["confirmations"] = -1
556 dummy_tx2["confirmations"] = 1
557 assert len(list(txmonitor.check_for_updated_txes())) == 0
558 assert len(txmonitor.get_electrum_history(sh1)) == 0
559 assert len(txmonitor.get_electrum_history(sh2)) == 1
560 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2,
561 height=containing_block_height2, txid=dummy_tx2["txid"],
562 subscribed=False)
563
564 def test_reorg_censor_tx():
565 #confirmed tx gets reorgd out and becomes unconfirmed
566 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
567
568 rpc = DummyJsonRpc([dummy_tx1], [dummy_tx1["vin"][0]],
569 {dummy_tx1["blockhash"]: containing_block_height1})
570 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
571 assert txmonitor.build_address_history([dummy_spk1])
572 assert len(txmonitor.address_history) == 1
573 sh = script_to_scripthash(dummy_spk1)
574 assert len(txmonitor.get_electrum_history(sh)) == 1
575 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
576 height=containing_block_height1, txid=dummy_tx1["txid"],
577 subscribed=False)
578 #blocks appear which reorg out the tx, making it unconfirmed
579 dummy_tx1["confirmations"] = 0
580 assert len(list(txmonitor.check_for_updated_txes())) == 0
581 assert len(txmonitor.get_electrum_history(sh)) == 1
582 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
583 height=0, txid=dummy_tx1["txid"], subscribed=False)
584
585 def test_reorg_different_block():
586 #confirmed tx gets reorged into another block with a different height
587 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
588 dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx()
589
590 rpc = DummyJsonRpc([dummy_tx1], [],
591 {dummy_tx1["blockhash"]: containing_block_height1,
592 dummy_tx2["blockhash"]: containing_block_height2})
593 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
594 assert txmonitor.build_address_history([dummy_spk1])
595 assert len(txmonitor.address_history) == 1
596 sh = script_to_scripthash(dummy_spk1)
597 assert len(txmonitor.get_electrum_history(sh)) == 1
598 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
599 height=containing_block_height1, txid=dummy_tx1["txid"],
600 subscribed=False)
601
602 #tx gets reorged into another block (so still confirmed)
603 dummy_tx1["blockhash"] = dummy_tx2["blockhash"]
604 assert len(list(txmonitor.check_for_updated_txes())) == 0
605 assert len(txmonitor.get_electrum_history(sh)) == 1
606 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1,
607 height=containing_block_height2, txid=dummy_tx1["txid"],
608 subscribed=False)
609
610 def test_tx_safe_from_reorg():
611 ##tx confirmed with 1 confirmation, then confirmations goes to 100
612 ## test that the reorganizable_txes list length goes down
613 dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx()
614 rpc = DummyJsonRpc([dummy_tx1], [],
615 {dummy_tx1["blockhash"]: containing_block_height1})
616 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
617 assert txmonitor.build_address_history([dummy_spk1])
618 assert len(list(txmonitor.check_for_updated_txes())) == 0
619 assert len(txmonitor.reorganizable_txes) == 1
620 dummy_tx1["confirmations"] = 2000
621 assert len(list(txmonitor.check_for_updated_txes())) == 0
622 assert len(txmonitor.reorganizable_txes) == 0
623
624 #other possible stuff to test:
625 #finding confirmed and unconfirmed tx, in that order, then both confirm
626 #finding unconfirmed and confirmed tx, in that order, then both confirm
627
628 def test_single_tx_no_address_key():
629 ### same as test_single_tx() but the result of listtransactions has no
630 ### address field, see the github issue #31
631 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx()
632 del dummy_tx["address"]
633 logger.info("dummy_tx with no address = " + str(dummy_tx))
634 print("pdummy_tx with no address = " + str(dummy_tx))
635
636 rpc = DummyJsonRpc([dummy_tx], [],
637 {dummy_tx["blockhash"]: containing_block_height})
638 txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger)
639 assert txmonitor.build_address_history([dummy_spk])
640 assert len(txmonitor.address_history) == 1
641 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk,
642 height=containing_block_height, txid=dummy_tx["txid"], subscribed=False)
643