def test_monitor_chain_single_update(block_processor):
    # This test tests that if both threads try to add the same block to the queue, only the first one will make it
    chain_monitor = ChainMonitor([Queue(), Queue()], block_processor, bitcoind_feed_params)

    chain_monitor.polling_delta = 2

    # We will create a block and wait for the polling thread. Then check the queues to see that the block hash has only
    # been added once.
    chain_monitor.monitor_chain()
    chain_monitor.activate()
    generate_blocks(1)

    assert len(chain_monitor.receiving_queues) == 2

    queue0_block = chain_monitor.receiving_queues[0].get()
    queue1_block = chain_monitor.receiving_queues[1].get()
    assert queue0_block == queue1_block
    assert chain_monitor.receiving_queues[0].empty()
    assert chain_monitor.receiving_queues[1].empty()

    # The delta for polling is 2 secs, so let's wait and see
    time.sleep(2)
    assert chain_monitor.receiving_queues[0].empty()
    assert chain_monitor.receiving_queues[1].empty()

    # We can also force an update and see that it won't go through
    assert chain_monitor.enqueue(queue0_block) is False

    chain_monitor.terminate()
    # The zmq thread needs a block generation to release from the recv method.
    generate_blocks(1)
def test_monitor_chain_polling(block_processor_mock, monkeypatch):
    # Monkeypatch the BlockProcessor so the best tip remains unchanged
    fixed_tip = get_random_value_hex(32)
    monkeypatch.setattr(block_processor_mock, "get_best_block_hash", lambda blocking: fixed_tip)

    chain_monitor = ChainMonitor([Queue(), Queue()], block_processor_mock, bitcoind_feed_params)
    chain_monitor.last_tips = [fixed_tip]
    chain_monitor.polling_delta = 0.1

    # monitor_chain_polling runs until not terminated
    polling_thread = Thread(target=chain_monitor.monitor_chain_polling, daemon=True)
    polling_thread.start()

    # Check that nothing changes as long as a block is not generated
    for _ in range(5):
        assert chain_monitor.queue.empty()
        time.sleep(0.1)

    # And that it does if we generate a block
    monkeypatch.setattr(block_processor_mock, "get_best_block_hash", lambda blocking: get_random_value_hex(32))
    time.sleep(0.1)

    chain_monitor.queue.get()
    assert chain_monitor.queue.empty()

    # Check that the bitcoind_reachable event is cleared if the connection is lost, and set once it's recovered
    monkeypatch.setattr(block_processor_mock, "get_best_block_hash", mock_connection_refused_return)
    time.sleep(0.5)
    assert not chain_monitor.bitcoind_reachable.is_set()
    monkeypatch.delattr(block_processor_mock, "get_best_block_hash")
    time.sleep(0.5)
    assert chain_monitor.bitcoind_reachable.is_set()

    chain_monitor.terminate()
def test_monitor_chain_polling(db_manager, block_processor):
    # Try polling with the Watcher
    watcher_queue = Queue()
    chain_monitor = ChainMonitor(watcher_queue, Queue(), block_processor,
                                 bitcoind_feed_params)
    chain_monitor.best_tip = block_processor.get_best_block_hash()
    chain_monitor.polling_delta = 0.1

    # monitor_chain_polling runs until terminate if set
    polling_thread = Thread(target=chain_monitor.monitor_chain_polling,
                            daemon=True)
    polling_thread.start()

    # Check that nothing changes as long as a block is not generated
    for _ in range(5):
        assert chain_monitor.watcher_queue.empty()
        time.sleep(0.1)

    # And that it does if we generate a block
    generate_block()

    chain_monitor.watcher_queue.get()
    assert chain_monitor.watcher_queue.empty()

    chain_monitor.terminate = True
    polling_thread.join()
def test_monitor_chain_single_update(db_manager, block_processor):
    # This test tests that if both threads try to add the same block to the queue, only the first one will make it
    chain_monitor = ChainMonitor(Queue(), Queue(), block_processor,
                                 bitcoind_feed_params)

    chain_monitor.best_tip = None
    chain_monitor.polling_delta = 2

    # We will create a block and wait for the polling thread. Then check the queues to see that the block hash has only
    # been added once.
    chain_monitor.monitor_chain()
    generate_block()

    watcher_block = chain_monitor.watcher_queue.get()
    responder_block = chain_monitor.responder_queue.get()
    assert watcher_block == responder_block
    assert chain_monitor.watcher_queue.empty()
    assert chain_monitor.responder_queue.empty()

    # The delta for polling is 2 secs, so let's wait and see
    time.sleep(2)
    assert chain_monitor.watcher_queue.empty()
    assert chain_monitor.responder_queue.empty()

    # We can also force an update and see that it won't go through
    assert chain_monitor.update_state(watcher_block) is False
