Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ markers = [
"example: marks tests that demonstrate how to use the Floresta test framework",
"florestad: marks tests specific to the Florestad daemon",
"rpc: marks tests focused on RPC calls",
"electrum: marks tests related to Electrum server interactions",
]

minversion = "9.0"
Expand Down
45 changes: 45 additions & 0 deletions tests/electrum/blockchain_block_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-License-Identifier: MIT OR Apache-2.0

"""
Tests for block header retrieval and validation in the Electrum server.

This tests the blockchain.block.header endpoint which returns a block header
at a given height, optionally with a merkle proof for verification.
"""

import random
import pytest
from test_framework.util import wait_until

MINE_BLOCKS = 100


@pytest.mark.electrum
def test_block_header(florestad_utreexod):
"""Test block header retrieval and validation."""
florestad, utreexod = florestad_utreexod

utreexod.rpc.generate(MINE_BLOCKS)
wait_until(lambda: florestad.rpc.get_block_count() == MINE_BLOCKS)

with pytest.raises(ValueError):
florestad.electrum.block_header(MINE_BLOCKS + 1)

with pytest.raises(ValueError):
florestad.electrum.block_header(-1)

compare_headers(florestad, 0)
compare_headers(florestad, MINE_BLOCKS)

random_height = random.randint(1, MINE_BLOCKS - 1)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

randomness in tests is a big footgun, debug becomes really annoying if the test isn't reproducible

compare_headers(florestad, random_height)


def compare_headers(florestad, height):
"""Helper function to compare block headers from Electrum and RPC."""
electrum_header = florestad.electrum.block_header(height)

block_hash = florestad.rpc.get_blockhash(height)
rpc_header = florestad.rpc.get_blockheader(block_hash, verbosity=False)

assert electrum_header == rpc_header
4 changes: 2 additions & 2 deletions tests/example/electrum.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ def test_electrum(florestad_node):
"""
rpc_response = florestad_node.electrum.get_version()

assert rpc_response["result"][0] == EXPECTED_VERSION[0]
assert rpc_response["result"][1] == EXPECTED_VERSION[1]
assert rpc_response[0] == EXPECTED_VERSION[0]
assert rpc_response[1] == EXPECTED_VERSION[1]
16 changes: 6 additions & 10 deletions tests/floresta-cli/getbestblockhash.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@
and utreexod, respectively.
"""

import time
import pytest

TIMEOUT_SECONDS = 20
from test_framework.util import wait_until


@pytest.mark.rpc
Expand All @@ -27,14 +26,11 @@ def test_get_best_block_hash(florestad_utreexod):
assert floresta_best_block == utreexo_best_block

utreexod.rpc.generate(10)
end = time.time() + TIMEOUT_SECONDS
while time.time() < end:
floresta_block = florestad.rpc.get_block_count()
utreexo_block = utreexod.rpc.get_block_count()
if floresta_block == utreexo_block:
break

time.sleep(1)

wait_until(
predicate=lambda: florestad.rpc.get_block_count()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps you could give some specialized functions for common cases like this one?

== utreexod.rpc.get_block_count()
)

utreexo_chain = utreexod.rpc.get_blockchain_info()
floresta_best_block = florestad.rpc.get_bestblockhash()
Expand Down
15 changes: 5 additions & 10 deletions tests/floresta-cli/getblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,9 @@
import time
import random
from typing import Any

import pytest

TIMEOUT_SECONDS = 20
from test_framework.util import wait_until


