Handle unknown operations in explorer

This commit is contained in:
gusto 2026-06-11 14:15:08 +03:00
parent 44043b8c1a
commit 527873c8c6
No known key found for this signature in database
5 changed files with 79 additions and 9 deletions

View File

@ -85,6 +85,20 @@ class LeaderClaim(NbeContent):
mantle_tx_hash: HexBytes
class UnknownOp(NbeContent):
type: Literal["UnknownOp"] = "UnknownOp"
opcode: int | str
raw_payload: dict | str
OperationContent = (
LedgerTransfer | ChannelInscribe | ChannelBlob | ChannelSetKeys | SDPDeclare | SDPWithdraw | SDPActive | LeaderClaim
LedgerTransfer
| ChannelInscribe
| ChannelBlob
| ChannelSetKeys
| SDPDeclare
| SDPWithdraw
| SDPActive
| LeaderClaim
| UnknownOp
)

View File

@ -31,4 +31,9 @@ class ZkAndEd25519Signature(NbeSignature):
ed25519_signature: HexBytes
OperationProof = Ed25519Signature | ZkSignature | ZkAndEd25519Signature
class UnknownSignature(NbeSignature):
type: Literal["Unknown"] = "Unknown"
signature: HexBytes
OperationProof = Ed25519Signature | ZkSignature | ZkAndEd25519Signature | UnknownSignature

View File

@ -51,6 +51,11 @@ class ChannelInscribeOpSerializer(NbeSerializer, FromRandom):
}
)
class UnknownOpSerializer(NbeSerializer):
"""Fallback serializer for unrecognized opcodes."""
opcode: int
payload: dict[str, Any] = Field(default_factory=dict)
OPCODE_TO_SERIALIZER: dict[int, type] = {
OPCODE_LEDGER: LedgerOpSerializer,
@ -58,19 +63,17 @@ OPCODE_TO_SERIALIZER: dict[int, type] = {
}
def _parse_mantle_op(data: Any) -> Union[LedgerOpSerializer, ChannelInscribeOpSerializer]:
if isinstance(data, (LedgerOpSerializer, ChannelInscribeOpSerializer)):
def _parse_mantle_op(data: Any) -> Any:
if isinstance(data, (LedgerOpSerializer, ChannelInscribeOpSerializer, UnknownOpSerializer)):
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:
raise ValueError(
f"Unsupported mantle op opcode {opcode}; known opcodes: {sorted(OPCODE_TO_SERIALIZER)}."
)
return UnknownOpSerializer(opcode=opcode, payload=data.get("payload", {}))
return serializer_class.model_validate(data["payload"])
raise ValueError(f"Cannot parse mantle op from {type(data).__name__}.")
MantleOpSerializerVariants = Union[LedgerOpSerializer, ChannelInscribeOpSerializer]
MantleOpSerializerVariants = Union[LedgerOpSerializer, ChannelInscribeOpSerializer, UnknownOpSerializer]
MantleOpSerializerField = Annotated[MantleOpSerializerVariants, BeforeValidator(_parse_mantle_op)]

View File

@ -80,7 +80,18 @@ class SignedTransactionSerializer(NbeSerializer, FromRandom):
}
)
else:
raise ValueError(f"Unsupported mantle op type: {type(op).__name__}")
# Gracefully handle unknown ops. We assume that whatever comes from the node is correct.
operations.append({
"content": {
"type": "UnknownOp",
"opcode": getattr(op, "opcode", "unknown"),
"raw_payload": op.model_dump() if hasattr(op, "model_dump") else str(op),
},
"proof": {
"type": "Unknown",
"signature": proof.to_bytes() if hasattr(proof, "to_bytes") else getattr(proof, "root", b""),
},
})
return Transaction.model_validate(
{

View File

@ -0,0 +1,37 @@
import pytest
from models.transactions.transaction import Transaction
from core.types import HexBytes
def test_transaction_validation_with_unknown_types():
# This data simulates what is produced by SignedTransactionSerializer.into_transaction()
# when it encounters unknown opcodes or proofs.
transaction_data = {
"hash": HexBytes(b"\x01" * 32),
"operations": [
{
"content": {
"type": "UnknownOp",
"opcode": 123,
"raw_payload": {"some": "data"}
},
"proof": {
"type": "Unknown",
"signature": HexBytes(b"\x02" * 64)
}
}
],
"execution_gas_price": 100,
"storage_gas_price": 200
}
tx = Transaction.model_validate(transaction_data)
assert tx.hash == HexBytes(b"\x01" * 32)
assert tx.operations[0].content.type == "UnknownOp"
assert tx.operations[0].content.opcode == 123
assert tx.operations[0].proof.type == "Unknown"
assert tx.operations[0].proof.signature == HexBytes(b"\x02" * 64)
if __name__ == "__main__":
test_transaction_validation_with_unknown_types()
print("Test passed!")