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_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_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_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_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_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 assert_address_history_tx(address_history, spk, height, txid, subscribed):
    history_element = address_history[script_to_scripthash(spk)]
    assert history_element["history"][0]["height"] == height
    assert history_element["history"][0]["tx_hash"] == txid
    #fee always zero, its easier to test because otherwise you have
    # to use Decimal to stop float weirdness
    if height == 0:
        assert history_element["history"][0]["fee"] == 0
    assert history_element["subscribed"] == subscribed
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_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_many_txes():
    ##many txes in wallet and many more added,, intended to test the loop
    ## in build_addr_history and check_for_new_txes()
    input_spk, input_block_height1, input_tx = create_dummy_funding_tx()
    dummy_spk, containing_block_height, dummy_tx = create_dummy_funding_tx(
        confirmations=0, input_txid=input_tx["vin"][0])
    sh = script_to_scripthash(dummy_spk)

    #batch size is 1000
    INITIAL_TX_COUNT = 1100
    txes = [dummy_tx]
    #0confirm to avoid having to obtain block hash
    txes.extend((create_dummy_funding_tx(output_spk=dummy_spk,
                                         input_txid=input_tx["vin"][0],
                                         confirmations=0)[2]
                 for i in range(INITIAL_TX_COUNT - 1)))
    assert len(txes) == INITIAL_TX_COUNT

    rpc = DummyJsonRpc(txes, [dummy_tx["vin"][0]], {})
    txmonitor = TransactionMonitor(rpc, deterministic_wallets, 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.address_history[sh]["history"]) == INITIAL_TX_COUNT

    ADDED_TX_COUNT = 130
    new_txes = []
    new_txes.extend((create_dummy_funding_tx(output_spk=dummy_spk,
                                             input_txid=input_tx["vin"][0],
                                             confirmations=0)[2]
                     for i in range(ADDED_TX_COUNT)))

    for tx in new_txes:
        rpc.add_transaction(tx)
    assert len(list(txmonitor.check_for_updated_txes())) == 0
    assert len(txmonitor.address_history[sh]["history"]) == (INITIAL_TX_COUNT +
                                                             ADDED_TX_COUNT)
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