From 8b007dcbab8e44036b3f3dc8927f37b7ed4412fb Mon Sep 17 00:00:00 2001 From: Andrew Miller Date: Fri, 29 May 2026 17:09:02 -0400 Subject: [PATCH] Fix Block model to match server: nest header with camelCase keys The block endpoint returns block id / prev / timestamp nested under a "header" object with camelCase keys (blockId, dcId, prevId, prevProof, timestamp) and a proof of just {proof}. The previous flat snake_case Block fields never matched the response and always deserialized empty. Add a BlockHeader dataclass, nest it in Block, make proof.scheme optional, and cover it with a unit test. Verified live against a dev chain. --- prime_sdk/__init__.py | 2 ++ prime_sdk/models.py | 33 +++++++++++++++++++++++---------- tests/test_client.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/prime_sdk/__init__.py b/prime_sdk/__init__.py index 23d5781..2bf8152 100644 --- a/prime_sdk/__init__.py +++ b/prime_sdk/__init__.py @@ -45,6 +45,7 @@ from .contract import ContractClient from .errors import DragonchainAPIError, DragonchainError from .models import ( Block, + BlockHeader, BlockProof, GrpcConnectionInfo, ListResponse, @@ -121,6 +122,7 @@ __all__ = [ "TransactionTypeClient", # models "Block", + "BlockHeader", "BlockProof", "GrpcConnectionInfo", "ListResponse", diff --git a/prime_sdk/models.py b/prime_sdk/models.py index db81754..44e6d44 100644 --- a/prime_sdk/models.py +++ b/prime_sdk/models.py @@ -301,26 +301,42 @@ class SmartContract: @dataclass class BlockProof: - scheme: str = "" proof: str = "" + scheme: str = "" # absent on trust-scheme chains; present on PoW nonce: int = 0 @classmethod def from_dict(cls, d: Dict[str, Any]) -> "BlockProof": return cls( - scheme=d.get("scheme", ""), proof=d.get("proof", ""), + scheme=d.get("scheme", ""), nonce=d.get("nonce", 0), ) +@dataclass +class BlockHeader: + block_id: str = "" + dc_id: str = "" + prev_id: str = "" + prev_proof: str = "" + timestamp: str = "" + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> "BlockHeader": + return cls( + block_id=d.get("blockId", ""), + dc_id=d.get("dcId", ""), + prev_id=d.get("prevId", ""), + prev_proof=d.get("prevProof", ""), + timestamp=d.get("timestamp", ""), + ) + + @dataclass class Block: version: str = "" - id: str = "" - timestamp: str = "" - prev_id: str = "" - prev_proof: str = "" + header: BlockHeader = field(default_factory=BlockHeader) transactions: List[str] = field(default_factory=list) proof: BlockProof = field(default_factory=BlockProof) @@ -328,10 +344,7 @@ class Block: def from_dict(cls, d: Dict[str, Any]) -> "Block": return cls( version=d.get("version", ""), - id=d.get("block_id", ""), - timestamp=d.get("timestamp", ""), - prev_id=d.get("prev_id", ""), - prev_proof=d.get("prev_proof", ""), + header=BlockHeader.from_dict(d.get("header") or {}), transactions=d.get("transactions") or [], proof=BlockProof.from_dict(d.get("proof") or {}), ) diff --git a/tests/test_client.py b/tests/test_client.py index afbd328..bcc2f51 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -13,6 +13,7 @@ import json import pytest from prime_sdk import ( + Block, DragonchainAPIError, DragonchainSDK, SmartContractCreateRequest, @@ -163,6 +164,33 @@ def test_transaction_from_dict_nested(): assert txn.payload == "{}" +def test_block_from_dict_nested_header(): + # Real server shape: id/prev/timestamp nested under "header" with camelCase + # keys; proof carries only "proof" on a trust-scheme chain. + raw = { + "version": "1", + "header": { + "blockId": "69569983", + "dcId": "chain-xyz", + "prevId": "69186326", + "prevProof": "MEUCIQ...", + "timestamp": "1780088135", + }, + "proof": {"proof": "MEQCIF..."}, + "transactions": ["{...}", "{...}", "{...}"], + } + blk = Block.from_dict(raw) + assert blk.version == "1" + assert blk.header.block_id == "69569983" + assert blk.header.dc_id == "chain-xyz" + assert blk.header.prev_id == "69186326" + assert blk.header.prev_proof == "MEUCIQ..." + assert blk.header.timestamp == "1780088135" + assert blk.proof.proof == "MEQCIF..." + assert blk.proof.scheme == "" # absent on trust-scheme chain + assert len(blk.transactions) == 3 + + def test_contract_create_request_omitempty(): req = SmartContractCreateRequest( environment="python3.8",