class TestGetBlock:
Expand Down Expand Up @@ -44,14 +43,10 @@ def test_get_block(
self.node_manager.connect_nodes(self.florestad, self.bitcoind)

block_count = self.bitcoind.rpc.get_block_count()
end = time.time() + TIMEOUT_SECONDS
while time.time() < end:
floresta_count = self.florestad.rpc.get_block_count()
if floresta_count == block_count:
break
time.sleep(0.5)

assert floresta_count == block_count

wait_until(
predicate=lambda: self.florestad.rpc.get_block_count() == block_count
)

self.log.info("Testing getblock RPC in the genesis block")
self.compare_block(0)
Expand Down
17 changes: 6 additions & 11 deletions tests/floresta-cli/getblockcount.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
`blocks` and `height/validated` fields given in `getblockchaininfo`
of utreexod/bitcoind and floresta, respectively"""

import time
import pytest
from test_framework.util import wait_until

MINE_BLOCKS = 10
TIMEOUT_SECONDS = 20


@pytest.mark.rpc
Expand All @@ -28,15 +27,11 @@ def test_get_block_count(florestad_utreexod):

# Mine blocks with utreexod
utreexod.rpc.generate(MINE_BLOCKS)
timeout = time.time() + TIMEOUT_SECONDS
while time.time() < timeout:
if (
florestad.rpc.get_block_count()
== utreexod.rpc.get_block_count()
== MINE_BLOCKS
):
break
time.sleep(1)
wait_until(
predicate=lambda: florestad.rpc.get_block_count()
== utreexod.rpc.get_block_count()
== MINE_BLOCKS
)

# Get final block counts
final_florestad_count = florestad.rpc.get_block_count()
Expand Down
17 changes: 6 additions & 11 deletions tests/floresta-cli/getblockhash.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
This functional test cli utility to interact with a Floresta node with `getblockhash`
"""

import time
import pytest

from test_framework.constants import GENESIS_BLOCK_HASH
from test_framework.util import wait_until

MINED_BLOCKS = 10
TIMEOUT = 20


@pytest.mark.rpc
Expand All @@ -30,15 +29,11 @@ def test_get_block_hash(florestad_utreexod):

# Mine blocks with utreexod
utreexod.rpc.generate(MINED_BLOCKS)
timeout = time.time() + TIMEOUT
while time.time() < timeout:
if (
florestad.rpc.get_block_count()
== utreexod.rpc.get_block_count()
== MINED_BLOCKS
):
break
time.sleep(1)
wait_until(
predicate=lambda: florestad.rpc.get_block_count()
== utreexod.rpc.get_block_count()
== MINED_BLOCKS
)

# Get final block hashes
final_florestad_hash = florestad.rpc.get_blockhash(MINED_BLOCKS)
Expand Down
29 changes: 14 additions & 15 deletions tests/floresta-cli/gettxout.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
This functional test cli utility to interact with a Floresta node with `gettxout` command.
"""

import time
import pytest

TIMEOUT_SECONDS = 120
from test_framework.util import wait_until


# pylint: disable=too-many-locals
Expand All @@ -27,27 +26,27 @@ def test_get_txout(setup_logging, florestad_bitcoind_utreexod_with_chain):
best_block_hash = utreexod.rpc.get_blockhash(blocks)

log.info("Waiting for Floresta and Bitcoind to sync with Utreexod...")
timeout = time.time() + TIMEOUT_SECONDS
while time.time() < timeout:
floresta_info = florestad.rpc.get_blockchain_info()
if (
floresta_info["height"]
== utreexod.rpc.get_block_count()
== bitcoind.rpc.get_block_count()
== blocks
and not floresta_info["ibd"]
):
break

time.sleep(1)
def check_sync():
# Forcing a re-fetch of the block from the peer
try:
bitcoind.rpc.get_block_from_peer(best_block_hash, peer_id)
# pylint: disable=broad-exception-caught
except Exception as e:
log.error(f"Error fetching block from peer: {e}")

assert floresta_info["height"] == blocks and not floresta_info["ibd"]
floresta_info = florestad.rpc.get_blockchain_info()
return (
floresta_info["height"]
== utreexod.rpc.get_block_count()
== bitcoind.rpc.get_block_count()
and not floresta_info["ibd"]
)

wait_until(
check_sync,
error_msg="Floresta and Bitcoind did not sync with Utreexod within the timeout period.",
)

log.info("Comparing gettxout results between Floresta and Bitcoind...")
for height in range(2, blocks):
Expand Down
21 changes: 7 additions & 14 deletions tests/florestad/reorg_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
accumulator to make sure they are the same.
"""

import time
import pytest

from test_framework.util import wait_until


@pytest.mark.florestad
def test_reorg_chain(setup_logging, florestad_utreexod):
Expand Down Expand Up @@ -74,16 +75,8 @@ def mine_blocks(self, blocks):
self.log.info(f"Utreexod node mine {blocks} blocks")
self.utreexod.rpc.generate(blocks)

timeout = 30
end = time.time() + timeout
while time.time() < end:
florestad_block = self.florestad.rpc.get_block_count()
utreexod_block = self.utreexod.rpc.get_block_count()
if florestad_block == utreexod_block:
self.log.info(f"Nodes are in sync: {florestad_block} blocks")
break

time.sleep(1)

if florestad_block != utreexod_block:
pytest.fail("Florestad node did not sync with Utreexod node in time")
wait_until(
predicate=lambda: self.florestad.rpc.get_block_count()
== self.utreexod.rpc.get_block_count(),
error_msg="Florestad node did not sync with Utreexod node in time.",
)
4 changes: 1 addition & 3 deletions tests/florestad/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,4 @@ def test_tls(add_node_with_tls):
assert florestad.electrum.tls

response = florestad.electrum.ping()
assert response["result"] is None
assert response["id"] == 0
assert response["jsonrpc"] == "2.0"
assert response is None
61 changes: 31 additions & 30 deletions tests/test_framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from test_framework.rpc import ConfigRPC
from test_framework.electrum import ConfigElectrum, ConfigTls
from test_framework.node import Node, NodeType
from test_framework.util import Utility
from test_framework.util import Utility, wait_until


# pylint: disable=too-many-public-methods
Expand Down Expand Up @@ -274,6 +274,9 @@ def check_connection(self, peer_one: Node, peer_two: Node, is_connected: bool):
f"Peer one running: {peer_one_running}, Peer two running: {peer_two_running}"
)

