update schemas to v0.1.3rc5

This commit is contained in:
David Rusu 2026-05-06 18:31:16 -04:00
parent 3e61dc8d80
commit 2045b8df6f
6 changed files with 127 additions and 37 deletions

View File

@ -1,5 +1,5 @@
from enum import Enum
from typing import List, Literal, Optional
from typing import Any, List, Literal, Optional
from core.models import NbeSchema
from core.types import HexBytes
@ -59,7 +59,7 @@ class SDPDeclareServiceType(Enum):
class SDPDeclare(NbeContent):
type: Literal["SDPDeclare"] = "SDPDeclare"
service_type: SDPDeclareServiceType
locators: List[bytes]
locators: List[str]
provider_id: HexBytes
zk_id: HexBytes
locked_note_id: HexBytes
@ -74,8 +74,8 @@ class SDPWithdraw(NbeContent):
class SDPActive(NbeContent):
type: Literal["SDPActive"] = "SDPActive"
declaration_id: HexBytes
nonce: HexBytes
metadata: Optional[bytes]
nonce: int
metadata: Optional[Any] = None
class LeaderClaim(NbeContent):

View File

@ -25,7 +25,7 @@ class HttpNodeApi(NodeApi):
# Paths can't have a leading slash since they are relative to the base URL
ENDPOINT_INFO = "cryptarchia/info"
ENDPOINT_BLOCKS_STREAM = "cryptarchia/events/blocks/stream"
ENDPOINT_BLOCK_BY_HASH = "storage/block"
ENDPOINT_BLOCK_BY_HASH = "cryptarchia/blocks/" # block hash appended as path segment
def __init__(self, settings: "NBESettings"):
self.host: str = settings.node_api_host
@ -82,8 +82,8 @@ class HttpNodeApi(NodeApi):
return InfoSerializer.model_validate(response.json())
async def get_block_by_hash(self, block_hash: str) -> Optional[BlockSerializer]:
url = urljoin(self.base_url, self.ENDPOINT_BLOCK_BY_HASH)
response = await self._client.post(url, json=block_hash)
url = urljoin(self.base_url, self.ENDPOINT_BLOCK_BY_HASH + block_hash)
response = await self._client.get(url)
if response.status_code == 404:
return None
response.raise_for_status()
@ -91,12 +91,7 @@ class HttpNodeApi(NodeApi):
if json_data is None:
logger.warning(f"Block {block_hash} returned null from API")
return None
block = BlockSerializer.model_validate(json_data)
# The storage endpoint doesn't include the block hash in the response,
# so we set it from the request body
if not block.header.hash:
block.header.hash = bytes.fromhex(block_hash)
return block
return BlockSerializer.model_validate(json_data)
async def get_blocks_stream(self) -> AsyncIterator[BlockSerializer]:
url = urljoin(self.base_url, self.ENDPOINT_BLOCKS_STREAM)

View File

