test_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