def test_reorg_different_block(): #confirmed tx gets reorged into another block with a different height dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx() rpc = DummyJsonRpc( [dummy_tx1], [], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1]) assert len(txmonitor.address_history) == 1 sh = script_to_scripthash(dummy_spk1) assert len(txmonitor.get_electrum_history(sh)) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=containing_block_height1, txid=dummy_tx1["txid"], subscribed=False) #tx gets reorged into another block (so still confirmed) dummy_tx1["blockhash"] = dummy_tx2["blockhash"] assert len(list(txmonitor.check_for_updated_txes())) == 0 assert len(txmonitor.get_electrum_history(sh)) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=containing_block_height2, txid=dummy_tx1["txid"], subscribed=False)
def test_duplicate_txid(): ###two txes with the same txid, built history dummy_spk, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() dummy_spk, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( output_spk=dummy_spk) dummy_spk, containing_block_height3, dummy_tx3 = create_dummy_funding_tx( output_spk=dummy_spk) dummy_tx2["txid"] = dummy_tx1["txid"] dummy_tx3["txid"] = dummy_tx1["txid"] sh = script_to_scripthash(dummy_spk) rpc = DummyJsonRpc( [dummy_tx1, dummy_tx2], [], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2, dummy_tx3["blockhash"]: containing_block_height3 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk]) assert len(txmonitor.get_electrum_history(sh)) == 1 txmonitor.subscribe_address(sh) assert txmonitor.get_electrum_history( sh)[0]["tx_hash"] == dummy_tx1["txid"] rpc.add_transaction(dummy_tx3) assert len(list(txmonitor.check_for_updated_txes())) == 1 assert len(txmonitor.get_electrum_history(sh)) == 1 assert txmonitor.get_electrum_history( sh)[0]["tx_hash"] == dummy_tx1["txid"]
def test_reorg_censor_tx(): #confirmed tx gets reorgd out and becomes unconfirmed dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() rpc = DummyJsonRpc([dummy_tx1], [dummy_tx1["vin"][0]], {dummy_tx1["blockhash"]: containing_block_height1}) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1]) assert len(txmonitor.address_history) == 1 sh = script_to_scripthash(dummy_spk1) assert len(txmonitor.get_electrum_history(sh)) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=containing_block_height1, txid=dummy_tx1["txid"], subscribed=False) #blocks appear which reorg out the tx, making it unconfirmed dummy_tx1["confirmations"] = 0 assert len(list(txmonitor.check_for_updated_txes())) == 0 assert len(txmonitor.get_electrum_history(sh)) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=0, txid=dummy_tx1["txid"], subscribed=False)
def test_overrun_gap_limit(): ###overrun gap limit so import address is needed dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx() dummy_spk_imported = create_dummy_spk() class DummyImportDeterministicWallet(DeterministicWallet): def __init__(self): pass def have_scriptpubkeys_overrun_gaplimit(self, scriptpubkeys): return {0: 1} #overrun by one def get_new_scriptpubkeys(self, change, count): return [dummy_spk_imported] rpc = DummyJsonRpc([], [], {dummy_tx["blockhash"]: containing_block_height}) txmonitor = TransactionMonitor(rpc, [DummyImportDeterministicWallet()], logger) assert txmonitor.build_address_history([dummy_spk]) assert len(txmonitor.address_history) == 1 assert len(list(txmonitor.check_for_updated_txes())) == 0 assert len(txmonitor.get_electrum_history( script_to_scripthash(dummy_spk))) == 0 rpc.add_transaction(dummy_tx) assert len(list(txmonitor.check_for_updated_txes())) == 0 assert len(txmonitor.get_electrum_history( script_to_scripthash(dummy_spk))) == 1 assert len( txmonitor.get_electrum_history( script_to_scripthash(dummy_spk_imported))) == 0 assert len(rpc.get_imported_addresses()) == 1 assert rpc.get_imported_addresses()[0] == dummy_spk_to_address( dummy_spk_imported)
def test_tx_arrival_then_confirmation(): ###build empty address history, subscribe one address ### an unconfirmed tx appears, then confirms dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( confirmations=0) rpc = DummyJsonRpc([], [dummy_tx["vin"][0]], {dummy_tx["blockhash"]: containing_block_height}) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk]) assert len(txmonitor.address_history) == 1 sh = script_to_scripthash(dummy_spk) assert len(txmonitor.get_electrum_history(sh)) == 0 txmonitor.subscribe_address(sh) # unconfirm transaction appears assert len(list(txmonitor.check_for_updated_txes())) == 0 rpc.add_transaction(dummy_tx) assert len(list(txmonitor.check_for_updated_txes())) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, height=0, txid=dummy_tx["txid"], subscribed=True) # transaction confirms dummy_tx["confirmations"] = 1 assert len(list(txmonitor.check_for_updated_txes())) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk, height=containing_block_height, txid=dummy_tx["txid"], subscribed=True)
def test_tx_within_wallet(): ###transaction from one address to the other, both addresses in wallet dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( input_txid=dummy_tx1["txid"]) rpc = DummyJsonRpc( [dummy_tx1, dummy_tx2], [], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1, dummy_spk2]) assert len(txmonitor.get_electrum_history( script_to_scripthash(dummy_spk1))) == 2 assert len(txmonitor.get_electrum_history( script_to_scripthash(dummy_spk2))) == 1
def test_tx_with_unconfirmed_input(): ###unconfirmed tx arrives with unconfirmed input, which then both confirm dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx( confirmations=0) dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( confirmations=0, input_txid=dummy_tx1["txid"], input_confirmations=0) rpc = DummyJsonRpc( [], [dummy_tx1["vin"][0], dummy_tx2["vin"][0]], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1, dummy_spk2]) assert len(txmonitor.address_history) == 2 sh1 = script_to_scripthash(dummy_spk1) sh2 = script_to_scripthash(dummy_spk2) assert len(txmonitor.get_electrum_history(sh1)) == 0 assert len(txmonitor.get_electrum_history(sh2)) == 0 txmonitor.subscribe_address(sh1) txmonitor.subscribe_address(sh2) #the unconfirmed transactions appear assert len(list(txmonitor.check_for_updated_txes())) == 0 rpc.add_transaction(dummy_tx1) rpc.add_transaction(dummy_tx2) assert len(list(txmonitor.check_for_updated_txes())) == 2 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=0, txid=dummy_tx1["txid"], subscribed=True) assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2, height=-1, txid=dummy_tx2["txid"], subscribed=True) #the transactions confirm dummy_tx1["confirmations"] = 1 dummy_tx2["confirmations"] = 1 assert len(list(txmonitor.check_for_updated_txes())) == 2 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=containing_block_height1, txid=dummy_tx1["txid"], subscribed=True) assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2, height=containing_block_height2, txid=dummy_tx2["txid"], subscribed=True)
def test_conflicted_tx(): ###conflicted transaction should get rejected dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( confirmations=-1) rpc = DummyJsonRpc([dummy_tx], [], {}) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) sh = script_to_scripthash(dummy_spk) assert txmonitor.build_address_history([dummy_spk]) assert len(txmonitor.address_history) == 1 #shouldnt show up after build history because conflicted assert len(txmonitor.get_electrum_history(sh)) == 0 dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( confirmations=-1, output_spk=dummy_spk) rpc.add_transaction(dummy_tx) assert len(list(txmonitor.check_for_updated_txes())) == 0 #incoming tx is not added either assert len(txmonitor.get_electrum_history(sh)) == 0
def test_address_reuse(): ###transaction which arrives to an address which already has a tx on it dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( output_spk=dummy_spk1) rpc = DummyJsonRpc( [dummy_tx1], [], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1]) sh = script_to_scripthash(dummy_spk1) assert len(txmonitor.get_electrum_history(sh)) == 1 rpc.add_transaction(dummy_tx2) assert len(txmonitor.get_electrum_history(sh)) == 1 txmonitor.check_for_updated_txes() assert len(txmonitor.get_electrum_history(sh)) == 2
def test_reorg_finney_attack(): ###an unconfirmed tx being broadcast, another conflicting tx being ### confirmed, the first tx gets conflicted status dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx( confirmations=0) dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( confirmations=0, input_txid=dummy_tx1["vin"][0]) #two unconfirmed txes spending the same input, so they are in conflict rpc = DummyJsonRpc( [dummy_tx1], [dummy_tx1["vin"][0]], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1, dummy_spk2]) assert len(txmonitor.address_history) == 2 sh1 = script_to_scripthash(dummy_spk1) sh2 = script_to_scripthash(dummy_spk2) assert len(txmonitor.get_electrum_history(sh1)) == 1 assert len(txmonitor.get_electrum_history(sh2)) == 0 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=0, txid=dummy_tx1["txid"], subscribed=False) # a conflicting transaction confirms rpc.add_transaction(dummy_tx2) dummy_tx1["confirmations"] = -1 dummy_tx2["confirmations"] = 1 assert len(list(txmonitor.check_for_updated_txes())) == 0 assert len(txmonitor.get_electrum_history(sh1)) == 0 assert len(txmonitor.get_electrum_history(sh2)) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2, height=containing_block_height2, txid=dummy_tx2["txid"], subscribed=False)
def test_unrelated_tx(): ###transaction that has nothing to do with our wallet dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx( confirmations=0) our_dummy_spk = create_dummy_spk() rpc = DummyJsonRpc([dummy_tx], [], {dummy_tx["blockhash"]: containing_block_height}) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([our_dummy_spk]) assert len(txmonitor.address_history) == 1 assert len( txmonitor.get_electrum_history( script_to_scripthash(our_dummy_spk))) == 0
def test_reorg_race_attack(): #a tx is confirmed, a chain reorganization happens and that tx is replaced # by another tx spending the same input, the original tx is now conflicted dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx() dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( input_txid=dummy_tx1["vin"][0]) rpc = DummyJsonRpc( [dummy_tx1], [], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1, dummy_spk2]) assert len(txmonitor.address_history) == 2 sh1 = script_to_scripthash(dummy_spk1) sh2 = script_to_scripthash(dummy_spk2) assert len(txmonitor.get_electrum_history(sh1)) == 1 assert len(txmonitor.get_electrum_history(sh2)) == 0 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=containing_block_height1, txid=dummy_tx1["txid"], subscribed=False) #race attack happens #dummy_tx1 goes to -1 confirmations, dummy_tx2 gets confirmed rpc.add_transaction(dummy_tx2) dummy_tx1["confirmations"] = -1 dummy_tx2["confirmations"] = 1 assert len(list(txmonitor.check_for_updated_txes())) == 0 assert len(txmonitor.get_electrum_history(sh1)) == 0 assert len(txmonitor.get_electrum_history(sh2)) == 1 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2, height=containing_block_height2, txid=dummy_tx2["txid"], subscribed=False)
def test_from_address(): ###transaction spending FROM one of our addresses dummy_spk1, containing_block_height1, input_tx = create_dummy_funding_tx() dummy_spk2, containing_block_height2, spending_tx = create_dummy_funding_tx( input_txid=input_tx["txid"]) rpc = DummyJsonRpc( [input_tx, spending_tx], [], { input_tx["blockhash"]: containing_block_height1, spending_tx["blockhash"]: containing_block_height2 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([dummy_spk1]) sh = script_to_scripthash(dummy_spk1) assert len(txmonitor.get_electrum_history(sh)) == 2
def test_coinbase_txs(): ###two coinbase txs (mature and immature) in wallet, addr history built ## two more coinbase txs added, addr history updated ## orphaned coinbase txs not added to addr history dummy_spk1, containing_block_height1, dummy_tx1 = create_dummy_funding_tx( coinbase=True, confirmations=1) dummy_spk2, containing_block_height2, dummy_tx2 = create_dummy_funding_tx( coinbase=True, confirmations=101) dummy_spk3, containing_block_height3, dummy_tx3 = create_dummy_funding_tx( coinbase=True, confirmations=0) dummy_spk4, containing_block_height4, dummy_tx4 = create_dummy_funding_tx( coinbase=True, confirmations=1) dummy_spk5, containing_block_height5, dummy_tx5 = create_dummy_funding_tx( coinbase=True, confirmations=101) dummy_spk6, containing_block_height6, dummy_tx6 = create_dummy_funding_tx( coinbase=True, confirmations=0) rpc = DummyJsonRpc( [dummy_tx1, dummy_tx2, dummy_tx3], [], { dummy_tx1["blockhash"]: containing_block_height1, dummy_tx2["blockhash"]: containing_block_height2, dummy_tx3["blockhash"]: containing_block_height3, dummy_tx4["blockhash"]: containing_block_height4, dummy_tx5["blockhash"]: containing_block_height5, dummy_tx6["blockhash"]: containing_block_height6 }) txmonitor = TransactionMonitor(rpc, deterministic_wallets, logger) assert txmonitor.build_address_history([ dummy_spk1, dummy_spk2, dummy_spk3, dummy_spk4, dummy_spk5, dummy_spk6 ]) assert len(txmonitor.address_history) == 6 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk1, height=containing_block_height1, txid=dummy_tx1["txid"], subscribed=False) assert_address_history_tx(txmonitor.address_history, spk=dummy_spk2, height=containing_block_height2, txid=dummy_tx2["txid"], subscribed=False) sh3 = script_to_scripthash(dummy_spk3) assert len(txmonitor.get_electrum_history(sh3)) == 0 rpc.add_transaction(dummy_tx4) rpc.add_transaction(dummy_tx5) rpc.add_transaction(dummy_tx6) assert len(list(txmonitor.check_for_updated_txes())) == 0 assert_address_history_tx(txmonitor.address_history, spk=dummy_spk4, height=containing_block_height4, txid=dummy_tx4["txid"], subscribed=False) assert_address_history_tx(txmonitor.address_history, spk=dummy_spk5, height=containing_block_height5, txid=dummy_tx5["txid"], subscribed=False) sh6 = script_to_scripthash(dummy_spk6) assert len(txmonitor.get_electrum_history(sh6)) == 0 #test orphan tx is removed from history dummy_tx1["confirmations"] = 0 dummy_tx1["category"] = "orphan" assert len(list(txmonitor.check_for_updated_txes())) == 0 sh1 = script_to_scripthash(dummy_spk1) assert len(txmonitor.get_electrum_history(sh1)) == 0