Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion crates/floresta-electrum/src/electrum_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ impl<Blockchain: BlockchainInterface> ElectrumServer<Blockchain> {

json_rpc_res!(request, {
"count": count,
"hex": String::from_iter(headers),
"headers": headers.collect::<Vec<_>>(),
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.

I think this is a 1.6 change, we need to keep versioning in mind for these, see #1109

"max": MAX_COUNT,
})
}
Expand Down
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
72 changes: 72 additions & 0 deletions tests/electrum/blockchain_block_headers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# SPDX-License-Identifier: MIT OR Apache-2.0

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

This tests the blockchain.block.headers endpoint which returns a chunk
of block headers from the main chain.
"""

import random
import pytest
from test_framework.util import wait_until

MINE_BLOCKS = 100
MAX_HEADERS = 2016


@pytest.mark.electrum
def test_block_headers(setup_logging, florestad_utreexod):
"""Test block headers retrieval and validation."""
log = setup_logging
florestad, utreexod = florestad_utreexod

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

log.info("Testing out-of-range request...")
response = florestad.electrum.block_headers(MINE_BLOCKS + 1, 10)
assert response["count"] == 0
assert response["headers"] == []
assert response["max"] == MAX_HEADERS

log.info("Testing invalid parameters...")
with pytest.raises(ValueError):
florestad.electrum.block_headers(-1, 10)

with pytest.raises(ValueError):
florestad.electrum.block_headers(0, -1)

log.info("Testing valid requests...")
compare_headers_range(florestad, 0, 10)
compare_headers_range(florestad, MINE_BLOCKS - 10, 10)

log.info("Testing random range...")
random_start = random.randint(1, MINE_BLOCKS - 10)
compare_headers_range(florestad, random_start, 10)

log.info("Testing max count limit...")
response = florestad.electrum.block_headers(0, MAX_HEADERS * 2)
assert response["count"] <= response["max"]
assert response["max"] == MAX_HEADERS
assert response["count"] <= MAX_HEADERS


def compare_headers_range(florestad, start_height, count):
"""Compare block headers from Electrum and RPC for a range."""
response = florestad.electrum.block_headers(start_height, count)

# Validate response structure
assert "count" in response
assert "headers" in response
assert "max" in response
assert response["count"] == len(response["headers"])
assert response["max"] == MAX_HEADERS

# Compare each header with RPC
for i, header in enumerate(response["headers"]):
height = start_height + i
block_hash = florestad.rpc.get_blockhash(height)
rpc_header = florestad.rpc.get_blockheader(block_hash, verbosity=False)

assert 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
Loading
Loading