diff --git a/.bumpversion.cfg b/.bumpversion.cfg index d486fb89..e4113416 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.1 +current_version = 0.9.0 commit = True tag = False diff --git a/CHANGELOG.md b/CHANGELOG.md index a55c47b3..8ba89cc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [0.9.0] - 2022-10-14 + +### Fixed + +- Fix RPC error parsing and introduce new classes for RPC error messages [(#17)](https://github.com/kevinheavey/solders/pull/17) + ## [0.8.1] - 2022-10-10 ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 97ababe8..e1e1b037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1464,7 +1464,7 @@ dependencies = [ [[package]] name = "solders" -version = "0.8.1" +version = "0.9.0" dependencies = [ "base64 0.13.0", "bincode", diff --git a/Cargo.toml b/Cargo.toml index 476f70aa..314732a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "solders" -version = "0.8.1" +version = "0.9.0" edition = "2021" include = ["/src", "/LICENSE", "/pyproject.toml"] description = "Python binding to the Solana Rust SDK" diff --git a/docs/conf.py b/docs/conf.py index f623e384..9248e23b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,7 +22,7 @@ author = "Kevin Heavey" # The full version, including alpha/beta/rc tags -release = "0.8.1" +release = "0.9.0" # -- General configuration --------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 8b88e08d..0fd1021d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "solders" -version = "0.8.1" +version = "0.9.0" description = "Python bindings for Solana Rust tools" authors = ["kevinheavey "] license = "Apache" @@ -29,7 +29,7 @@ build-backend = "maturin" [project] name = "solders" -version = "0.8.1" +version = "0.9.0" description = "Python binding to the Solana Rust SDK" authors = [ {name = "kevinheavey", email = "kevinheavey123@gmail.com"} ] license = {file = "LICENSE"} diff --git a/python/solders/rpc/errors.pyi b/python/solders/rpc/errors.pyi index ee79dacc..dbc240a8 100644 --- a/python/solders/rpc/errors.pyi +++ b/python/solders/rpc/errors.pyi @@ -6,11 +6,60 @@ class BlockCleanedUp: slot: int first_available_block: int def __init__(self, slot: int, first_available_block: int) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "BlockCleanedUp": ... + @staticmethod + def from_bytes(data: bytes) -> "BlockCleanedUp": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class BlockCleanedUpMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "BlockCleanedUpMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "BlockCleanedUpMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class SendTransactionPreflightFailure: message: str result: RpcSimulateTransactionResult - def __init__(self, message: str, result: RpcSimulateTransactionResult) -> None: ... + def __init__(self, message: str, data: RpcSimulateTransactionResult) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "SendTransactionPreflightFailure": ... + @staticmethod + def from_bytes(data: bytes) -> "SendTransactionPreflightFailure": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class SendTransactionPreflightFailureMessage: + message: str + data: RpcSimulateTransactionResult + def __init__(self, message: str, data: RpcSimulateTransactionResult) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "SendTransactionPreflightFailureMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "SendTransactionPreflightFailureMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class RpcCustomErrorFieldless: TransactionSignatureVerificationFailure: "RpcCustomErrorFieldless" @@ -26,42 +75,356 @@ class RpcCustomErrorFieldless: class BlockNotAvailable: slot: int def __init__(self, slot: int) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "BlockNotAvailable": ... + @staticmethod + def from_bytes(data: bytes) -> "BlockNotAvailable": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class BlockNotAvailableMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "BlockNotAvailableMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "BlockNotAvailableMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class NodeUnhealthy: num_slots_behind: Optional[int] def __init__(self, num_slots_behind: Optional[int] = None) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "NodeUnhealthy": ... + @staticmethod + def from_bytes(data: bytes) -> "NodeUnhealthy": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class NodeUnhealthyMessage: + message: str + data: NodeUnhealthy + def __init__(self, message: str, data: NodeUnhealthy) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "NodeUnhealthyMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "NodeUnhealthyMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class TransactionPrecompileVerificationFailure: def __init__(self, error: TransactionErrorType) -> None: ... def error(self) -> TransactionErrorType: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "TransactionPrecompileVerificationFailure": ... + @staticmethod + def from_bytes(data: bytes) -> "TransactionPrecompileVerificationFailure": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class TransactionPrecompileVerificationFailureMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "TransactionPrecompileVerificationFailureMessage": ... + @staticmethod + def from_bytes( + data: bytes, + ) -> "TransactionPrecompileVerificationFailureMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class SlotSkipped: slot: int def __init__(self, slot: int) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "SlotSkipped": ... + @staticmethod + def from_bytes(data: bytes) -> "SlotSkipped": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class SlotSkippedMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "SlotSkippedMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "SlotSkippedMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class LongTermStorageSlotSkipped: slot: int def __init__(self, slot: int) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "LongTermStorageSlotSkipped": ... + @staticmethod + def from_bytes(data: bytes) -> "LongTermStorageSlotSkipped": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class LongTermStorageSlotSkippedMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "LongTermStorageSlotSkippedMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "LongTermStorageSlotSkippedMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class KeyExcludedFromSecondaryIndex: index_key: str def __init__(self, index_key: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "KeyExcludedFromSecondaryIndex": ... + @staticmethod + def from_bytes(data: bytes) -> "KeyExcludedFromSecondaryIndex": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class KeyExcludedFromSecondaryIndexMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "KeyExcludedFromSecondaryIndexMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "KeyExcludedFromSecondaryIndexMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class ScanError: message: str def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "ScanError": ... + @staticmethod + def from_bytes(data: bytes) -> "ScanError": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class ScanErrorMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "ScanErrorMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "ScanErrorMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class BlockStatusNotAvailableYet: slot: int def __init__(self, slot: int) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "BlockStatusNotAvailableYet": ... + @staticmethod + def from_bytes(data: bytes) -> "BlockStatusNotAvailableYet": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class BlockStatusNotAvailableYetMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "BlockStatusNotAvailableYetMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "BlockStatusNotAvailableYetMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class MinContextSlotNotReached: context_slot: int def __init__(self, context_slot: int) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "MinContextSlotNotReached": ... + @staticmethod + def from_bytes(data: bytes) -> "MinContextSlotNotReached": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class MinContextSlotNotReachedMessage: + message: str + data: MinContextSlotNotReached + def __init__(self, message: str, data: MinContextSlotNotReached) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "MinContextSlotNotReachedMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "MinContextSlotNotReachedMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... class UnsupportedTransactionVersion: def __init__(self, value: int) -> None: ... def value(self) -> int: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "UnsupportedTransactionVersion": ... + @staticmethod + def from_bytes(data: bytes) -> "UnsupportedTransactionVersion": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class UnsupportedTransactionVersionMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "UnsupportedTransactionVersionMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "UnsupportedTransactionVersionMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class ParseErrorMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "ParseErrorMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "ParseErrorMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class InvalidRequestMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "InvalidRequestMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "InvalidRequestMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class MethodNotFoundMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "MethodNotFoundMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "MethodNotFoundMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class InvalidParamsMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "InvalidParamsMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "InvalidParamsMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... + +class InternalErrorMessage: + message: str + def __init__(self, message: str) -> None: ... + def to_json(self) -> str: ... + @staticmethod + def from_json(raw: str) -> "InternalErrorMessage": ... + @staticmethod + def from_bytes(data: bytes) -> "InternalErrorMessage": ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, o: object) -> bool: ... + def __bytes__(self) -> bytes: ... + def __hash__(self) -> int: ... RpcCustomError = Union[ RpcCustomErrorFieldless, diff --git a/python/solders/rpc/responses.pyi b/python/solders/rpc/responses.pyi index f730a0e4..7f6a1206 100644 --- a/python/solders/rpc/responses.pyi +++ b/python/solders/rpc/responses.pyi @@ -7,7 +7,28 @@ from solders.transaction_status import UiConfirmedBlock from solders.signature import Signature from solders.pubkey import Pubkey from solders.epoch_schedule import EpochSchedule -from solders.rpc.errors import RpcCustomError, UnsupportedTransactionVersion +from solders.rpc.errors import ( + RpcCustomError, + UnsupportedTransactionVersion, + RpcCustomErrorFieldless, + BlockCleanedUpMessage, + SendTransactionPreflightFailureMessage, + BlockNotAvailableMessage, + NodeUnhealthyMessage, + TransactionPrecompileVerificationFailureMessage, + SlotSkippedMessage, + LongTermStorageSlotSkippedMessage, + KeyExcludedFromSecondaryIndexMessage, + ScanErrorMessage, + BlockStatusNotAvailableYetMessage, + MinContextSlotNotReachedMessage, + UnsupportedTransactionVersionMessage, + ParseErrorMessage, + InvalidRequestMessage, + MethodNotFoundMessage, + InvalidParamsMessage, + InternalErrorMessage, +) from solders.transaction import VersionedTransaction from solders.transaction_status import ( TransactionErrorType, @@ -22,16 +43,29 @@ class RpcResponseContext: api_version: Optional[str] def __init__(self, slot: int, api_version: Optional[str] = None) -> None: ... -class RpcError: - code: int - message: str - data: Optional[RpcCustomError] - def __init__( - self, code: int, message: str, data: Optional[RpcCustomError] = None - ) -> None: ... +RPCError = Union[ + RpcCustomErrorFieldless, + BlockCleanedUpMessage, + SendTransactionPreflightFailureMessage, + BlockNotAvailableMessage, + NodeUnhealthyMessage, + TransactionPrecompileVerificationFailureMessage, + SlotSkippedMessage, + LongTermStorageSlotSkippedMessage, + KeyExcludedFromSecondaryIndexMessage, + ScanErrorMessage, + BlockStatusNotAvailableYetMessage, + MinContextSlotNotReachedMessage, + UnsupportedTransactionVersionMessage, + ParseErrorMessage, + InvalidRequestMessage, + MethodNotFoundMessage, + InvalidParamsMessage, + InternalErrorMessage, +] T = TypeVar("T") -Resp = Union[RpcError, T] +Resp = Union[RPCError, T] class GetAccountInfoResp: context: RpcResponseContext @@ -2112,11 +2146,11 @@ class SubscriptionResult: class SubscriptionError: id: int - error: RpcError + error: RPCError def __init__( self, id: int, - error: RpcError, + error: RPCError, ) -> None: ... def to_json(self) -> str: ... @staticmethod @@ -2175,7 +2209,7 @@ SlotUpdate = Union[ ] RPCResult = Union[ - RpcError, + RPCError, GetAccountInfoResp, GetAccountInfoJsonParsedResp, GetAccountInfoMaybeJsonParsedResp, diff --git a/src/rpc/errors.rs b/src/rpc/errors.rs index 1ea16e62..37372545 100644 --- a/src/rpc/errors.rs +++ b/src/rpc/errors.rs @@ -11,6 +11,53 @@ use std::fmt::Display; use super::responses::RpcSimulateTransactionResult; +macro_rules! error_message { + ($name:ident) => { + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] + #[pyclass(module = "solders.rpc.errors", subclass)] + #[serde(rename_all = "camelCase")] + pub struct $name { + #[pyo3(get)] + message: String, + } + + transaction_status_boilerplate!($name); + + #[richcmp_eq_only] + #[common_methods] + #[pymethods] + impl $name { + #[new] + pub fn new(message: String) -> Self { + message.into() + } + } + }; + ($name:ident, $data_type:ty) => { + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] + #[pyclass(module = "solders.rpc.errors", subclass)] + #[serde(rename_all = "camelCase")] + pub struct $name { + #[pyo3(get)] + message: String, + #[pyo3(get)] + data: $data_type, + } + + transaction_status_boilerplate!($name); + + #[richcmp_eq_only] + #[common_methods] + #[pymethods] + impl $name { + #[new] + pub fn new(message: String, data: $data_type) -> Self { + (message, data).into() + } + } + }; +} + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] #[serde(rename_all = "camelCase")] @@ -33,6 +80,8 @@ impl BlockCleanedUp { } } +error_message!(BlockCleanedUpMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] pub struct SendTransactionPreflightFailure { @@ -54,6 +103,11 @@ impl SendTransactionPreflightFailure { } } +error_message!( + SendTransactionPreflightFailureMessage, + RpcSimulateTransactionResult +); + #[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] #[pyclass(module = "solders.transaction_status")] pub enum RpcCustomErrorFieldless { @@ -82,6 +136,8 @@ impl BlockNotAvailable { } } +error_message!(BlockNotAvailableMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] #[serde(rename_all = "camelCase")] @@ -102,6 +158,8 @@ impl NodeUnhealthy { } } +error_message!(NodeUnhealthyMessage, NodeUnhealthy); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] pub struct TransactionPrecompileVerificationFailure(TransactionErrorType); @@ -123,6 +181,8 @@ impl TransactionPrecompileVerificationFailure { } } +error_message!(TransactionPrecompileVerificationFailureMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] pub struct SlotSkipped { @@ -142,6 +202,8 @@ impl SlotSkipped { } } +error_message!(SlotSkippedMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] pub struct LongTermStorageSlotSkipped { @@ -161,6 +223,8 @@ impl LongTermStorageSlotSkipped { } } +error_message!(LongTermStorageSlotSkippedMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] #[serde(rename_all = "camelCase")] @@ -181,6 +245,8 @@ impl KeyExcludedFromSecondaryIndex { } } +error_message!(KeyExcludedFromSecondaryIndexMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] pub struct ScanError { @@ -200,6 +266,8 @@ impl ScanError { } } +error_message!(ScanErrorMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] pub struct BlockStatusNotAvailableYet { @@ -219,6 +287,8 @@ impl BlockStatusNotAvailableYet { } } +error_message!(BlockStatusNotAvailableYetMessage); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] #[serde(rename_all = "camelCase")] @@ -239,6 +309,8 @@ impl MinContextSlotNotReached { } } +error_message!(MinContextSlotNotReachedMessage, MinContextSlotNotReached); + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, From, Into)] #[pyclass(module = "solders.rpc.errors", subclass)] pub struct UnsupportedTransactionVersion(pub u8); @@ -260,6 +332,8 @@ impl UnsupportedTransactionVersion { } } +error_message!(UnsupportedTransactionVersionMessage); + #[derive(FromPyObject, Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum RpcCustomError { @@ -278,6 +352,12 @@ pub enum RpcCustomError { UnsupportedTransactionVersion(UnsupportedTransactionVersion), } +error_message!(ParseErrorMessage); +error_message!(InvalidRequestMessage); +error_message!(MethodNotFoundMessage); +error_message!(InvalidParamsMessage); +error_message!(InternalErrorMessage); + impl IntoPy for RpcCustomError { fn into_py(self, py: Python<'_>) -> PyObject { match self { @@ -304,16 +384,34 @@ pub(crate) fn create_errors_mod(py: Python<'_>) -> PyResult<&PyModule> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; let typing = py.import("typing")?; let union = typing.getattr("Union")?; let union_members = vec![ diff --git a/src/rpc/responses.rs b/src/rpc/responses.rs index e5b115d0..894dfe1b 100644 --- a/src/rpc/responses.rs +++ b/src/rpc/responses.rs @@ -10,7 +10,8 @@ use pyo3::{ types::{PyBytes, PyTuple}, PyClass, PyTypeInfo, }; -use serde::{Deserialize, Serialize}; +use serde::{de::Error, Deserialize, Serialize, Serializer}; +use serde_json::Value; use serde_with::{serde_as, DisplayFromStr, FromInto, OneOrMany, TryFromInto}; use solana_sdk::{ clock::{Epoch, Slot, UnixTimestamp}, @@ -36,7 +37,18 @@ use crate::rpc::tmp_response::{ RpcStakeActivation as RpcStakeActivationOriginal, RpcSupply as RpcSupplyOriginal, RpcVote as RpcVoteOriginal, SlotInfo as SlotInfoOriginal, SlotTransactionStats as SlotTransactionStatsOriginal, SlotUpdate as SlotUpdateOriginal, - StakeActivationState as StakeActivationStateOriginal, + StakeActivationState as StakeActivationStateOriginal, JSON_RPC_SCAN_ERROR, + JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP, JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE, + JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET, + JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX, + JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED, + JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY, + JSON_RPC_SERVER_ERROR_NO_SNAPSHOT, JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE, + JSON_RPC_SERVER_ERROR_SLOT_SKIPPED, JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE, + JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE, + JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH, + JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE, + JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION, }; use crate::transaction_status::{ EncodedConfirmedTransactionWithStatusMeta, TransactionConfirmationStatus, TransactionErrorType, @@ -58,9 +70,15 @@ use crate::{ }; use camelpaste::paste; -use super::errors::{RpcCustomError, UnsupportedTransactionVersion}; - -// note: the `data` field of the error struct is always None +use super::errors::{ + BlockCleanedUpMessage, BlockNotAvailableMessage, BlockStatusNotAvailableYetMessage, + InternalErrorMessage, InvalidParamsMessage, InvalidRequestMessage, + KeyExcludedFromSecondaryIndexMessage, LongTermStorageSlotSkippedMessage, MethodNotFoundMessage, + MinContextSlotNotReachedMessage, NodeUnhealthyMessage, ParseErrorMessage, + RpcCustomErrorFieldless, ScanErrorMessage, SendTransactionPreflightFailureMessage, + SlotSkippedMessage, TransactionPrecompileVerificationFailureMessage, + UnsupportedTransactionVersion, UnsupportedTransactionVersionMessage, +}; pub trait CommonMethodsRpcResp<'a>: std::fmt::Display @@ -229,36 +247,313 @@ macro_rules! contextless_resp_no_eq { }; } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[pyclass(module = "solders.rpc.responses", subclass)] -pub struct RpcError { - /// Code - #[pyo3(get)] - pub code: i64, - /// Message - #[pyo3(get)] - pub message: String, - /// Data - #[serde(default, skip_serializing_if = "Option::is_none")] - #[pyo3(get)] - pub data: Option, +#[derive(FromPyObject, Clone, Debug, PartialEq, Eq)] +pub enum RPCError { + Fieldless(RpcCustomErrorFieldless), + BlockCleanedUpMessage(BlockCleanedUpMessage), + SendTransactionPreflightFailureMessage(SendTransactionPreflightFailureMessage), + BlockNotAvailableMessage(BlockNotAvailableMessage), + NodeUnhealthyMessage(NodeUnhealthyMessage), + TransactionPrecompileVerificationFailureMessage( + TransactionPrecompileVerificationFailureMessage, + ), + SlotSkippedMessage(SlotSkippedMessage), + LongTermStorageSlotSkippedMessage(LongTermStorageSlotSkippedMessage), + KeyExcludedFromSecondaryIndexMessage(KeyExcludedFromSecondaryIndexMessage), + ScanErrorMessage(ScanErrorMessage), + BlockStatusNotAvailableYetMessage(BlockStatusNotAvailableYetMessage), + MinContextSlotNotReachedMessage(MinContextSlotNotReachedMessage), + UnsupportedTransactionVersionMessage(UnsupportedTransactionVersionMessage), + ParseErrorMessage(ParseErrorMessage), + InvalidRequestMessage(InvalidRequestMessage), + MethodNotFoundMessage(MethodNotFoundMessage), + InvalidParamsMessage(InvalidParamsMessage), + InternalErrorMessage(InternalErrorMessage), +} + +impl RPCError { + fn py_to_json(&self) -> String { + match self { + Self::Fieldless(x) => serde_json::to_string(x).unwrap(), + Self::BlockCleanedUpMessage(x) => serde_json::to_string(x).unwrap(), + Self::SendTransactionPreflightFailureMessage(x) => serde_json::to_string(x).unwrap(), + Self::BlockNotAvailableMessage(x) => serde_json::to_string(x).unwrap(), + Self::NodeUnhealthyMessage(x) => serde_json::to_string(x).unwrap(), + Self::TransactionPrecompileVerificationFailureMessage(x) => { + serde_json::to_string(x).unwrap() + } + Self::SlotSkippedMessage(x) => serde_json::to_string(x).unwrap(), + Self::LongTermStorageSlotSkippedMessage(x) => serde_json::to_string(x).unwrap(), + Self::KeyExcludedFromSecondaryIndexMessage(x) => serde_json::to_string(x).unwrap(), + Self::ScanErrorMessage(x) => serde_json::to_string(x).unwrap(), + Self::BlockStatusNotAvailableYetMessage(x) => serde_json::to_string(x).unwrap(), + Self::MinContextSlotNotReachedMessage(x) => serde_json::to_string(x).unwrap(), + Self::UnsupportedTransactionVersionMessage(x) => serde_json::to_string(x).unwrap(), + Self::ParseErrorMessage(x) => serde_json::to_string(x).unwrap(), + Self::InvalidRequestMessage(x) => serde_json::to_string(x).unwrap(), + Self::MethodNotFoundMessage(x) => serde_json::to_string(x).unwrap(), + Self::InvalidParamsMessage(x) => serde_json::to_string(x).unwrap(), + Self::InternalErrorMessage(x) => serde_json::to_string(x).unwrap(), + } + } + + fn py_from_json(raw: &str) -> PyResult { + serde_json::from_str(raw).map_err(to_py_err) + } } -#[richcmp_eq_only] -#[common_methods] -#[pymethods] -impl RpcError { - #[new] - pub fn new(code: i64, message: &str, data: Option) -> Self { - Self { - code, - message: message.to_string(), - data, - } +impl IntoPy for RPCError { + fn into_py(self, py: Python<'_>) -> PyObject { + match self { + Self::Fieldless(x) => x.into_py(py), + Self::BlockCleanedUpMessage(x) => x.into_py(py), + Self::SendTransactionPreflightFailureMessage(x) => x.into_py(py), + Self::BlockNotAvailableMessage(x) => x.into_py(py), + Self::NodeUnhealthyMessage(x) => x.into_py(py), + Self::TransactionPrecompileVerificationFailureMessage(x) => x.into_py(py), + Self::SlotSkippedMessage(x) => x.into_py(py), + Self::LongTermStorageSlotSkippedMessage(x) => x.into_py(py), + Self::KeyExcludedFromSecondaryIndexMessage(x) => x.into_py(py), + Self::ScanErrorMessage(x) => x.into_py(py), + Self::BlockStatusNotAvailableYetMessage(x) => x.into_py(py), + Self::MinContextSlotNotReachedMessage(x) => x.into_py(py), + Self::UnsupportedTransactionVersionMessage(x) => x.into_py(py), + Self::ParseErrorMessage(x) => x.into_py(py), + Self::InvalidRequestMessage(x) => x.into_py(py), + Self::MethodNotFoundMessage(x) => x.into_py(py), + Self::InvalidParamsMessage(x) => x.into_py(py), + Self::InternalErrorMessage(x) => x.into_py(py), + } + } +} + +impl<'de> serde::Deserialize<'de> for RPCError { + fn deserialize>(d: D) -> Result { + let value = Value::deserialize(d)?; + + Ok( + match value + .get("code") + .ok_or_else(|| D::Error::custom("Object has no field 'code'.")) + .map(Value::as_i64)? + { + Some(JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE) => { + Self::Fieldless( + RpcCustomErrorFieldless::TransactionSignatureVerificationFailure, + ) + } + Some(JSON_RPC_SERVER_ERROR_NO_SNAPSHOT) => { + Self::Fieldless(RpcCustomErrorFieldless::NoSnapshot) + } + Some(JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE) => { + Self::Fieldless(RpcCustomErrorFieldless::TransactionHistoryNotAvailable) + } + Some(JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH) => { + Self::Fieldless(RpcCustomErrorFieldless::TransactionSignatureLenMismatch) + } + Some(JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP) => { + Self::BlockCleanedUpMessage(BlockCleanedUpMessage::deserialize(value).unwrap()) + } + Some(JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE) => { + Self::SendTransactionPreflightFailureMessage( + SendTransactionPreflightFailureMessage::deserialize(value).unwrap(), + ) + } + Some(JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE) => Self::BlockNotAvailableMessage( + BlockNotAvailableMessage::deserialize(value).unwrap(), + ), + Some(JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY) => { + Self::NodeUnhealthyMessage(NodeUnhealthyMessage::deserialize(value).unwrap()) + } + Some(JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE) => { + Self::TransactionPrecompileVerificationFailureMessage( + TransactionPrecompileVerificationFailureMessage::deserialize(value) + .unwrap(), + ) + } + Some(JSON_RPC_SERVER_ERROR_SLOT_SKIPPED) => { + Self::SlotSkippedMessage(SlotSkippedMessage::deserialize(value).unwrap()) + } + Some(JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED) => { + Self::LongTermStorageSlotSkippedMessage( + LongTermStorageSlotSkippedMessage::deserialize(value).unwrap(), + ) + } + Some(JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX) => { + Self::KeyExcludedFromSecondaryIndexMessage( + KeyExcludedFromSecondaryIndexMessage::deserialize(value).unwrap(), + ) + } + Some(JSON_RPC_SCAN_ERROR) => { + Self::ScanErrorMessage(ScanErrorMessage::deserialize(value).unwrap()) + } + Some(JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET) => { + Self::BlockStatusNotAvailableYetMessage( + BlockStatusNotAvailableYetMessage::deserialize(value).unwrap(), + ) + } + Some(JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED) => { + Self::MinContextSlotNotReachedMessage( + MinContextSlotNotReachedMessage::deserialize(value).unwrap(), + ) + } + Some(JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION) => { + Self::UnsupportedTransactionVersionMessage( + UnsupportedTransactionVersionMessage::deserialize(value).unwrap(), + ) + } + Some(-32700) => { + Self::ParseErrorMessage(ParseErrorMessage::deserialize(value).unwrap()) + } + Some(-32600) => { + Self::InvalidRequestMessage(InvalidRequestMessage::deserialize(value).unwrap()) + } + Some(-32601) => { + Self::MethodNotFoundMessage(MethodNotFoundMessage::deserialize(value).unwrap()) + } + Some(-32602) => { + Self::InvalidParamsMessage(InvalidParamsMessage::deserialize(value).unwrap()) + } + Some(-32603) => { + Self::InternalErrorMessage(InternalErrorMessage::deserialize(value).unwrap()) + } + type_ => panic!("unsupported type {:?}", type_), + }, + ) } } -response_data_boilerplate!(RpcError); +impl Serialize for RPCError { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + #[serde(untagged)] + enum RPCError_<'a> { + Fieldless(&'a RpcCustomErrorFieldless), + BlockCleanedUpMessage(&'a BlockCleanedUpMessage), + SendTransactionPreflightFailureMessage(&'a SendTransactionPreflightFailureMessage), + BlockNotAvailableMessage(&'a BlockNotAvailableMessage), + NodeUnhealthyMessage(&'a NodeUnhealthyMessage), + TransactionPrecompileVerificationFailureMessage( + &'a TransactionPrecompileVerificationFailureMessage, + ), + SlotSkippedMessage(&'a SlotSkippedMessage), + LongTermStorageSlotSkippedMessage(&'a LongTermStorageSlotSkippedMessage), + KeyExcludedFromSecondaryIndexMessage(&'a KeyExcludedFromSecondaryIndexMessage), + ScanErrorMessage(&'a ScanErrorMessage), + BlockStatusNotAvailableYetMessage(&'a BlockStatusNotAvailableYetMessage), + MinContextSlotNotReachedMessage(&'a MinContextSlotNotReachedMessage), + UnsupportedTransactionVersionMessage(&'a UnsupportedTransactionVersionMessage), + ParseErrorMessage(&'a ParseErrorMessage), + InvalidRequestMessage(&'a InvalidRequestMessage), + MethodNotFoundMessage(&'a MethodNotFoundMessage), + InvalidParamsMessage(&'a InvalidParamsMessage), + InternalErrorMessage(&'a InternalErrorMessage), + } + + #[derive(Serialize)] + struct RPCErrorWithCode<'a> { + #[serde(rename = "code")] + t: i64, + #[serde(flatten)] + err: RPCError_<'a>, + } + + let msg = match self { + RPCError::Fieldless(f) => match f { + RpcCustomErrorFieldless::TransactionSignatureVerificationFailure => { + RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE, + err: RPCError_::Fieldless(f), + } + } + RpcCustomErrorFieldless::NoSnapshot => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_NO_SNAPSHOT, + err: RPCError_::Fieldless(f), + }, + RpcCustomErrorFieldless::TransactionHistoryNotAvailable => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE, + err: RPCError_::Fieldless(f), + }, + RpcCustomErrorFieldless::TransactionSignatureLenMismatch => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH, + err: RPCError_::Fieldless(f), + }, + }, + RPCError::BlockCleanedUpMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP, + err: RPCError_::BlockCleanedUpMessage(x), + }, + RPCError::SendTransactionPreflightFailureMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE, + err: RPCError_::SendTransactionPreflightFailureMessage(x), + }, + RPCError::BlockNotAvailableMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE, + err: RPCError_::BlockNotAvailableMessage(x), + }, + RPCError::NodeUnhealthyMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY, + err: RPCError_::NodeUnhealthyMessage(x), + }, + RPCError::TransactionPrecompileVerificationFailureMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE, + err: RPCError_::TransactionPrecompileVerificationFailureMessage(x), + }, + RPCError::SlotSkippedMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_SLOT_SKIPPED, + err: RPCError_::SlotSkippedMessage(x), + }, + RPCError::LongTermStorageSlotSkippedMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED, + err: RPCError_::LongTermStorageSlotSkippedMessage(x), + }, + RPCError::KeyExcludedFromSecondaryIndexMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX, + err: RPCError_::KeyExcludedFromSecondaryIndexMessage(x), + }, + RPCError::ScanErrorMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SCAN_ERROR, + err: RPCError_::ScanErrorMessage(x), + }, + RPCError::BlockStatusNotAvailableYetMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET, + err: RPCError_::BlockStatusNotAvailableYetMessage(x), + }, + RPCError::MinContextSlotNotReachedMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED, + err: RPCError_::MinContextSlotNotReachedMessage(x), + }, + RPCError::UnsupportedTransactionVersionMessage(x) => RPCErrorWithCode { + t: JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION, + err: RPCError_::UnsupportedTransactionVersionMessage(x), + }, + RPCError::ParseErrorMessage(x) => RPCErrorWithCode { + t: -32700, + err: RPCError_::ParseErrorMessage(x), + }, + RPCError::InvalidRequestMessage(x) => RPCErrorWithCode { + t: -32600, + err: RPCError_::InvalidRequestMessage(x), + }, + RPCError::MethodNotFoundMessage(x) => RPCErrorWithCode { + t: -32601, + err: RPCError_::MethodNotFoundMessage(x), + }, + RPCError::InvalidParamsMessage(x) => RPCErrorWithCode { + t: -32602, + err: RPCError_::InvalidParamsMessage(x), + }, + RPCError::InternalErrorMessage(x) => RPCErrorWithCode { + t: -32603, + err: RPCError_::InternalErrorMessage(x), + }, + }; + msg.serialize(serializer) + } +} #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -296,7 +591,7 @@ pub enum Resp> { Error { #[serde(skip_deserializing)] jsonrpc: crate::rpc::requests::V2, - error: RpcError, + error: RPCError, #[serde(skip_deserializing)] id: u64, }, @@ -993,6 +1288,7 @@ pub struct RpcSimulateTransactionResult { #[pyo3(get)] pub units_consumed: Option, #[serde_as(as = "Option>")] + #[serde(default)] #[pyo3(get)] pub return_data: Option, } @@ -2421,7 +2717,7 @@ pub struct SubscriptionError { #[serde(skip_deserializing)] jsonrpc: crate::rpc::requests::V2, #[pyo3(get)] - error: RpcError, + error: RPCError, #[pyo3(get)] id: u64, } @@ -2433,7 +2729,7 @@ response_data_boilerplate!(SubscriptionError); #[pymethods] impl SubscriptionError { #[new] - pub fn new(id: u64, error: RpcError) -> Self { + pub fn new(id: u64, error: RPCError) -> Self { Self { id, error, @@ -2519,8 +2815,8 @@ macro_rules ! pyunion_resp { fn from_json(raw: &str, parser: &str) -> PyResult { match parser { - stringify!($err_variant) => {let parsed = $err_variant::py_from_json(raw)?; let as_enum = Self::RpcError(parsed); Ok(as_enum)}, - $(stringify!($variant) => {let parsed = $variant::py_from_json(raw)?; let as_enum = match parsed {Resp::Error {error, ..} => Self::RpcError(error), Resp::Result {result, ..} => Self::$variant(result)};Ok(as_enum)},)+ + stringify!($err_variant) => {let parsed = $err_variant::py_from_json(raw)?; let as_enum = Self::RPCError(parsed); Ok(as_enum)}, + $(stringify!($variant) => {let parsed = $variant::py_from_json(raw)?; let as_enum = match parsed {Resp::Error {error, ..} => Self::RPCError(error), Resp::Result {result, ..} => Self::$variant(result)};Ok(as_enum)},)+ _ => Err(PyValueError::new_err(format!("Unrecognised parser: {}", parser))) } } @@ -2539,7 +2835,7 @@ macro_rules ! pyunion_resp { pyunion_resp!( RPCResult, - RpcError, + RPCError, GetAccountInfoResp, GetAccountInfoJsonParsedResp, GetAccountInfoMaybeJsonParsedResp, @@ -2620,7 +2916,7 @@ pyunion_resp!( /// #[pyfunction] pub fn batch_to_json(resps: Vec) -> String { - let objects: Vec> = resps + let objects: Vec> = resps .iter() .map(|r| serde_json::from_str(&r.to_json()).unwrap()) .collect(); @@ -2648,7 +2944,7 @@ pub fn batch_to_json(resps: Vec) -> String { /// #[pyfunction] pub fn batch_from_json(raw: &str, parsers: Vec<&PyType>) -> PyResult> { - let raw_objects: Vec> = + let raw_objects: Vec> = serde_json::from_str(raw).map_err(to_py_err)?; let raw_objects_len = raw_objects.len(); let parsers_len = parsers.len(); @@ -2724,84 +3020,101 @@ pub(crate) fn create_responses_mod(py: Python<'_>) -> PyResult<&PyModule> { let union = typing.getattr("Union")?; let typevar = typing.getattr("TypeVar")?; let t = typevar.call1(("T",))?; - m.add("T", t)?; - m.add( - "Resp", - union.get_item(PyTuple::new( - py, - vec![RpcError::type_object(py).as_ref(), t], - ))?, - )?; - let rpc_result_members = PyTuple::new( - py, - vec![ - RpcError::type_object(py), - GetAccountInfoResp::type_object(py), - GetAccountInfoJsonParsedResp::type_object(py), - GetAccountInfoMaybeJsonParsedResp::type_object(py), - GetBalanceResp::type_object(py), - GetBlockProductionResp::type_object(py), - GetBlockResp::type_object(py), - GetBlockCommitmentResp::type_object(py), - GetBlockHeightResp::type_object(py), - GetBlocksResp::type_object(py), - GetBlocksWithLimitResp::type_object(py), - GetBlockTimeResp::type_object(py), - GetClusterNodesResp::type_object(py), - GetEpochInfoResp::type_object(py), - GetEpochScheduleResp::type_object(py), - GetFeeForMessageResp::type_object(py), - GetFirstAvailableBlockResp::type_object(py), - GetGenesisHashResp::type_object(py), - GetHealthResp::type_object(py), - GetHighestSnapshotSlotResp::type_object(py), - GetIdentityResp::type_object(py), - GetInflationGovernorResp::type_object(py), - GetInflationRateResp::type_object(py), - GetInflationRewardResp::type_object(py), - GetLargestAccountsResp::type_object(py), - GetLatestBlockhashResp::type_object(py), - GetLeaderScheduleResp::type_object(py), - GetMaxRetransmitSlotResp::type_object(py), - GetMaxShredInsertSlotResp::type_object(py), - GetMinimumBalanceForRentExemptionResp::type_object(py), - GetMultipleAccountsResp::type_object(py), - GetMultipleAccountsJsonParsedResp::type_object(py), - GetMultipleAccountsMaybeJsonParsedResp::type_object(py), - GetProgramAccountsWithContextResp::type_object(py), - GetProgramAccountsResp::type_object(py), - GetProgramAccountsWithContextJsonParsedResp::type_object(py), - GetProgramAccountsJsonParsedResp::type_object(py), - GetProgramAccountsMaybeJsonParsedResp::type_object(py), - GetProgramAccountsWithContextMaybeJsonParsedResp::type_object(py), - GetRecentPerformanceSamplesResp::type_object(py), - GetSignaturesForAddressResp::type_object(py), - GetSignatureStatusesResp::type_object(py), - GetSlotResp::type_object(py), - GetSlotLeaderResp::type_object(py), - GetSlotLeadersResp::type_object(py), - GetStakeActivationResp::type_object(py), - GetSupplyResp::type_object(py), - GetTokenAccountBalanceResp::type_object(py), - GetTokenAccountsByDelegateResp::type_object(py), - GetTokenAccountsByDelegateJsonParsedResp::type_object(py), - GetTokenAccountsByOwnerResp::type_object(py), - GetTokenAccountsByOwnerJsonParsedResp::type_object(py), - GetTokenLargestAccountsResp::type_object(py), - GetTokenSupplyResp::type_object(py), - GetTransactionResp::type_object(py), - GetTransactionCountResp::type_object(py), - GetVersionResp::type_object(py), - RpcVersionInfo::type_object(py), - GetVoteAccountsResp::type_object(py), - IsBlockhashValidResp::type_object(py), - MinimumLedgerSlotResp::type_object(py), - RequestAirdropResp::type_object(py), - SendTransactionResp::type_object(py), - SimulateTransactionResp::type_object(py), - ValidatorExitResp::type_object(py), - ], + let rpc_error_members_raw = vec![ + RpcCustomErrorFieldless::type_object(py), + BlockCleanedUpMessage::type_object(py), + SendTransactionPreflightFailureMessage::type_object(py), + BlockNotAvailableMessage::type_object(py), + NodeUnhealthyMessage::type_object(py), + TransactionPrecompileVerificationFailureMessage::type_object(py), + SlotSkippedMessage::type_object(py), + LongTermStorageSlotSkippedMessage::type_object(py), + KeyExcludedFromSecondaryIndexMessage::type_object(py), + ScanErrorMessage::type_object(py), + BlockStatusNotAvailableYetMessage::type_object(py), + MinContextSlotNotReachedMessage::type_object(py), + UnsupportedTransactionVersionMessage::type_object(py), + ]; + let rpc_error_members = PyTuple::new(py, rpc_error_members_raw.clone()); + let rpc_error_alias = union.get_item(rpc_error_members)?; + let rpc_error_members_raw_cloned = rpc_error_members_raw.clone(); + let mut resp_members = vec![t]; + resp_members.extend( + rpc_error_members_raw_cloned + .iter() + .map(|x| x.as_ref()) + .collect::>(), ); + m.add("T", t)?; + m.add("Resp", union.get_item(PyTuple::new(py, resp_members))?)?; + let mut rpc_result_members_raw = rpc_error_members_raw.clone(); + rpc_result_members_raw.extend(vec![ + GetAccountInfoResp::type_object(py), + GetAccountInfoJsonParsedResp::type_object(py), + GetAccountInfoMaybeJsonParsedResp::type_object(py), + GetBalanceResp::type_object(py), + GetBlockProductionResp::type_object(py), + GetBlockResp::type_object(py), + GetBlockCommitmentResp::type_object(py), + GetBlockHeightResp::type_object(py), + GetBlocksResp::type_object(py), + GetBlocksWithLimitResp::type_object(py), + GetBlockTimeResp::type_object(py), + GetClusterNodesResp::type_object(py), + GetEpochInfoResp::type_object(py), + GetEpochScheduleResp::type_object(py), + GetFeeForMessageResp::type_object(py), + GetFirstAvailableBlockResp::type_object(py), + GetGenesisHashResp::type_object(py), + GetHealthResp::type_object(py), + GetHighestSnapshotSlotResp::type_object(py), + GetIdentityResp::type_object(py), + GetInflationGovernorResp::type_object(py), + GetInflationRateResp::type_object(py), + GetInflationRewardResp::type_object(py), + GetLargestAccountsResp::type_object(py), + GetLatestBlockhashResp::type_object(py), + GetLeaderScheduleResp::type_object(py), + GetMaxRetransmitSlotResp::type_object(py), + GetMaxShredInsertSlotResp::type_object(py), + GetMinimumBalanceForRentExemptionResp::type_object(py), + GetMultipleAccountsResp::type_object(py), + GetMultipleAccountsJsonParsedResp::type_object(py), + GetMultipleAccountsMaybeJsonParsedResp::type_object(py), + GetProgramAccountsWithContextResp::type_object(py), + GetProgramAccountsResp::type_object(py), + GetProgramAccountsWithContextJsonParsedResp::type_object(py), + GetProgramAccountsJsonParsedResp::type_object(py), + GetProgramAccountsMaybeJsonParsedResp::type_object(py), + GetProgramAccountsWithContextMaybeJsonParsedResp::type_object(py), + GetRecentPerformanceSamplesResp::type_object(py), + GetSignaturesForAddressResp::type_object(py), + GetSignatureStatusesResp::type_object(py), + GetSlotResp::type_object(py), + GetSlotLeaderResp::type_object(py), + GetSlotLeadersResp::type_object(py), + GetStakeActivationResp::type_object(py), + GetSupplyResp::type_object(py), + GetTokenAccountBalanceResp::type_object(py), + GetTokenAccountsByDelegateResp::type_object(py), + GetTokenAccountsByDelegateJsonParsedResp::type_object(py), + GetTokenAccountsByOwnerResp::type_object(py), + GetTokenAccountsByOwnerJsonParsedResp::type_object(py), + GetTokenLargestAccountsResp::type_object(py), + GetTokenSupplyResp::type_object(py), + GetTransactionResp::type_object(py), + GetTransactionCountResp::type_object(py), + GetVersionResp::type_object(py), + RpcVersionInfo::type_object(py), + GetVoteAccountsResp::type_object(py), + IsBlockhashValidResp::type_object(py), + MinimumLedgerSlotResp::type_object(py), + RequestAirdropResp::type_object(py), + SendTransactionResp::type_object(py), + SimulateTransactionResp::type_object(py), + ValidatorExitResp::type_object(py), + ]); + let rpc_result_members = PyTuple::new(py, rpc_result_members_raw); let rpc_result_alias = union.get_item(rpc_result_members)?; let slot_update_members = PyTuple::new( py, @@ -2846,7 +3159,6 @@ pub(crate) fn create_responses_mod(py: Python<'_>) -> PyResult<&PyModule> { let websocket_message_members = PyTuple::new(py, websocket_message_members_raw); let websocket_message_alias = union.get_item(websocket_message_members)?; m.add_class::()?; - m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; @@ -2966,6 +3278,7 @@ pub(crate) fn create_responses_mod(py: Python<'_>) -> PyResult<&PyModule> { m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add("RPCError", rpc_error_alias)?; m.add("RPCResult", rpc_result_alias)?; m.add("SlotUpdate", slot_update_alias)?; m.add("Notification", notification_alias)?; diff --git a/src/rpc/tmp_response.rs b/src/rpc/tmp_response.rs index 4e4c3fa6..98aab06a 100644 --- a/src/rpc/tmp_response.rs +++ b/src/rpc/tmp_response.rs @@ -17,6 +17,23 @@ use { thiserror::Error, }; +pub const JSON_RPC_SERVER_ERROR_BLOCK_CLEANED_UP: i64 = -32001; +pub const JSON_RPC_SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE: i64 = -32002; +pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_VERIFICATION_FAILURE: i64 = -32003; +pub const JSON_RPC_SERVER_ERROR_BLOCK_NOT_AVAILABLE: i64 = -32004; +pub const JSON_RPC_SERVER_ERROR_NODE_UNHEALTHY: i64 = -32005; +pub const JSON_RPC_SERVER_ERROR_TRANSACTION_PRECOMPILE_VERIFICATION_FAILURE: i64 = -32006; +pub const JSON_RPC_SERVER_ERROR_SLOT_SKIPPED: i64 = -32007; +pub const JSON_RPC_SERVER_ERROR_NO_SNAPSHOT: i64 = -32008; +pub const JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_SLOT_SKIPPED: i64 = -32009; +pub const JSON_RPC_SERVER_ERROR_KEY_EXCLUDED_FROM_SECONDARY_INDEX: i64 = -32010; +pub const JSON_RPC_SERVER_ERROR_TRANSACTION_HISTORY_NOT_AVAILABLE: i64 = -32011; +pub const JSON_RPC_SCAN_ERROR: i64 = -32012; +pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: i64 = -32013; +pub const JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: i64 = -32014; +pub const JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: i64 = -32015; +pub const JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: i64 = -32016; + #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)] #[serde(rename_all = "camelCase")] pub struct RpcBlockCommitment { diff --git a/tests/test_rpc_responses.py b/tests/test_rpc_responses.py index 3eae1678..720aab91 100644 --- a/tests/test_rpc_responses.py +++ b/tests/test_rpc_responses.py @@ -91,7 +91,6 @@ RpcVoteAccountStatus, RpcSignatureResponse, EpochInfo, - RpcError, RpcBlockUpdate, RpcLogsResponse, RpcVote, @@ -119,7 +118,12 @@ parse_notification, parse_websocket_message, ) -from solders.rpc.errors import NodeUnhealthy +from solders.rpc.errors import ( + NodeUnhealthy, + NodeUnhealthyMessage, + SendTransactionPreflightFailureMessage, + InvalidParamsMessage, +) from solders.hash import Hash from solders.account import Account, AccountJSON from solders.epoch_schedule import EpochSchedule @@ -206,9 +210,8 @@ def test_get_account_info_null() -> None: def test_get_account_info_error() -> None: raw = '{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid param: WrongSize"},"id":1}' parsed = GetAccountInfoResp.from_json(raw) - assert isinstance(parsed, RpcError) - error = RpcError(code=-32602, message="Invalid param: WrongSize") - assert parsed == error + assert isinstance(parsed, InvalidParamsMessage) + assert parsed.message == "Invalid param: WrongSize" def test_get_account_info_json_parsed() -> None: @@ -633,10 +636,7 @@ def test_get_health_resp_unhealthy_generic() -> None: "id": 1 }""" parsed = GetHealthResp.from_json(raw) - assert isinstance(parsed, RpcError) - # This is the only custom rpc error that can be empty. - # Thus, if the JSON error has `"data": {}`, - # it's implicitly a NodeUnhealthy error. + assert isinstance(parsed, NodeUnhealthyMessage) assert parsed.data == NodeUnhealthy() @@ -653,7 +653,7 @@ def test_get_health_additional_info() -> None: "id": 1 }""" parsed = GetHealthResp.from_json(raw) - assert isinstance(parsed, RpcError) + assert isinstance(parsed, NodeUnhealthyMessage) assert parsed.data == NodeUnhealthy(42) @@ -2509,7 +2509,7 @@ def test_parse_ws_message() -> None: raw_err = '{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid param: WrongSize"},"id":1}' parsed_err = parse_websocket_message(raw_err) assert isinstance(parsed_err[0], SubscriptionError) - assert isinstance(parsed_err[0].error, RpcError) + assert isinstance(parsed_err[0].error, InvalidParamsMessage) raw_ok = '{ "jsonrpc": "2.0", "result": 23784, "id": 3 }' parsed_ok = parse_websocket_message(raw_ok) assert isinstance(parsed_ok[0], SubscriptionResult) @@ -2523,3 +2523,12 @@ def test_parse_ws_message() -> None: parsed_multi = parse_websocket_message(raw_multi) assert len(parsed_multi) == 2 assert isinstance(parsed_multi[0], SubscriptionResult) + + +def test_parse_preflight_error() -> None: + raw = '{"code":-32002,"message":"Transaction simulation failed: Error processing Instruction 0: custom program error: 0x1","data":{"accounts":null,"err":{"InstructionError":[0,{"Custom":1}]},"logs":["Program 11111111111111111111111111111111 invoke [1]","Transfer: insufficient lamports 995000, need 1000001","Program 11111111111111111111111111111111 failed: custom program error: 0x1"],"unitsConsumed":0}}' + raw_full = '{"jsonrpc":"2.0","error":{"code":-32002,"message":"Transaction simulation failed: Error processing Instruction 0: custom program error: 0x1","data":{"accounts":null,"err":{"InstructionError":[0,{"Custom":1}]},"logs":["Program 11111111111111111111111111111111 invoke [1]","Transfer: insufficient lamports 995000, need 1000001","Program 11111111111111111111111111111111 failed: custom program error: 0x1"],"unitsConsumed":0}},"id":0}' + err = SendTransactionPreflightFailureMessage.from_json(raw) + assert isinstance(err, SendTransactionPreflightFailureMessage) + err2 = SendTransactionResp.from_json(raw_full) + assert isinstance(err2, SendTransactionPreflightFailureMessage)