# Send pings to both peers to trigger a peer state update
self._send_peer_pings(peer_one, peer_two)

peer_two_in_peer_one = (
peer_one.is_peer_connected(peer_two) if peer_one_running else False
)
Expand All @@ -293,42 +296,40 @@ def wait_for_peers_connections(
Wait for two peers to connect/disconnect to each other.
"""
attempts = 0
timeout = time.time() + 30
while time.time() < timeout:
if self.check_connection(peer_one, peer_two, is_connected):
self.log.debug(
f"Peers {peer_one.variant} and {peer_two.variant} are in the expected "
f"connection state."
)
return

if attempts < 10:
def check_peers_connection():
nonlocal attempts

if attempts > 10:
time.sleep(1)
else:
time.sleep(2)

attempts += 1

# Send a ping to both peers to trigger a peer state update
if peer_one.daemon.is_running:
peer_one.rpc.ping()
self.log.debug(
f"Peer one {peer_one.variant} is connected to peer two {peer_two.variant}: "
f"{peer_one.is_peer_connected(peer_two)}"
)

if peer_two.daemon.is_running:
peer_two.rpc.ping()
self.log.debug(
f"Peer two {peer_two.variant} is connected to peer one {peer_one.variant}: "
f"{peer_two.is_peer_connected(peer_one)}"
)

raise AssertionError(
f"Peers {peer_one.variant} and {peer_two.variant} failed to reach the expected "
f"connection state within the timeout. Expected connected: {is_connected}."
return self.check_connection(peer_one, peer_two, is_connected)

wait_until(predicate=check_peers_connection)

self.log.debug(
f"Peers {peer_one.variant} and {peer_two.variant} are "
f"{'connected' if is_connected else 'disconnected'}"
)

def _send_peer_pings(self, peer_one: Node, peer_two: Node):
"""Send pings to both peers and log connection status."""
if peer_one.daemon.is_running:
peer_one.rpc.ping()
self.log.debug(
f"Peer one {peer_one.variant} is connected to peer two {peer_two.variant}: "
f"{peer_one.is_peer_connected(peer_two)}"
)

if peer_two.daemon.is_running:
peer_two.rpc.ping()
self.log.debug(
f"Peer two {peer_two.variant} is connected to peer one {peer_one.variant}: "
f"{peer_two.is_peer_connected(peer_one)}"
)

def connect_nodes(
self,
peer_one: Node,
Expand Down
Loading