global: snap

This commit is contained in:
nym21
2026-04-12 18:00:02 +02:00
parent 18d9c166d8
commit c3cef71aa3
36 changed files with 2366 additions and 371 deletions

View File

@@ -0,0 +1,265 @@
"""
Block endpoint compatibility tests — parametrized across blockchain eras.
Endpoints covered:
GET /api/block/{hash}
GET /api/v1/block/{hash} (with extras)
GET /api/block/{hash}/header text/plain
GET /api/block/{hash}/status
GET /api/block/{hash}/txids
GET /api/block/{hash}/txs
GET /api/block/{hash}/txs/{start}
GET /api/block/{hash}/txid/{index} text/plain
GET /api/block-height/{height} text/plain
GET /api/blocks
GET /api/blocks/{height}
GET /api/v1/blocks
GET /api/v1/blocks/{height}
GET /api/blocks/tip/height text/plain
GET /api/blocks/tip/hash text/plain
"""
import pytest
from conftest import show, assert_same_structure, assert_same_values
def _block_ids(live):
return [f"h{b.height}" for b in live.blocks]
def _bi(request, live):
"""Resolve parametrized block index, skip if out of range."""
i = request.param
if i >= len(live.blocks):
pytest.skip("block not discovered")
return live.blocks[i]
@pytest.fixture(params=range(8), ids=[
"h100", "h100k", "h400k", "h630k", "h800k", "recent1k", "recent100", "recent10",
])
def block(request, live):
i = request.param
if i >= len(live.blocks):
pytest.skip("block not discovered")
return live.blocks[i]
# ── /api/block/{hash} ────────────────────────────────────────────────
def test_block_by_hash(brk, mempool, block):
"""Confirmed block info must be identical."""
path = f"/api/block/{block.hash}"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, b, m)
assert_same_values(b, m)
# ── /api/v1/block/{hash} ─────────────────────────────────────────────
def test_block_v1_extras_all_values(brk, mempool, block):
"""Every shared extras field must match — exposes computation differences."""
path = f"/api/v1/block/{block.hash}"
b = brk.get_json(path)["extras"]
m = mempool.get_json(path)["extras"]
show("GET", f"{path} [extras]", b, m, max_lines=50)
assert_same_structure(b, m)
assert_same_values(b, m)
def test_block_v1_extras_pool(brk, mempool, block):
"""Pool identification structure must match."""
path = f"/api/v1/block/{block.hash}"
bp = brk.get_json(path)["extras"]["pool"]
mp = mempool.get_json(path)["extras"]["pool"]
show("GET", f"{path} [extras.pool]", bp, mp)
assert_same_structure(bp, mp)
# ── /api/block/{hash}/header ─────────────────────────────────────────
def test_block_header(brk, mempool, block):
"""80-byte hex block header must be identical."""
path = f"/api/block/{block.hash}/header"
b = brk.get_text(path)
m = mempool.get_text(path)
show("GET", path, b, m)
assert len(b) == 160, f"Expected 160 hex chars (80 bytes), got {len(b)}"
assert b == m
# ── /api/block/{hash}/status ─────────────────────────────────────────
def test_block_status(brk, mempool, block):
"""Block status must be identical for a confirmed block."""
path = f"/api/block/{block.hash}/status"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, b, m)
assert_same_values(b, m)
# ── /api/block/{hash}/txids ──────────────────────────────────────────
def test_block_txids(brk, mempool, block):
"""Ordered txid list must be identical."""
path = f"/api/block/{block.hash}/txids"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, b[:3], m[:3])
assert b == m
# ── /api/block/{hash}/txs ────────────────────────────────────────────
def test_block_txs_page0(brk, mempool, block):
"""First page of block transactions must match."""
path = f"/api/block/{block.hash}/txs"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, f"({len(b)} txs)", f"({len(m)} txs)")
assert len(b) == len(m), f"Page size: brk={len(b)} vs mempool={len(m)}"
if b and m:
assert_same_values(b[0], m[0], exclude={"sigops"})
def test_block_txs_start_index(brk, mempool, block):
"""Paginated txs from index 25 must match (skip small blocks)."""
# Blocks with <26 txs don't have a second page
txids = mempool.get_json(f"/api/block/{block.hash}/txids")
if len(txids) <= 25:
pytest.skip(f"block has only {len(txids)} txs")
path = f"/api/block/{block.hash}/txs/25"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, f"({len(b)} txs)", f"({len(m)} txs)")
assert len(b) == len(m)
if b and m:
assert_same_structure(b[0], m[0])
# ── /api/block/{hash}/txid/{index} ───────────────────────────────────
def test_block_txid_at_index_0(brk, mempool, block):
"""Txid at position 0 (coinbase) must match."""
path = f"/api/block/{block.hash}/txid/0"
b = brk.get_text(path)
m = mempool.get_text(path)
show("GET", path, b, m)
assert b == m
def test_block_txid_at_index_1(brk, mempool, block):
"""Txid at position 1 (first non-coinbase) must match."""
txids = mempool.get_json(f"/api/block/{block.hash}/txids")
if len(txids) <= 1:
pytest.skip("block has only coinbase")
path = f"/api/block/{block.hash}/txid/1"
b = brk.get_text(path)
m = mempool.get_text(path)
show("GET", path, b, m)
assert b == m
def test_block_txid_at_last_index(brk, mempool, block):
"""Txid at last position must match."""
txids = mempool.get_json(f"/api/block/{block.hash}/txids")
last = len(txids) - 1
path = f"/api/block/{block.hash}/txid/{last}"
b = brk.get_text(path)
m = mempool.get_text(path)
show("GET", path, b, m)
assert b == m
# ── /api/block-height/{height} ───────────────────────────────────────
def test_block_height_to_hash(brk, mempool, block):
"""Block hash at a given height must match."""
path = f"/api/block-height/{block.height}"
b = brk.get_text(path)
m = mempool.get_text(path)
show("GET", path, b, m)
assert b == m
assert b == block.hash
# ── /api/blocks/{height} ─────────────────────────────────────────────
def test_blocks_from_height(brk, mempool, block):
"""Confirmed blocks from a fixed height must match exactly."""
path = f"/api/blocks/{block.height}"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, f"({len(b)} blocks)", f"({len(m)} blocks)")
assert len(b) == len(m)
if b and m:
assert_same_values(b[0], m[0])
def test_blocks_v1_from_height(brk, mempool, block):
"""v1 blocks from a confirmed height — all values must match."""
path = f"/api/v1/blocks/{block.height}"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, f"({len(b)} blocks)", f"({len(m)} blocks)")
assert len(b) == len(m)
if b and m:
assert_same_values(b[0], m[0])
# ── non-parametrized (no block param) ────────────────────────────────
def test_blocks_recent(brk, mempool):
"""Recent blocks list must have the same structure."""
path = "/api/blocks"
b = brk.get_json(path)
m = mempool.get_json(path)
show(
"GET", path,
f"({len(b)} blocks, {b[-1]['height']}{b[0]['height']})" if b else "[]",
f"({len(m)} blocks, {m[-1]['height']}{m[0]['height']})" if m else "[]",
)
assert len(b) > 0
assert_same_structure(b, m)
def test_blocks_v1_recent(brk, mempool):
"""Recent v1 blocks (with extras) must have the same structure."""
path = "/api/v1/blocks"
b = brk.get_json(path)
m = mempool.get_json(path)
show("GET", path, f"({len(b)} blocks)", f"({len(m)} blocks)")
assert len(b) > 0
assert_same_structure(b, m)
def test_blocks_tip_height(brk, mempool):
"""Tip heights must be within a few blocks of each other."""
path = "/api/blocks/tip/height"
b = int(brk.get_text(path))
m = int(mempool.get_text(path))
show("GET", path, b, m)
assert abs(b - m) <= 3, f"Tip heights differ by {abs(b - m)}: brk={b}, mempool={m}"
def test_blocks_tip_hash(brk, mempool):
"""Tip hash must be a valid 64-char hex string."""
path = "/api/blocks/tip/hash"
b = brk.get_text(path)
m = mempool.get_text(path)
show("GET", path, b, m)
assert len(b) == 64
assert len(m) == 64