Sync block structure with latest state

This commit is contained in:
Antonio Antonino 2025-12-19 16:48:21 +01:00
parent 6fa406f83f
commit cce312ecb6
No known key found for this signature in database
GPG Key ID: 70CC1DF6BCF7E76D
7 changed files with 182 additions and 77 deletions

View File

@ -1,4 +1,4 @@
from typing import Annotated from typing import Annotated, Union
from pydantic import BeforeValidator, PlainSerializer, ValidationError from pydantic import BeforeValidator, PlainSerializer, ValidationError
@ -28,6 +28,20 @@ def bytes_from_int(data: int) -> bytes:
return data.to_bytes((data.bit_length() + 7) // 8) # TODO: Ensure endianness is correct. return data.to_bytes((data.bit_length() + 7) // 8) # TODO: Ensure endianness is correct.
def bytes_from_hex_or_intarray(data: Union[str, list[int]]) -> bytes:
"""Accepts either hex string or int array and converts to bytes."""
if isinstance(data, str):
return bytes.fromhex(data)
elif isinstance(data, list):
if not all(isinstance(item, int) for item in data):
raise ValueError("List items must be integers.")
return bytes(data)
else:
raise ValueError(
f"Unsupported data type for bytes deserialization. Expected string or list, got {type(data).__name__}."
)
def bytes_into_hex(data: bytes) -> str: def bytes_into_hex(data: bytes) -> str:
return data.hex() return data.hex()
@ -35,3 +49,4 @@ def bytes_into_hex(data: bytes) -> str:
BytesFromIntArray = Annotated[bytes, BeforeValidator(bytes_from_intarray), PlainSerializer(bytes_into_hex)] BytesFromIntArray = Annotated[bytes, BeforeValidator(bytes_from_intarray), PlainSerializer(bytes_into_hex)]
BytesFromHex = Annotated[bytes, BeforeValidator(bytes_from_hex), PlainSerializer(bytes_into_hex)] BytesFromHex = Annotated[bytes, BeforeValidator(bytes_from_hex), PlainSerializer(bytes_into_hex)]
BytesFromInt = Annotated[bytes, BeforeValidator(bytes_from_int), PlainSerializer(bytes_into_hex)] BytesFromInt = Annotated[bytes, BeforeValidator(bytes_from_int), PlainSerializer(bytes_into_hex)]
BytesFlexible = Annotated[bytes, BeforeValidator(bytes_from_hex_or_intarray), PlainSerializer(bytes_into_hex)]

View File

@ -1,8 +1,9 @@
from enum import IntEnum
from random import randint from random import randint
from typing import Self from typing import Optional, Self
from pydantic import Field from pydantic import Field, computed_field
from rusty_results import Option, Some from rusty_results import Option
from core.models import NbeSerializer from core.models import NbeSerializer
from node.api.serializers.fields import BytesFromHex from node.api.serializers.fields import BytesFromHex
@ -14,18 +15,30 @@ from utils.protocols import FromRandom
from utils.random import random_hash from utils.random import random_hash
class Version(IntEnum):
Bedrock = 1
class HeaderSerializer(NbeSerializer, FromRandom): class HeaderSerializer(NbeSerializer, FromRandom):
hash: BytesFromHex = Field(alias="id", description="Hash id in hex format.") id: Optional[BytesFromHex] = Field(default=None, description="Header ID hash in hex format.")
version: Version = Field(default=Version.Bedrock, description="Block version.")
parent_block: BytesFromHex = Field(description="Hash in hex format.") parent_block: BytesFromHex = Field(description="Hash in hex format.")
slot: int = Field(description="Integer in u64 format.") slot: int = Field(description="Integer in u64 format.")
block_root: BytesFromHex = Field(description="Hash in hex format.") block_root: BytesFromHex = Field(description="Hash in hex format.")
proof_of_leadership: ProofOfLeadershipSerializerField proof_of_leadership: ProofOfLeadershipSerializerField
@computed_field
@property
def hash(self) -> bytes:
"""Return the header hash (id if available, otherwise block_root)."""
return self.id if self.id is not None else self.block_root
@classmethod @classmethod
def from_random(cls, *, slot: Option[int]) -> Self: def from_random(cls, *, slot: Option[int]) -> Self:
return cls.model_validate( return cls.model_validate(
{ {
"id": random_hash().hex(), "id": random_hash().hex(),
"version": Version.Bedrock,
"parent_block": random_hash().hex(), "parent_block": random_hash().hex(),
"slot": slot.unwrap_or_else(lambda: randint(0, 10_000)), "slot": slot.unwrap_or_else(lambda: randint(0, 10_000)),
"block_root": random_hash().hex(), "block_root": random_hash().hex(),

View File

@ -1,9 +1,9 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import Enum from enum import IntEnum
from random import choice, randint from random import choice, randint
from typing import Annotated, List, Optional, Self, Union from typing import Annotated, Any, Dict, List, Optional, Self, Union
from pydantic import Field from pydantic import Field, model_validator
from core.models import NbeSerializer from core.models import NbeSerializer
from models.transactions.operations.contents import ( from models.transactions.operations.contents import (
@ -16,7 +16,7 @@ from models.transactions.operations.contents import (
SDPDeclare, SDPDeclare,
SDPWithdraw, SDPWithdraw,
) )
from node.api.serializers.fields import BytesFromHex, BytesFromInt, BytesFromIntArray from node.api.serializers.fields import BytesFlexible, BytesFromHex, BytesFromInt, BytesFromIntArray
from utils.protocols import EnforceSubclassFromRandom from utils.protocols import EnforceSubclassFromRandom
from utils.random import random_bytes from utils.random import random_bytes
@ -28,9 +28,9 @@ class OperationContentSerializer(NbeSerializer, EnforceSubclassFromRandom, ABC):
class ChannelInscribeSerializer(OperationContentSerializer): class ChannelInscribeSerializer(OperationContentSerializer):
channel_id: BytesFromIntArray = Field(description="Bytes as a 32-integer array.") channel_id: BytesFlexible = Field(description="Bytes as hex or 32-integer array.")
inscription: BytesFromIntArray = Field(description="Bytes as an integer array.") inscription: BytesFlexible = Field(description="Bytes as hex or integer array.")
parent: BytesFromIntArray = Field(description="Bytes as a 32-integer array.") parent: BytesFlexible = Field(description="Bytes as hex or 32-integer array.")
signer: BytesFromHex = Field(description="Public Key in hex format.") signer: BytesFromHex = Field(description="Public Key in hex format.")
def into_operation_content(self) -> ChannelInscribe: def into_operation_content(self) -> ChannelInscribe:
@ -56,11 +56,11 @@ class ChannelInscribeSerializer(OperationContentSerializer):
class ChannelBlobSerializer(OperationContentSerializer): class ChannelBlobSerializer(OperationContentSerializer):
channel: BytesFromIntArray = Field(description="Bytes as a 32-integer array.") channel: BytesFlexible = Field(description="Bytes as hex or 32-integer array.")
blob: BytesFromIntArray = Field(description="Bytes as a 32-integer array.") blob: BytesFlexible = Field(description="Bytes as hex or 32-integer array.")
blob_size: int blob_size: int
da_storage_gas_price: int da_storage_gas_price: int
parent: BytesFromIntArray = Field(description="Bytes as a 32-integer array.") parent: BytesFlexible = Field(description="Bytes as hex or 32-integer array.")
signer: BytesFromHex = Field(description="Public Key in hex format.") signer: BytesFromHex = Field(description="Public Key in hex format.")
def into_operation_content(self) -> ChannelBlob: def into_operation_content(self) -> ChannelBlob:
@ -90,7 +90,7 @@ class ChannelBlobSerializer(OperationContentSerializer):
class ChannelSetKeysSerializer(OperationContentSerializer): class ChannelSetKeysSerializer(OperationContentSerializer):
channel: BytesFromIntArray = Field(description="Bytes as a 32-integer array.") channel: BytesFlexible = Field(description="Bytes as hex or 32-integer array.")
keys: List[BytesFromHex] = Field(description="List of Public Keys in hex format.") keys: List[BytesFromHex] = Field(description="List of Public Keys in hex format.")
def into_operation_content(self) -> ChannelSetKeys: def into_operation_content(self) -> ChannelSetKeys:
@ -112,9 +112,9 @@ class ChannelSetKeysSerializer(OperationContentSerializer):
) )
class SDPDeclareServiceType(Enum): class SDPDeclareServiceType(IntEnum):
BN = "BN" BN = 0
DA = "DA" DA = 1
class SDPDeclareSerializer(OperationContentSerializer): class SDPDeclareSerializer(OperationContentSerializer):
@ -127,7 +127,7 @@ class SDPDeclareSerializer(OperationContentSerializer):
def into_operation_content(self) -> SDPDeclare: def into_operation_content(self) -> SDPDeclare:
return SDPDeclare.model_validate( return SDPDeclare.model_validate(
{ {
"service_type": self.service_type.value, "service_type": self.service_type.name,
"locators": self.locators, "locators": self.locators,
"provider_id": self.provider_id, "provider_id": self.provider_id,
"zk_id": self.zk_id, "zk_id": self.zk_id,
@ -140,7 +140,7 @@ class SDPDeclareSerializer(OperationContentSerializer):
n = 1 if randint(0, 1) <= 0.5 else randint(1, 5) n = 1 if randint(0, 1) <= 0.5 else randint(1, 5)
return cls.model_validate( return cls.model_validate(
{ {
"service_type": choice(list(SDPDeclareServiceType)).value, "service_type": choice(list(SDPDeclareServiceType)),
"locators": [random_bytes(32).hex() for _ in range(n)], "locators": [random_bytes(32).hex() for _ in range(n)],
"provider_id": list(random_bytes(32)), "provider_id": list(random_bytes(32)),
"zk_id": random_bytes(32).hex(), "zk_id": random_bytes(32).hex(),
@ -174,7 +174,7 @@ class SDPWithdrawSerializer(OperationContentSerializer):
class SDPActiveSerializer(OperationContentSerializer): class SDPActiveSerializer(OperationContentSerializer):
declaration_id: BytesFromIntArray = Field(description="Bytes as a 32-integer array.") declaration_id: BytesFromIntArray = Field(description="Bytes as a 32-integer array.")
nonce: BytesFromInt nonce: BytesFromInt
metadata: Optional[BytesFromIntArray] = Field(description="Bytes as an integer array.") metadata: Optional[BytesFromIntArray] = Field(default=None, description="Bytes as an integer array.")
def into_operation_content(self) -> SDPActive: def into_operation_content(self) -> SDPActive:
return SDPActive.model_validate( return SDPActive.model_validate(
@ -221,6 +221,59 @@ class LeaderClaimSerializer(OperationContentSerializer):
) )
class OpCode(IntEnum):
ChannelInscribe = 0
ChannelBlob = 1
ChannelSetKeys = 2
SDPDeclare = 3
SDPWithdraw = 4
SDPActive = 5
LeaderClaim = 6
# Map opcode to serializer class
OPCODE_TO_SERIALIZER: Dict[int, type[OperationContentSerializer]] = {
OpCode.ChannelInscribe: ChannelInscribeSerializer,
OpCode.ChannelBlob: ChannelBlobSerializer,
OpCode.ChannelSetKeys: ChannelSetKeysSerializer,
OpCode.SDPDeclare: SDPDeclareSerializer,
OpCode.SDPWithdraw: SDPWithdrawSerializer,
OpCode.SDPActive: SDPActiveSerializer,
OpCode.LeaderClaim: LeaderClaimSerializer,
}
class OperationWrapper(NbeSerializer):
"""Wrapper for operations with opcode and payload structure."""
opcode: int
payload: Dict[str, Any]
_content: Optional[OperationContentSerializer] = None
@model_validator(mode="after")
def parse_payload(self) -> Self:
serializer_cls = OPCODE_TO_SERIALIZER.get(self.opcode)
if serializer_cls is None:
raise ValueError(f"Unknown opcode: {self.opcode}")
self._content = serializer_cls.model_validate(self.payload)
return self
def into_operation_content(self) -> NbeContent:
if self._content is None:
raise ValueError("Content not parsed")
return self._content.into_operation_content()
@classmethod
def from_random(cls) -> Self:
opcode = choice(list(OpCode))
serializer_cls = OPCODE_TO_SERIALIZER[opcode]
content = serializer_cls.from_random()
return cls.model_validate({
"opcode": opcode,
"payload": content.model_dump(),
})
type OperationContentSerializerVariants = Union[ type OperationContentSerializerVariants = Union[
ChannelInscribeSerializer, ChannelInscribeSerializer,
ChannelBlobSerializer, ChannelBlobSerializer,

View File

@ -1,7 +1,7 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Annotated, Self, Union from typing import Annotated, Any, Dict, Self, Union
from pydantic import Field, RootModel from pydantic import Field, RootModel, model_validator
from core.models import NbeSerializer from core.models import NbeSerializer
from models.transactions.operations.proofs import ( from models.transactions.operations.proofs import (
@ -10,7 +10,7 @@ from models.transactions.operations.proofs import (
ZkAndEd25519Signature, ZkAndEd25519Signature,
ZkSignature, ZkSignature,
) )
from node.api.serializers.fields import BytesFromHex from node.api.serializers.fields import BytesFromHex, BytesFromIntArray
from utils.protocols import EnforceSubclassFromRandom from utils.protocols import EnforceSubclassFromRandom
from utils.random import random_bytes from utils.random import random_bytes
@ -21,57 +21,77 @@ class OperationProofSerializer(EnforceSubclassFromRandom, ABC):
raise NotImplementedError raise NotImplementedError
# TODO: Differentiate between Ed25519SignatureSerializer and ZkSignatureSerializer class Ed25519SignatureSerializer(OperationProofSerializer, NbeSerializer):
"""Ed25519 signature as int array, wrapped in Ed25519Sig key."""
signature: BytesFromIntArray = Field(alias="Ed25519Sig")
class Ed25519SignatureSerializer(OperationProofSerializer, RootModel[str]):
root: BytesFromHex
def into_operation_proof(self) -> NbeSignature: def into_operation_proof(self) -> NbeSignature:
return Ed25519Signature.model_validate( return Ed25519Signature.model_validate(
{ {
"signature": self.root, "signature": self.signature,
} }
) )
@classmethod @classmethod
def from_random(cls, *args, **kwargs) -> Self: def from_random(cls, *args, **kwargs) -> Self:
return cls.model_validate(random_bytes(64).hex()) return cls.model_validate({"Ed25519Sig": list(random_bytes(64))})
class ZkSignatureSerializer(OperationProofSerializer, RootModel[str]): class ZkSignatureComponentsSerializer(NbeSerializer):
root: BytesFromHex """ZK signature proof with pi_a, pi_b, pi_c components as int arrays."""
pi_a: BytesFromIntArray = Field(description="32 bytes as int array")
pi_b: BytesFromIntArray = Field(description="64 bytes as int array")
pi_c: BytesFromIntArray = Field(description="32 bytes as int array")
class ZkSignatureSerializer(OperationProofSerializer, NbeSerializer):
"""ZK signature wrapped in ZkSig key."""
zk_sig: ZkSignatureComponentsSerializer = Field(alias="ZkSig")
def into_operation_proof(self) -> NbeSignature: def into_operation_proof(self) -> NbeSignature:
# Concatenate the components for storage
signature = self.zk_sig.pi_a + self.zk_sig.pi_b + self.zk_sig.pi_c
return ZkSignature.model_validate( return ZkSignature.model_validate(
{ {
"signature": self.root, "signature": signature,
} }
) )
@classmethod @classmethod
def from_random(cls, *args, **kwargs) -> Self: def from_random(cls, *args, **kwargs) -> Self:
return cls.model_validate(random_bytes(32).hex()) return cls.model_validate({
"ZkSig": {
"pi_a": list(random_bytes(32)),
"pi_b": list(random_bytes(64)),
"pi_c": list(random_bytes(32)),
}
})
class ZkAndEd25519SignaturesSerializer(OperationProofSerializer, NbeSerializer): class ZkAndEd25519SignaturesSerializer(OperationProofSerializer, NbeSerializer):
zk_signature: BytesFromHex = Field(alias="zk_sig") """Combined ZK and Ed25519 signatures."""
ed25519_signature: BytesFromHex = Field(alias="ed25519_sig") zk_signature: ZkSignatureComponentsSerializer = Field(alias="zk_sig")
ed25519_signature: BytesFromIntArray = Field(alias="ed25519_sig")
def into_operation_proof(self) -> NbeSignature: def into_operation_proof(self) -> NbeSignature:
zk_sig = self.zk_signature.pi_a + self.zk_signature.pi_b + self.zk_signature.pi_c
return ZkAndEd25519Signature.model_validate( return ZkAndEd25519Signature.model_validate(
{ {
"zk_signature": self.zk_signature, "zk_signature": zk_sig,
"ed25519_signature": self.ed25519_signature, "ed25519_signature": self.ed25519_signature,
} }
) )
@classmethod @classmethod
def from_random(cls, *args, **kwargs) -> Self: def from_random(cls, *args, **kwargs) -> Self:
return ZkAndEd25519SignaturesSerializer.model_validate( return cls.model_validate(
{ {
"zk_sig": random_bytes(32).hex(), "zk_sig": {
"ed25519_sig": random_bytes(32).hex(), "pi_a": list(random_bytes(32)),
"pi_b": list(random_bytes(64)),
"pi_c": list(random_bytes(32)),
},
"ed25519_sig": list(random_bytes(64)),
} }
) )

View File

@ -1,7 +1,7 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import Annotated, Optional, Self, Union from typing import Annotated, Optional, Self, Union
from pydantic import Field from pydantic import BeforeValidator, Field
from rusty_results import Option from rusty_results import Option
from core.models import NbeSerializer from core.models import NbeSerializer
@ -22,13 +22,11 @@ class ProofOfLeadershipSerializer(NbeSerializer, EnforceSubclassFromRandom, ABC)
class Groth16LeaderProofSerializer(ProofOfLeadershipSerializer, NbeSerializer): class Groth16LeaderProofSerializer(ProofOfLeadershipSerializer, NbeSerializer):
entropy_contribution: BytesFromHex = Field(description="Fr integer.") proof: BytesFromIntArray = Field(description="Bytes in Integer Array format (128 bytes).")
leader_key: BytesFromHex = Field(description="Bytes in Integer Array format.") entropy_contribution: BytesFromHex = Field(description="Fr integer in hex.")
proof: BytesFromIntArray = Field( leader_key: BytesFromHex = Field(description="Ed25519PublicKey in hex.")
description="Bytes in Integer Array format.", voucher_cm: BytesFromHex = Field(description="VoucherCm hash in hex.")
) public: Optional[PublicSerializer] = Field(default=None, description="Only received if Node is running in dev mode.")
public: Optional[PublicSerializer] = Field(description="Only received if Node is running in dev mode.")
voucher_cm: BytesFromHex = Field(description="Hash.")
def into_proof_of_leadership(self) -> ProofOfLeadership: def into_proof_of_leadership(self) -> ProofOfLeadership:
public = self.public.into_public() if self.public else None public = self.public.into_public() if self.public else None
@ -46,27 +44,21 @@ class Groth16LeaderProofSerializer(ProofOfLeadershipSerializer, NbeSerializer):
def from_random(cls, *, slot: Option[int]) -> Self: def from_random(cls, *, slot: Option[int]) -> Self:
return cls.model_validate( return cls.model_validate(
{ {
"proof": list(random_bytes(128)),
"entropy_contribution": random_bytes(32).hex(), "entropy_contribution": random_bytes(32).hex(),
"leader_key": random_bytes(32).hex(), "leader_key": random_bytes(32).hex(),
"proof": list(random_bytes(128)),
"public": PublicSerializer.from_random(slot),
"voucher_cm": random_bytes(32).hex(), "voucher_cm": random_bytes(32).hex(),
"public": PublicSerializer.from_random(slot),
} }
) )
# Fake Variant that never resolves to allow union type checking to work # Fake Variant that never resolves to allow union type checking to work
# TODO: Remove this when another Variant is added
from pydantic import BeforeValidator
def _always_fail(_): def _always_fail(_):
raise ValueError("Never matches.") raise ValueError("Never matches.")
_NeverType = Annotated[object, BeforeValidator(_always_fail)] _NeverType = Annotated[object, BeforeValidator(_always_fail)]
#
ProofOfLeadershipVariants = Union[ ProofOfLeadershipVariants = Union[
Groth16LeaderProofSerializer, _NeverType Groth16LeaderProofSerializer, _NeverType

View File

@ -1,14 +1,14 @@
from typing import List, Self from typing import List, Self
from pydantic import Field from pydantic import Field
from rusty_results import Option
from core.models import NbeSerializer from core.models import NbeSerializer
from models.transactions.transaction import Transaction from models.transactions.transaction import Transaction
from node.api.serializers.fields import BytesFromHex from node.api.serializers.fields import BytesFromIntArray
from node.api.serializers.proof import ( from node.api.serializers.proof import (
OperationProofSerializer, OperationProofSerializer,
OperationProofSerializerField, OperationProofSerializerField,
ZkSignatureComponentsSerializer,
) )
from node.api.serializers.transaction import TransactionSerializer from node.api.serializers.transaction import TransactionSerializer
from utils.protocols import FromRandom from utils.protocols import FromRandom
@ -20,8 +20,8 @@ class SignedTransactionSerializer(NbeSerializer, FromRandom):
operations_proofs: List[OperationProofSerializerField] = Field( operations_proofs: List[OperationProofSerializerField] = Field(
alias="ops_proofs", description="List of OperationProof. Order should match `Self::transaction::operations`." alias="ops_proofs", description="List of OperationProof. Order should match `Self::transaction::operations`."
) )
ledger_transaction_proof: BytesFromHex = Field( ledger_transaction_proof: ZkSignatureComponentsSerializer = Field(
alias="ledger_tx_proof", description="Hash.", min_length=128, max_length=128 alias="ledger_tx_proof", description="ZK proof with pi_a, pi_b, pi_c."
) )
def into_transaction(self) -> Transaction: def into_transaction(self) -> Transaction:
@ -42,13 +42,20 @@ class SignedTransactionSerializer(NbeSerializer, FromRandom):
ledger_transaction = self.transaction.ledger_transaction ledger_transaction = self.transaction.ledger_transaction
outputs = [output.into_note() for output in ledger_transaction.outputs] outputs = [output.into_note() for output in ledger_transaction.outputs]
# Combine pi_a, pi_b, pi_c into single proof bytes
proof_bytes = (
self.ledger_transaction_proof.pi_a +
self.ledger_transaction_proof.pi_b +
self.ledger_transaction_proof.pi_c
)
return Transaction.model_validate( return Transaction.model_validate(
{ {
"hash": self.transaction.hash, "hash": self.transaction.hash,
"operations": operations, "operations": operations,
"inputs": ledger_transaction.inputs, "inputs": ledger_transaction.inputs,
"outputs": outputs, "outputs": outputs,
"proof": self.ledger_transaction_proof, "proof": proof_bytes,
"execution_gas_price": self.transaction.execution_gas_price, "execution_gas_price": self.transaction.execution_gas_price,
"storage_gas_price": self.transaction.storage_gas_price, "storage_gas_price": self.transaction.storage_gas_price,
} }
@ -60,5 +67,13 @@ class SignedTransactionSerializer(NbeSerializer, FromRandom):
n = len(transaction.operations_contents) n = len(transaction.operations_contents)
operations_proofs = [OperationProofSerializer.from_random() for _ in range(n)] operations_proofs = [OperationProofSerializer.from_random() for _ in range(n)]
return cls.model_validate( return cls.model_validate(
{"mantle_tx": transaction, "ops_proofs": operations_proofs, "ledger_tx_proof": random_bytes(128).hex()} {
"mantle_tx": transaction,
"ops_proofs": operations_proofs,
"ledger_tx_proof": {
"pi_a": list(random_bytes(32)),
"pi_b": list(random_bytes(64)),
"pi_c": list(random_bytes(32)),
},
}
) )

View File

@ -6,17 +6,14 @@ from pydantic import Field
from core.models import NbeSerializer from core.models import NbeSerializer
from node.api.serializers.fields import BytesFromHex from node.api.serializers.fields import BytesFromHex
from node.api.serializers.ledger_transaction import LedgerTransactionSerializer from node.api.serializers.ledger_transaction import LedgerTransactionSerializer
from node.api.serializers.operation import ( from node.api.serializers.operation import OperationWrapper
OperationContentSerializer,
OperationContentSerializerField,
)
from utils.protocols import FromRandom from utils.protocols import FromRandom
from utils.random import random_bytes from utils.random import random_bytes
class TransactionSerializer(NbeSerializer, FromRandom): class TransactionSerializer(NbeSerializer, FromRandom):
hash: BytesFromHex = Field(description="Hash id in hex format.") hash: BytesFromHex = Field(description="Transaction hash in hex format.")
operations_contents: List[OperationContentSerializerField] = Field(alias="ops") operations_contents: List[OperationWrapper] = Field(alias="ops")
ledger_transaction: LedgerTransactionSerializer = Field(alias="ledger_tx") ledger_transaction: LedgerTransactionSerializer = Field(alias="ledger_tx")
execution_gas_price: int = Field(description="Integer in u64 format.") execution_gas_price: int = Field(description="Integer in u64 format.")
storage_gas_price: int = Field(description="Integer in u64 format.") storage_gas_price: int = Field(description="Integer in u64 format.")
@ -24,7 +21,7 @@ class TransactionSerializer(NbeSerializer, FromRandom):
@classmethod @classmethod
def from_random(cls) -> Self: def from_random(cls) -> Self:
n = 0 if randint(0, 1) <= 0.5 else randint(1, 5) n = 0 if randint(0, 1) <= 0.5 else randint(1, 5)
operations_contents = [OperationContentSerializer.from_random() for _ in range(n)] operations_contents = [OperationWrapper.from_random() for _ in range(n)]
return cls.model_validate( return cls.model_validate(
{ {
"hash": random_bytes(32).hex(), "hash": random_bytes(32).hex(),