@ -1,17 +1,20 @@
from random import randint
from typing import Annotated, Any, List, Self, Union
from typing import Annotated, Any, List, Optional, Self, Union
from pydantic import BeforeValidator, Field
from core.models import NbeSerializer
from models.transactions.operations.contents import SDPDeclareServiceType
from node.api.serializers.fields import BytesFromHex, BytesFromIntArray
from node.api.serializers.note import NoteSerializer
from utils.protocols import FromRandom
from utils.random import random_bytes
# Mantle op opcodes (new node release).
# Mantle op opcodes.
OPCODE_LEDGER = 0
OPCODE_CHANNEL_INSCRIBE = 17
OPCODE_SDP_DECLARE = 32
OPCODE_SDP_ACTIVE = 34
class LedgerOpSerializer(NbeSerializer, FromRandom):
@ -52,14 +55,68 @@ class ChannelInscribeOpSerializer(NbeSerializer, FromRandom):
)
class SDPDeclareOpSerializer(NbeSerializer, FromRandom):
"""SDP declare op (opcode 32): registers a service provider."""
service_type: SDPDeclareServiceType
locators: List[str] = Field(description="Multiaddr strings, e.g. /ip4/.../udp/.../quic-v1.")
provider_id: BytesFromHex = Field(description="Provider ID in hex format.")
zk_id: BytesFromHex = Field(description="ZK ID in hex format.")
locked_note_id: BytesFromHex = Field(description="Locked note ID in hex format.")
@classmethod
def from_random(cls) -> Self:
return cls.model_validate(
{
"service_type": "BN",
"locators": ["/ip4/127.0.0.1/udp/3400/quic-v1"],
"provider_id": random_bytes(32).hex(),
"zk_id": random_bytes(32).hex(),
"locked_note_id": random_bytes(32).hex(),
}
)
class SDPActiveOpSerializer(NbeSerializer, FromRandom):
"""SDP active op (opcode 34): proves a declared provider is online."""
declaration_id: BytesFromHex = Field(description="Declaration ID in hex format.")
nonce: int = Field(description="Activity nonce in u64 format.")
metadata: Optional[Any] = Field(
default=None,
description="Service-specific metadata (e.g. Blend session/proofs). Stored verbatim.",
)
@classmethod
def from_random(cls) -> Self:
return cls.model_validate(
{
"declaration_id": random_bytes(32).hex(),
"nonce": randint(0, 1_000),
"metadata": None,
}
)
OPCODE_TO_SERIALIZER: dict[int, type] = {
OPCODE_LEDGER: LedgerOpSerializer,
OPCODE_CHANNEL_INSCRIBE: ChannelInscribeOpSerializer,
OPCODE_SDP_DECLARE: SDPDeclareOpSerializer,
OPCODE_SDP_ACTIVE: SDPActiveOpSerializer,
}
def _parse_mantle_op(data: Any) -> Union[LedgerOpSerializer, ChannelInscribeOpSerializer]:
if isinstance(data, (LedgerOpSerializer, ChannelInscribeOpSerializer)):
MantleOpSerializerVariants = Union[
LedgerOpSerializer,
ChannelInscribeOpSerializer,
SDPDeclareOpSerializer,
SDPActiveOpSerializer,
]
_MANTLE_OP_SERIALIZER_CLASSES = tuple(OPCODE_TO_SERIALIZER.values())
def _parse_mantle_op(data: Any) -> MantleOpSerializerVariants:
if isinstance(data, _MANTLE_OP_SERIALIZER_CLASSES):
return data
if isinstance(data, dict) and "opcode" in data:
opcode = data["opcode"]
@ -72,5 +129,4 @@ def _parse_mantle_op(data: Any) -> Union[LedgerOpSerializer, ChannelInscribeOpSe
raise ValueError(f"Cannot parse mantle op from {type(data).__name__}.")
MantleOpSerializerVariants = Union[LedgerOpSerializer, ChannelInscribeOpSerializer]
MantleOpSerializerField = Annotated[MantleOpSerializerVariants, BeforeValidator(_parse_mantle_op)]

View File

@ -10,7 +10,7 @@ from models.transactions.operations.proofs import (
ZkAndEd25519Signature,
ZkSignature,
)
from node.api.serializers.fields import BytesFromIntArray
from node.api.serializers.fields import BytesFromHex, BytesFromIntArray
from utils.protocols import EnforceSubclassFromRandom
from utils.random import random_bytes
@ -65,13 +65,13 @@ class ZkSignatureSerializer(OperationProofSerializer, NbeSerializer):
class ZkAndEd25519SignaturesSerializer(OperationProofSerializer, NbeSerializer):
zk_signature: BytesFromIntArray = Field(alias="zk_sig")
ed25519_signature: BytesFromIntArray = Field(alias="ed25519_sig")
zk_signature: ZkSignatureSerializer = Field(alias="zk_sig")
ed25519_signature: BytesFromHex = Field(alias="ed25519_sig")
def into_operation_proof(self) -> NbeSignature:
return ZkAndEd25519Signature.model_validate(
{
"zk_signature": self.zk_signature,
"zk_signature": self.zk_signature.to_bytes(),
"ed25519_signature": self.ed25519_signature,
}
)
@ -80,8 +80,12 @@ class ZkAndEd25519SignaturesSerializer(OperationProofSerializer, NbeSerializer):
def from_random(cls, *args, **kwargs) -> Self:
return ZkAndEd25519SignaturesSerializer.model_validate(
{
"zk_sig": list(random_bytes(32)),
"ed25519_sig": list(random_bytes(64)),
"zk_sig": {
"pi_a": list(random_bytes(32)),
"pi_b": list(random_bytes(64)),
"pi_c": list(random_bytes(32)),
},
"ed25519_sig": random_bytes(64).hex(),
}
)

View File

@ -9,7 +9,7 @@ from models.header.proof_of_leadership import (
Groth16ProofOfLeadership,
ProofOfLeadership,
)
from node.api.serializers.fields import BytesFromHex, BytesFromIntArray
from node.api.serializers.fields import BytesFromHex
from utils.protocols import EnforceSubclassFromRandom
from utils.random import random_bytes
@ -22,10 +22,8 @@ class ProofOfLeadershipSerializer(NbeSerializer, EnforceSubclassFromRandom, ABC)
class Groth16LeaderProofSerializer(ProofOfLeadershipSerializer, NbeSerializer):
entropy_contribution: BytesFromHex = Field(description="Fr integer.")
leader_key: BytesFromHex = Field(description="Bytes in Integer Array format.")
proof: BytesFromIntArray = Field(
description="Bytes in Integer Array format.",
)
leader_key: BytesFromHex = Field(description="Hash in hex format.")
proof: BytesFromHex = Field(description="Groth16 proof bytes (128B) in hex format.")
voucher_cm: BytesFromHex = Field(description="Hash.")
def into_proof_of_leadership(self) -> ProofOfLeadership:
@ -44,7 +42,7 @@ class Groth16LeaderProofSerializer(ProofOfLeadershipSerializer, NbeSerializer):
{
"entropy_contribution": random_bytes(32).hex(),
"leader_key": random_bytes(32).hex(),
"proof": list(random_bytes(128)),
"proof": random_bytes(128).hex(),
"voucher_cm": random_bytes(32).hex(),
}
)

