add get_interchain: trace a transaction/block to validator blocks + interchain anchors

New transaction.get_interchain and block.get_interchain call the prime-node
/api/v1/{transaction,block}/{id}/interchain endpoints, returning an
InterchainTrace {block_id, validator_blocks, interchain_transactions}. Adds
VerificationBlock / InterchainTransaction / InterchainTrace dataclasses with
from_dict, exports them, and a from_dict test.
This commit is contained in:
2026-06-02 14:12:59 -04:00
parent 8b007dcbab
commit e4e49218d0
5 changed files with 148 additions and 1 deletions

View File

@@ -48,6 +48,8 @@ from .models import (
BlockHeader, BlockHeader,
BlockProof, BlockProof,
GrpcConnectionInfo, GrpcConnectionInfo,
InterchainTrace,
InterchainTransaction,
ListResponse, ListResponse,
ListTransactionsResponse, ListTransactionsResponse,
SmartContract, SmartContract,
@@ -67,6 +69,7 @@ from .models import (
TransactionType, TransactionType,
TransactionTypeCreateRequest, TransactionTypeCreateRequest,
TransactionTypeCreateResponse, TransactionTypeCreateResponse,
VerificationBlock,
) )
from .system import SystemClient from .system import SystemClient
from .transaction import TransactionClient from .transaction import TransactionClient
@@ -125,6 +128,8 @@ __all__ = [
"BlockHeader", "BlockHeader",
"BlockProof", "BlockProof",
"GrpcConnectionInfo", "GrpcConnectionInfo",
"InterchainTrace",
"InterchainTransaction",
"ListResponse", "ListResponse",
"ListTransactionsResponse", "ListTransactionsResponse",
"SmartContract", "SmartContract",
@@ -144,4 +149,5 @@ __all__ = [
"TransactionType", "TransactionType",
"TransactionTypeCreateRequest", "TransactionTypeCreateRequest",
"TransactionTypeCreateResponse", "TransactionTypeCreateResponse",
"VerificationBlock",
] ]

View File

@@ -1,7 +1,7 @@
"""Block endpoints.""" """Block endpoints."""
from .client import Client from .client import Client
from .models import Block from .models import Block, InterchainTrace
class BlockClient: class BlockClient:
@@ -10,3 +10,11 @@ class BlockClient:
def get(self, block_id: str) -> Block: def get(self, block_id: str) -> Block:
return self._client.get(f"/api/v1/block/{block_id}", Block) return self._client.get(f"/api/v1/block/{block_id}", Block)
def get_interchain(self, block_id: str) -> InterchainTrace:
"""Trace a block to the validator (verification) blocks that validated it
and the public-chain interchain anchors those validator blocks were
bundled into."""
return self._client.get(
f"/api/v1/block/{block_id}/interchain", InterchainTrace
)

View File

@@ -350,6 +350,92 @@ class Block:
) )
# --------------------------------------------------------------------------- #
# Interchain trace
# --------------------------------------------------------------------------- #
@dataclass
class VerificationBlock:
"""A validator's verification of a prime block."""
version: str = ""
prime_chain_id: str = ""
prime_block_id: str = ""
timestamp: str = ""
verifier_public_key: str = ""
verifier_signature: str = ""
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "VerificationBlock":
return cls(
version=d.get("version", ""),
prime_chain_id=d.get("primeChainId", ""),
prime_block_id=d.get("primeBlockId", ""),
timestamp=d.get("timestamp", ""),
verifier_public_key=d.get("verifierPublicKey", ""),
verifier_signature=d.get("verifierSignature", ""),
)
@dataclass
class InterchainTransaction:
"""An anchor broadcast to a public blockchain (e.g. ETH/BTC) bundling one or
more validator blocks. ``validator_blocks`` holds the covered prime block ids;
``covered_prime_chain_ids`` the prime chains they belong to."""
id: int = 0
version: str = ""
timestamp: str = ""
chain_id: str = ""
trans_hash: str = ""
block_id: str = ""
validator_blocks: List[str] = field(default_factory=list)
validator_blockhash: str = ""
signature: str = ""
covered_prime_chain_ids: List[str] = field(default_factory=list)
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "InterchainTransaction":
return cls(
id=d.get("id", 0),
version=d.get("version", ""),
timestamp=d.get("timestamp", ""),
chain_id=d.get("chainId", ""),
trans_hash=d.get("transHash", ""),
block_id=d.get("blockId", ""),
validator_blocks=d.get("validatorBlocks") or [],
validator_blockhash=d.get("validatorBlockhash", ""),
signature=d.get("signature", ""),
covered_prime_chain_ids=d.get("coveredPrimeChainIds") or [],
)
@dataclass
class InterchainTrace:
"""Links a prime block to the validator (verification) blocks that validated
it and the public-chain interchain anchors those validator blocks were
bundled into. Returned by ``transaction.get_interchain`` and
``block.get_interchain``."""
block_id: str = ""
validator_blocks: List[VerificationBlock] = field(default_factory=list)
interchain_transactions: List[InterchainTransaction] = field(default_factory=list)
@classmethod
def from_dict(cls, d: Dict[str, Any]) -> "InterchainTrace":
return cls(
block_id=d.get("blockId", ""),
validator_blocks=[
VerificationBlock.from_dict(v) for v in (d.get("validatorBlocks") or [])
],
interchain_transactions=[
InterchainTransaction.from_dict(v)
for v in (d.get("interchainTransactions") or [])
],
)
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
# System / generic # System / generic
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #

View File

@@ -2,6 +2,7 @@
from .client import CONTENT_TYPE_JSON, Client from .client import CONTENT_TYPE_JSON, Client
from .models import ( from .models import (
InterchainTrace,
ListTransactionsResponse, ListTransactionsResponse,
Transaction, Transaction,
TransactionBulkRequest, TransactionBulkRequest,
@@ -39,5 +40,14 @@ class TransactionClient:
def get(self, transaction_id: str) -> Transaction: def get(self, transaction_id: str) -> Transaction:
return self._client.get(f"/api/v1/transaction/{transaction_id}", Transaction) return self._client.get(f"/api/v1/transaction/{transaction_id}", Transaction)
def get_interchain(self, transaction_id: str) -> InterchainTrace:
"""Trace a transaction to the validator (verification) blocks that
validated its prime block and the public-chain interchain anchors those
validator blocks were bundled into. If the transaction is still pending
(not yet in a block) the trace's lists are empty."""
return self._client.get(
f"/api/v1/transaction/{transaction_id}/interchain", InterchainTrace
)
def list(self) -> ListTransactionsResponse: def list(self) -> ListTransactionsResponse:
return self._client.get("/api/v1/transaction/", ListTransactionsResponse) return self._client.get("/api/v1/transaction/", ListTransactionsResponse)

View File

@@ -16,6 +16,7 @@ from prime_sdk import (
Block, Block,
DragonchainAPIError, DragonchainAPIError,
DragonchainSDK, DragonchainSDK,
InterchainTrace,
SmartContractCreateRequest, SmartContractCreateRequest,
Transaction, Transaction,
TransactionCreateRequest, TransactionCreateRequest,
@@ -191,6 +192,42 @@ def test_block_from_dict_nested_header():
assert len(blk.transactions) == 3 assert len(blk.transactions) == 3
def test_interchain_trace_from_dict_nested():
# Server shape: validatorBlocks + interchainTransactions arrays, camelCase.
raw = {
"blockId": "69636602",
"validatorBlocks": [
{
"version": "1",
"primeChainId": "zDYr",
"primeBlockId": "69636602",
"verifierPublicKey": "02c4...",
"verifierSignature": "MEUC...",
}
],
"interchainTransactions": [
{
"id": 19,
"chainId": "1",
"transHash": "0xd46e",
"validatorBlocks": ["69636602"],
"coveredPrimeChainIds": ["zDYr"],
}
],
}
trace = InterchainTrace.from_dict(raw)
assert trace.block_id == "69636602"
assert len(trace.validator_blocks) == 1
assert trace.validator_blocks[0].prime_block_id == "69636602"
assert trace.validator_blocks[0].verifier_public_key == "02c4..."
assert len(trace.interchain_transactions) == 1
assert trace.interchain_transactions[0].id == 19
assert trace.interchain_transactions[0].trans_hash == "0xd46e"
assert trace.interchain_transactions[0].chain_id == "1"
assert trace.interchain_transactions[0].validator_blocks == ["69636602"]
assert trace.interchain_transactions[0].covered_prime_chain_ids == ["zDYr"]
def test_contract_create_request_omitempty(): def test_contract_create_request_omitempty():
req = SmartContractCreateRequest( req = SmartContractCreateRequest(
environment="python3.8", environment="python3.8",