def test_terminate(block_processor):
    queue = Queue()
    chain_monitor = ChainMonitor([queue, Queue()], block_processor, bitcoind_feed_params)
    chain_monitor.polling_delta = 0.1

    chain_monitor.monitor_chain()
    chain_monitor.activate()

    chain_monitor.terminate()

    assert chain_monitor.status == ChainMonitorStatus.TERMINATED

    # generate a new block
    generate_blocks(1)
    time.sleep(0.11)  # wait longer than the polling_delta

    # there should be only the ChainMonitor.END_MESSAGE message in the receiving queue, as the new block was generated
    # after terminating
    assert queue.qsize() == 1
    assert queue.get() == ChainMonitor.END_MESSAGE
def test_monitor_chain_polling(block_processor):
    chain_monitor = ChainMonitor([Queue(), Queue()], block_processor, bitcoind_feed_params)
    chain_monitor.last_tips = [block_processor.get_best_block_hash()]
    chain_monitor.polling_delta = 0.1

    # monitor_chain_polling runs until not terminated
    polling_thread = Thread(target=chain_monitor.monitor_chain_polling, daemon=True)
    polling_thread.start()

    # Check that nothing changes as long as a block is not generated
    for _ in range(5):
        assert chain_monitor.queue.empty()
        time.sleep(0.1)

    # And that it does if we generate a block
    generate_blocks(1)

    chain_monitor.queue.get()
    assert chain_monitor.queue.empty()

    chain_monitor.terminate()
def test_monitor_chain_and_activate(block_processor):
    # In this test, we generate some blocks after `monitor_chain`, then `activate` and generate few more blocks.
    # We verify that all the generated blocks are indeed sent to the queues in the right order.

    queue1 = Queue()
    queue2 = Queue()

    # We add some initial blocks to the receiving queues, to simulate a bootstrap with previous information
    pre_blocks = [get_random_value_hex(32) for _ in range(5)]
    for block in pre_blocks:
        queue1.put(block)
        queue2.put(block)

    # We don't activate the ChainMonitor but we start listening; therefore received blocks should accumulate in the
    # internal queue
    chain_monitor = ChainMonitor([queue1, queue2], block_processor, bitcoind_feed_params)
    chain_monitor.polling_delta = 0.1

    chain_monitor.monitor_chain()
    assert chain_monitor.status == ChainMonitorStatus.LISTENING

    # we generate some blocks while the monitor is listening but not active
    init_blocks = generate_blocks_with_delay(3, 0.15)

    time.sleep(0.11)  # higher than the polling interval

    chain_monitor.activate()

    # generate some more blocks after activating
    after_blocks = generate_blocks_with_delay(3, 0.15)

    # we now check that all the blocks are in the receiving queues in the correct order
    all_blocks = pre_blocks + init_blocks + after_blocks
    for block in all_blocks:
        assert queue1.get(timeout=0.1) == block
        assert queue2.get(timeout=0.1) == block

    chain_monitor.terminate()
    # The zmq thread needs a block generation to release from the recv method.
    generate_blocks(1)
def test_terminate(block_processor_mock, monkeypatch):
    # Test that the ChainMonitor is stopped on a terminate signal
    queue = Queue()
    chain_monitor = ChainMonitor([queue, Queue()], block_processor_mock, bitcoind_feed_params)
    chain_monitor.polling_delta = 0.1

    # Activate the monitor
    chain_monitor.monitor_chain()
    chain_monitor.activate()

    # Ask it to terminate
    chain_monitor.terminate()
    assert chain_monitor.status == ChainMonitorStatus.TERMINATED

    # Mock generating a block generate a new block
    monkeypatch.setattr(block_processor_mock, "get_best_block_hash", lambda blocking: get_random_value_hex(32))
    time.sleep(0.11)  # wait longer than the polling_delta

    # there should be only the ChainMonitor.END_MESSAGE message in the receiving queue, as the new block was generated
    # after terminating
    assert queue.qsize() == 1
    assert queue.get() == ChainMonitor.END_MESSAGE
def test_monitor_chain(block_processor):
    # We don't activate it but we start listening; therefore received blocks should accumulate in the internal queue
    chain_monitor = ChainMonitor([Queue(), Queue()], block_processor, bitcoind_feed_params)
    chain_monitor.polling_delta = 0.1

    chain_monitor.monitor_chain()
    assert chain_monitor.status == ChainMonitorStatus.LISTENING

    # The tip is updated before starting the threads, so it should have been added to last_tips.
    assert len(chain_monitor.last_tips) > 0

    # Blocks should be received and added to the queue
    count = 0
    for _ in range(5):
        generate_blocks(1)
        count += 1
        time.sleep(0.11)  # higher than the polling interval
        assert chain_monitor.receiving_queues[0].empty()
        assert chain_monitor.receiving_queues[1].empty()
        assert chain_monitor.queue.qsize() == count

    chain_monitor.terminate()
    # The zmq thread needs a block generation to release from the recv method.
    generate_blocks(1)