View File

@ -9,16 +9,33 @@ from models.transactions.transaction import Transaction
from node.api.serializers.operation import (
ChannelInscribeOpSerializer,
LedgerOpSerializer,
SDPActiveOpSerializer,
SDPDeclareOpSerializer,
)
from node.api.serializers.proof import (
Ed25519SignatureSerializer,
OperationProofSerializerField,
ZkAndEd25519SignaturesSerializer,
ZkSignatureSerializer,
)
from node.api.serializers.transaction import TransactionSerializer
from utils.protocols import FromRandom
def _proof_to_internal(proof) -> dict:
if isinstance(proof, ZkSignatureSerializer):
return {"type": "Zk", "signature": proof.to_bytes()}
if isinstance(proof, Ed25519SignatureSerializer):
return {"type": "Ed25519", "signature": proof.root}
if isinstance(proof, ZkAndEd25519SignaturesSerializer):
return {
"type": "ZkAndEd25519",
"zk_signature": proof.zk_signature.to_bytes(),
"ed25519_signature": proof.ed25519_signature,
}
raise ValueError(f"Unsupported proof type: {type(proof).__name__}")
class SignedTransactionSerializer(NbeSerializer, FromRandom):
transaction: TransactionSerializer = Field(alias="mantle_tx", description="Transaction.")
operations_proofs: List[OperationProofSerializerField] = Field(
@ -53,10 +70,7 @@ class SignedTransactionSerializer(NbeSerializer, FromRandom):
"inputs": list(op.inputs),
"outputs": [o.into_note() for o in op.outputs],
},
"proof": {
"type": "Zk",
"signature": proof.to_bytes(),
},
"proof": _proof_to_internal(proof),
}
)
elif isinstance(op, ChannelInscribeOpSerializer):
@ -73,10 +87,33 @@ class SignedTransactionSerializer(NbeSerializer, FromRandom):
"parent": op.parent,
"signer": op.signer,
},
"proof": {
"type": "Ed25519",
"signature": proof.root,
"proof": _proof_to_internal(proof),
}
)
elif isinstance(op, SDPDeclareOpSerializer):
operations.append(
{
"content": {
"type": "SDPDeclare",
"service_type": op.service_type,
"locators": list(op.locators),
"provider_id": op.provider_id,
"zk_id": op.zk_id,
"locked_note_id": op.locked_note_id,
},
"proof": _proof_to_internal(proof),
}
)
elif isinstance(op, SDPActiveOpSerializer):
operations.append(
{
"content": {
"type": "SDPActive",
"declaration_id": op.declaration_id,
"nonce": op.nonce,
"metadata": op.metadata,
},
"proof": _proof_to_internal(proof),
}
)
else: