133 lines
4.7 KiB
Python
Raw Normal View History

2026-04-09 23:27:30 -04:00
from random import randint
2026-05-06 18:31:16 -04:00
from typing import Annotated, Any, List, Optional, Self, Union
2025-10-30 11:48:34 +01:00
2026-02-13 01:03:47 +04:00
from pydantic import BeforeValidator, Field
2025-10-30 11:48:34 +01:00
from core.models import NbeSerializer
2026-05-06 18:31:16 -04:00
from models.transactions.operations.contents import SDPDeclareServiceType
2026-04-09 23:27:30 -04:00
from node.api.serializers.fields import BytesFromHex, BytesFromIntArray
from node.api.serializers.note import NoteSerializer
from utils.protocols import FromRandom
2025-10-30 11:48:34 +01:00
from utils.random import random_bytes
2026-05-06 18:31:16 -04:00
# Mantle op opcodes.
2026-04-09 23:27:30 -04:00
OPCODE_LEDGER = 0
OPCODE_CHANNEL_INSCRIBE = 17
2026-05-06 18:31:16 -04:00
OPCODE_SDP_DECLARE = 32
OPCODE_SDP_ACTIVE = 34
2025-10-30 11:48:34 +01:00
2026-04-09 23:27:30 -04:00
class LedgerOpSerializer(NbeSerializer, FromRandom):
"""Mantle ledger op (opcode 0): consumes input notes and produces outputs."""
2025-10-30 11:48:34 +01:00
2026-04-09 23:27:30 -04:00
inputs: List[BytesFromHex] = Field(description="Input note IDs (Fr).")
outputs: List[NoteSerializer]
2025-10-30 11:48:34 +01:00
@classmethod
def from_random(cls) -> Self:
2026-04-09 23:27:30 -04:00
n_inputs = 0 if randint(0, 1) <= 0.5 else randint(1, 5)
n_outputs = 0 if randint(0, 1) <= 0.5 else randint(1, 5)
2025-10-30 11:48:34 +01:00
return cls.model_validate(
{
2026-04-09 23:27:30 -04:00
"inputs": [random_bytes(32).hex() for _ in range(n_inputs)],
"outputs": [NoteSerializer.from_random() for _ in range(n_outputs)],
2025-10-30 11:48:34 +01:00
}
)
2026-04-09 23:27:30 -04:00
class ChannelInscribeOpSerializer(NbeSerializer, FromRandom):
"""Channel inscribe op (opcode 17): writes an inscription to a channel."""
2025-10-30 11:48:34 +01:00
2026-04-09 23:27:30 -04:00
channel_id: BytesFromHex = Field(description="Channel ID in hex format.")
inscription: BytesFromIntArray = Field(description="Inscription bytes (int array).")
parent: BytesFromHex = Field(description="Parent inscription hash in hex format.")
signer: BytesFromHex = Field(description="Signer public key in hex format.")
2025-10-30 11:48:34 +01:00
@classmethod
def from_random(cls) -> Self:
return cls.model_validate(
{
2026-04-09 23:27:30 -04:00
"channel_id": random_bytes(32).hex(),
"inscription": list(random_bytes(32)),
2026-02-13 01:03:47 +04:00
"parent": random_bytes(32).hex(),
2025-10-30 11:48:34 +01:00
"signer": random_bytes(32).hex(),
}
)
2026-05-06 18:31:16 -04:00
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,
}
)
2026-04-09 23:27:30 -04:00
OPCODE_TO_SERIALIZER: dict[int, type] = {
OPCODE_LEDGER: LedgerOpSerializer,
OPCODE_CHANNEL_INSCRIBE: ChannelInscribeOpSerializer,
2026-05-06 18:31:16 -04:00
OPCODE_SDP_DECLARE: SDPDeclareOpSerializer,
OPCODE_SDP_ACTIVE: SDPActiveOpSerializer,
2026-02-13 01:03:47 +04:00
}
2026-05-06 18:31:16 -04:00
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):
2026-02-13 01:03:47 +04:00
return data
if isinstance(data, dict) and "opcode" in data:
opcode = data["opcode"]
serializer_class = OPCODE_TO_SERIALIZER.get(opcode)
if serializer_class is None:
2026-04-09 23:27:30 -04:00
raise ValueError(
f"Unsupported mantle op opcode {opcode}; known opcodes: {sorted(OPCODE_TO_SERIALIZER)}."
)
return serializer_class.model_validate(data["payload"])
raise ValueError(f"Cannot parse mantle op from {type(data).__name__}.")
2026-02-13 01:03:47 +04:00
2026-04-09 23:27:30 -04:00
MantleOpSerializerField = Annotated[MantleOpSerializerVariants, BeforeValidator(_parse_mantle_op)]