77 lines
2.8 KiB
Python
Raw Normal View History

2026-04-09 23:27:30 -04:00
from random import randint
from typing import Annotated, Any, List, 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-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-04-09 23:27:30 -04:00
# Mantle op opcodes (new node release).
OPCODE_LEDGER = 0
OPCODE_CHANNEL_INSCRIBE = 17
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-04-09 23:27:30 -04:00
OPCODE_TO_SERIALIZER: dict[int, type] = {
OPCODE_LEDGER: LedgerOpSerializer,
OPCODE_CHANNEL_INSCRIBE: ChannelInscribeOpSerializer,
2026-02-13 01:03:47 +04:00
}
2026-04-09 23:27:30 -04:00
def _parse_mantle_op(data: Any) -> Union[LedgerOpSerializer, ChannelInscribeOpSerializer]:
if isinstance(data, (LedgerOpSerializer, ChannelInscribeOpSerializer)):
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
MantleOpSerializerVariants = Union[LedgerOpSerializer, ChannelInscribeOpSerializer]
MantleOpSerializerField = Annotated[MantleOpSerializerVariants, BeforeValidator(_parse_mantle_op)]