mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-08-26 20:01:16 +00:00
Merge branch 'main' into schouhy/add-block-context
This commit is contained in:
@@ -60,6 +60,18 @@ pub struct Block {
|
||||
pub bedrock_parent_id: MantleMsgId,
|
||||
}
|
||||
|
||||
impl Serialize for Block {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
crate::borsh_base64::serialize(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Block {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
crate::borsh_base64::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub struct HashableBlockData {
|
||||
pub block_id: BlockId,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//! This module provides utilities for serializing and deserializing data by combining Borsh and
|
||||
//! Base64 encodings.
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub fn serialize<T: BorshSerialize, S: serde::Serializer>(
|
||||
value: &T,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let borsh_encoded = borsh::to_vec(value).map_err(serde::ser::Error::custom)?;
|
||||
let base64_encoded = STANDARD.encode(&borsh_encoded);
|
||||
Serialize::serialize(&base64_encoded, serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, T: BorshDeserialize, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<T, D::Error> {
|
||||
let base64_encoded = <String as Deserialize>::deserialize(deserializer)?;
|
||||
let borsh_encoded = STANDARD
|
||||
.decode(base64_encoded.as_bytes())
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
borsh::from_slice(&borsh_encoded).map_err(serde::de::Error::custom)
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
use nssa::AccountId;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::rpc_primitives::errors::RpcError;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SequencerRpcError {
|
||||
pub jsonrpc: String,
|
||||
pub error: RpcError,
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum SequencerClientError {
|
||||
#[error("HTTP error")]
|
||||
HTTPError(#[from] reqwest::Error),
|
||||
#[error("Serde error")]
|
||||
SerdeError(#[from] serde_json::Error),
|
||||
#[error("Internal error: {0:?}")]
|
||||
InternalError(SequencerRpcError),
|
||||
}
|
||||
|
||||
impl From<SequencerRpcError> for SequencerClientError {
|
||||
fn from(value: SequencerRpcError) -> Self {
|
||||
Self::InternalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ExecutionFailureKind {
|
||||
#[error("Failed to get data from sequencer")]
|
||||
SequencerError(#[source] anyhow::Error),
|
||||
#[error("Inputs amounts does not match outputs")]
|
||||
AmountMismatchError,
|
||||
#[error("Accounts key not found")]
|
||||
KeyNotFoundError,
|
||||
#[error("Sequencer client error: {0:?}")]
|
||||
SequencerClientError(#[from] SequencerClientError),
|
||||
#[error("Can not pay for operation")]
|
||||
InsufficientFundsError,
|
||||
#[error("Account {0} data is invalid")]
|
||||
AccountDataError(AccountId),
|
||||
}
|
||||
+1
-3
@@ -4,10 +4,8 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
|
||||
pub mod block;
|
||||
mod borsh_base64;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod rpc_primitives;
|
||||
pub mod sequencer_client;
|
||||
pub mod transaction;
|
||||
|
||||
// Module for tests utility functions
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
use std::fmt;
|
||||
|
||||
use serde_json::{Value, to_value};
|
||||
|
||||
#[derive(serde::Serialize)]
|
||||
pub struct RpcParseError(pub String);
|
||||
|
||||
/// This struct may be returned from JSON RPC server in case of error.
|
||||
///
|
||||
/// It is expected that that this struct has impls From<_> all other RPC errors
|
||||
/// like [`RpcBlockError`](crate::types::blocks::RpcBlockError).
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RpcError {
|
||||
#[serde(flatten)]
|
||||
pub error_struct: Option<RpcErrorKind>,
|
||||
/// Deprecated please use the `error_struct` instead.
|
||||
pub code: i64,
|
||||
/// Deprecated please use the `error_struct` instead.
|
||||
pub message: String,
|
||||
/// Deprecated please use the `error_struct` instead.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub data: Option<Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(tag = "name", content = "cause", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum RpcErrorKind {
|
||||
RequestValidationError(RpcRequestValidationErrorKind),
|
||||
HandlerError(Value),
|
||||
InternalError(Value),
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(tag = "name", content = "info", rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
pub enum RpcRequestValidationErrorKind {
|
||||
MethodNotFound { method_name: String },
|
||||
ParseError { error_message: String },
|
||||
}
|
||||
|
||||
/// A general Server Error.
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub enum ServerError {
|
||||
Timeout,
|
||||
Closed,
|
||||
}
|
||||
|
||||
impl RpcError {
|
||||
/// A generic constructor.
|
||||
///
|
||||
/// Mostly for completeness, doesn't do anything but filling in the corresponding fields.
|
||||
#[must_use]
|
||||
pub const fn new(code: i64, message: String, data: Option<Value>) -> Self {
|
||||
Self {
|
||||
code,
|
||||
message,
|
||||
data,
|
||||
error_struct: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an Invalid Param error.
|
||||
pub fn invalid_params(data: impl serde::Serialize) -> Self {
|
||||
let value = match to_value(data) {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return Self::server_error(Some(format!(
|
||||
"Failed to serialize invalid parameters error: {:?}",
|
||||
err.to_string()
|
||||
)));
|
||||
}
|
||||
};
|
||||
Self::new(-32_602, "Invalid params".to_owned(), Some(value))
|
||||
}
|
||||
|
||||
/// Create a server error.
|
||||
pub fn server_error<E: serde::Serialize>(e: Option<E>) -> Self {
|
||||
Self::new(
|
||||
-32_000,
|
||||
"Server error".to_owned(),
|
||||
e.map(|v| to_value(v).expect("Must be representable in JSON")),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a parse error.
|
||||
#[must_use]
|
||||
pub fn parse_error(e: String) -> Self {
|
||||
Self {
|
||||
code: -32_700,
|
||||
message: "Parse error".to_owned(),
|
||||
data: Some(Value::String(e.clone())),
|
||||
error_struct: Some(RpcErrorKind::RequestValidationError(
|
||||
RpcRequestValidationErrorKind::ParseError { error_message: e },
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn serialization_error(e: &str) -> Self {
|
||||
Self::new_internal_error(Some(Value::String(e.to_owned())), e)
|
||||
}
|
||||
|
||||
/// Helper method to define extract `INTERNAL_ERROR` in separate `RpcErrorKind`
|
||||
/// Returns `HANDLER_ERROR` if the error is not internal one.
|
||||
#[must_use]
|
||||
pub fn new_internal_or_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
|
||||
if error_struct["name"] == "INTERNAL_ERROR" {
|
||||
let error_message = match error_struct["info"].get("error_message") {
|
||||
Some(Value::String(error_message)) => error_message.as_str(),
|
||||
_ => "InternalError happened during serializing InternalError",
|
||||
};
|
||||
Self::new_internal_error(error_data, error_message)
|
||||
} else {
|
||||
Self::new_handler_error(error_data, error_struct)
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new_internal_error(error_data: Option<Value>, info: &str) -> Self {
|
||||
Self {
|
||||
code: -32_000,
|
||||
message: "Server error".to_owned(),
|
||||
data: error_data,
|
||||
error_struct: Some(RpcErrorKind::InternalError(serde_json::json!({
|
||||
"name": "INTERNAL_ERROR",
|
||||
"info": serde_json::json!({"error_message": info})
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_handler_error(error_data: Option<Value>, error_struct: Value) -> Self {
|
||||
Self {
|
||||
code: -32_000,
|
||||
message: "Server error".to_owned(),
|
||||
data: error_data,
|
||||
error_struct: Some(RpcErrorKind::HandlerError(error_struct)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a method not found error.
|
||||
#[must_use]
|
||||
pub fn method_not_found(method: String) -> Self {
|
||||
Self {
|
||||
code: -32_601,
|
||||
message: "Method not found".to_owned(),
|
||||
data: Some(Value::String(method.clone())),
|
||||
error_struct: Some(RpcErrorKind::RequestValidationError(
|
||||
RpcRequestValidationErrorKind::MethodNotFound {
|
||||
method_name: method,
|
||||
},
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RpcError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RpcParseError> for RpcError {
|
||||
fn from(parse_error: RpcParseError) -> Self {
|
||||
Self::parse_error(parse_error.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for RpcError {
|
||||
fn from(_: std::convert::Infallible) -> Self {
|
||||
// SAFETY: Infallible error can never be constructed, so this code can never be reached.
|
||||
unsafe { core::hint::unreachable_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ServerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Timeout => write!(f, "ServerError: Timeout"),
|
||||
Self::Closed => write!(f, "ServerError: Closed"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServerError> for RpcError {
|
||||
fn from(e: ServerError) -> Self {
|
||||
let error_data = match to_value(&e) {
|
||||
Ok(value) => value,
|
||||
Err(_err) => {
|
||||
return Self::new_internal_error(None, "Failed to serialize ServerError");
|
||||
}
|
||||
};
|
||||
Self::new_internal_error(Some(error_data), e.to_string().as_str())
|
||||
}
|
||||
}
|
||||
@@ -1,588 +0,0 @@
|
||||
// Copyright 2017 tokio-jsonrpc Developers
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
||||
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
||||
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
||||
// copied, modified, or distributed except according to those terms.
|
||||
|
||||
//! JSON-RPC 2.0 messages.
|
||||
//!
|
||||
//! The main entrypoint here is the [Message](enum.Message.html). The others are just building
|
||||
//! blocks and you should generally work with `Message` instead.
|
||||
use std::fmt::{Formatter, Result as FmtResult};
|
||||
|
||||
use serde::{
|
||||
de::{Deserializer, Error, Unexpected, Visitor},
|
||||
ser::{SerializeStruct as _, Serializer},
|
||||
};
|
||||
use serde_json::{Result as JsonResult, Value};
|
||||
|
||||
use super::errors::RpcError;
|
||||
|
||||
pub type Parsed = Result<Message, Broken>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct Version;
|
||||
|
||||
impl serde::Serialize for Version {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_str("2.0")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for Version {
|
||||
#[expect(
|
||||
clippy::renamed_function_params,
|
||||
reason = "More readable than original serde parameter names"
|
||||
)]
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
struct VersionVisitor;
|
||||
impl Visitor<'_> for VersionVisitor {
|
||||
type Value = Version;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
|
||||
formatter.write_str("a version string")
|
||||
}
|
||||
|
||||
fn visit_str<E: Error>(self, value: &str) -> Result<Version, E> {
|
||||
match value {
|
||||
"2.0" => Ok(Version),
|
||||
_ => Err(E::invalid_value(Unexpected::Str(value), &"value 2.0")),
|
||||
}
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_str(VersionVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
/// An RPC request.
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[expect(
|
||||
clippy::partial_pub_fields,
|
||||
reason = "We don't want to allow access to the version, but the others are public for ease of use"
|
||||
)]
|
||||
pub struct Request {
|
||||
jsonrpc: Version,
|
||||
pub method: String,
|
||||
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||
pub params: Value,
|
||||
pub id: Value,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
#[must_use]
|
||||
pub fn from_payload_version_2_0(method: String, payload: serde_json::Value) -> Self {
|
||||
Self {
|
||||
jsonrpc: Version,
|
||||
method,
|
||||
params: payload,
|
||||
// ToDo: Correct checking of id
|
||||
id: 1.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Answer the request with a (positive) reply.
|
||||
///
|
||||
/// The ID is taken from the request.
|
||||
#[must_use]
|
||||
pub fn reply(&self, reply: Value) -> Message {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(reply),
|
||||
id: self.id.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Answer the request with an error.
|
||||
#[must_use]
|
||||
pub fn error(&self, error: RpcError) -> Message {
|
||||
Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(error),
|
||||
id: self.id.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A response to an RPC.
|
||||
///
|
||||
/// It is created by the methods on [Request](struct.Request.html).
|
||||
#[expect(
|
||||
clippy::partial_pub_fields,
|
||||
reason = "We don't want to allow access to the version, but the others are public for ease of use"
|
||||
)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Response {
|
||||
jsonrpc: Version,
|
||||
pub result: Result<Value, RpcError>,
|
||||
pub id: Value,
|
||||
}
|
||||
|
||||
impl serde::Serialize for Response {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut sub = serializer.serialize_struct("Response", 3)?;
|
||||
sub.serialize_field("jsonrpc", &self.jsonrpc)?;
|
||||
match &self.result {
|
||||
Ok(value) => sub.serialize_field("result", value),
|
||||
Err(err) => sub.serialize_field("error", err),
|
||||
}?;
|
||||
sub.serialize_field("id", &self.id)?;
|
||||
sub.end()
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trick for deserialization.
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct WireResponse {
|
||||
// It is actually used to eat and sanity check the deserialized text
|
||||
#[serde(rename = "jsonrpc")]
|
||||
_jsonrpc: Version,
|
||||
// Make sure we accept null as Some(Value::Null), instead of going to None
|
||||
#[serde(default, deserialize_with = "some_value")]
|
||||
result: Option<Value>,
|
||||
error: Option<RpcError>,
|
||||
id: Value,
|
||||
}
|
||||
|
||||
// Implementing deserialize is hard. We sidestep the difficulty by deserializing a similar
|
||||
// structure that directly corresponds to whatever is on the wire and then convert it to our more
|
||||
// convenient representation.
|
||||
impl<'de> serde::Deserialize<'de> for Response {
|
||||
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
let wr: WireResponse = serde::Deserialize::deserialize(deserializer)?;
|
||||
let result = match (wr.result, wr.error) {
|
||||
(Some(res), None) => Ok(res),
|
||||
(None, Some(err)) => Err(err),
|
||||
_ => {
|
||||
let err = D::Error::custom("Either 'error' or 'result' is expected, but not both");
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
Ok(Self {
|
||||
jsonrpc: Version,
|
||||
result,
|
||||
id: wr.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A notification (doesn't expect an answer).
|
||||
#[expect(
|
||||
clippy::partial_pub_fields,
|
||||
reason = "We don't want to allow access to the version, but the others are public for ease of use"
|
||||
)]
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Notification {
|
||||
jsonrpc: Version,
|
||||
pub method: String,
|
||||
#[serde(default, skip_serializing_if = "Value::is_null")]
|
||||
pub params: Value,
|
||||
}
|
||||
|
||||
/// One message of the JSON RPC protocol.
|
||||
///
|
||||
/// One message, directly mapped from the structures of the protocol. See the
|
||||
/// [specification](http://www.jsonrpc.org/specification) for more details.
|
||||
///
|
||||
/// Since the protocol allows one endpoint to be both client and server at the same time, the
|
||||
/// message can decode and encode both directions of the protocol.
|
||||
///
|
||||
/// The `Batch` variant is supposed to be created directly, without a constructor.
|
||||
///
|
||||
/// The `UnmatchedSub` variant is used when a request is an array and some of the subrequests
|
||||
/// aren't recognized as valid json rpc 2.0 messages. This is never returned as a top-level
|
||||
/// element, it is returned as `Err(Broken::Unmatched)`.
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Message {
|
||||
/// An RPC request.
|
||||
Request(Request),
|
||||
/// A response to a Request.
|
||||
Response(Response),
|
||||
/// A notification.
|
||||
Notification(Notification),
|
||||
/// A batch of more requests or responses.
|
||||
///
|
||||
/// The protocol allows bundling multiple requests, notifications or responses to a single
|
||||
/// message.
|
||||
///
|
||||
/// This variant has no direct constructor and is expected to be constructed manually.
|
||||
Batch(Vec<Self>),
|
||||
/// An unmatched sub entry in a `Batch`.
|
||||
///
|
||||
/// When there's a `Batch` and an element doesn't comform to the JSONRPC 2.0 format, that one
|
||||
/// is represented by this. This is never produced as a top-level value when parsing, the
|
||||
/// `Err(Broken::Unmatched)` is used instead. It is not possible to serialize.
|
||||
#[serde(skip_serializing)]
|
||||
UnmatchedSub(Value),
|
||||
}
|
||||
|
||||
impl Message {
|
||||
/// A constructor for a request.
|
||||
///
|
||||
/// The ID is auto-set to dontcare.
|
||||
#[must_use]
|
||||
pub fn request(method: String, params: Value) -> Self {
|
||||
let id = Value::from("dontcare");
|
||||
Self::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method,
|
||||
params,
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a top-level error (without an ID).
|
||||
#[must_use]
|
||||
pub const fn error(error: RpcError) -> Self {
|
||||
Self::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(error),
|
||||
id: Value::Null,
|
||||
})
|
||||
}
|
||||
|
||||
/// A constructor for a notification.
|
||||
#[must_use]
|
||||
pub const fn notification(method: String, params: Value) -> Self {
|
||||
Self::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/// A constructor for a response.
|
||||
#[must_use]
|
||||
pub const fn response(id: Value, result: Result<Value, RpcError>) -> Self {
|
||||
Self::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result,
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns id or Null if there is no id.
|
||||
#[must_use]
|
||||
pub fn id(&self) -> Value {
|
||||
match self {
|
||||
Self::Request(req) => req.id.clone(),
|
||||
Self::Response(response) => response.id.clone(),
|
||||
Self::Notification(_) | Self::Batch(_) | Self::UnmatchedSub(_) => Value::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for String {
|
||||
fn from(val: Message) -> Self {
|
||||
::serde_json::ser::to_string(&val).expect("message serialization to json should not fail")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Message> for Vec<u8> {
|
||||
fn from(val: Message) -> Self {
|
||||
::serde_json::ser::to_vec(&val)
|
||||
.expect("message serialization to json bytes should not fail")
|
||||
}
|
||||
}
|
||||
|
||||
/// A broken message.
|
||||
///
|
||||
/// Protocol-level errors.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Broken {
|
||||
/// It was valid JSON, but doesn't match the form of a JSONRPC 2.0 message.
|
||||
Unmatched(Value),
|
||||
/// Invalid JSON.
|
||||
#[serde(skip_deserializing)]
|
||||
SyntaxError(String),
|
||||
}
|
||||
|
||||
impl Broken {
|
||||
/// Generate an appropriate error message.
|
||||
///
|
||||
/// The error message for these things are specified in the RFC, so this just creates an error
|
||||
/// with the right values.
|
||||
#[must_use]
|
||||
pub fn reply(&self) -> Message {
|
||||
match self {
|
||||
Self::Unmatched(_) => Message::error(RpcError::parse_error(
|
||||
"JSON RPC Request format was expected".to_owned(),
|
||||
)),
|
||||
Self::SyntaxError(e) => Message::error(RpcError::parse_error(e.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A trick to easily deserialize and detect valid JSON, but invalid Message.
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum WireMessage {
|
||||
Message(Message),
|
||||
Broken(Broken),
|
||||
}
|
||||
|
||||
pub fn decoded_to_parsed(res: JsonResult<WireMessage>) -> Parsed {
|
||||
match res {
|
||||
Ok(WireMessage::Message(Message::UnmatchedSub(value))) => Err(Broken::Unmatched(value)),
|
||||
Ok(WireMessage::Message(m)) => Ok(m),
|
||||
Ok(WireMessage::Broken(b)) => Err(b),
|
||||
Err(e) => Err(Broken::SyntaxError(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a [Message](enum.Message.html) from a slice.
|
||||
///
|
||||
/// Invalid JSON or JSONRPC messages are reported as [Broken](enum.Broken.html).
|
||||
pub fn from_slice(s: &[u8]) -> Parsed {
|
||||
decoded_to_parsed(::serde_json::de::from_slice(s))
|
||||
}
|
||||
|
||||
/// Read a [Message](enum.Message.html) from a string.
|
||||
///
|
||||
/// Invalid JSON or JSONRPC messages are reported as [Broken](enum.Broken.html).
|
||||
pub fn from_str(s: &str) -> Parsed {
|
||||
from_slice(s.as_bytes())
|
||||
}
|
||||
|
||||
/// Deserializer for `Option<Value>` that produces `Some(Value::Null)`.
|
||||
///
|
||||
/// The usual one produces None in that case. But we need to know the difference between
|
||||
/// `{x: null}` and `{}`.
|
||||
fn some_value<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<Value>, D::Error> {
|
||||
serde::Deserialize::deserialize(deserializer).map(Some)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::{Value, de::from_slice, json, ser::to_vec};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Test serialization and deserialization of the Message.
|
||||
///
|
||||
/// We first deserialize it from a string. That way we check deserialization works.
|
||||
/// But since serialization doesn't have to produce the exact same result (order, spaces, …),
|
||||
/// we then serialize and deserialize the thing again and check it matches.
|
||||
#[test]
|
||||
fn message_serde() {
|
||||
// A helper for running one message test
|
||||
fn one(input: &str, expected: &Message) {
|
||||
let parsed: Message = from_str(input).unwrap();
|
||||
assert_eq!(*expected, parsed);
|
||||
let serialized = to_vec(&parsed).unwrap();
|
||||
let deserialized: Message = from_slice(&serialized).unwrap();
|
||||
assert_eq!(parsed, deserialized);
|
||||
}
|
||||
|
||||
// A request without parameters
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "method": "call", "id": 1}"#,
|
||||
&Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: Value::Null,
|
||||
id: json!(1),
|
||||
}),
|
||||
);
|
||||
// A request with parameters
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "method": "call", "params": [1, 2, 3], "id": 2}"#,
|
||||
&Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: json!([1, 2, 3]),
|
||||
id: json!(2),
|
||||
}),
|
||||
);
|
||||
// A notification (with parameters)
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "method": "notif", "params": {"x": "y"}}"#,
|
||||
&Message::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method: "notif".to_owned(),
|
||||
params: json!({"x": "y"}),
|
||||
}),
|
||||
);
|
||||
// A successful response
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "result": 42, "id": 3}"#,
|
||||
&Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(json!(42)),
|
||||
id: json!(3),
|
||||
}),
|
||||
);
|
||||
// A successful response
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "result": null, "id": 3}"#,
|
||||
&Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(Value::Null),
|
||||
id: json!(3),
|
||||
}),
|
||||
);
|
||||
// An error
|
||||
one(
|
||||
r#"{"jsonrpc": "2.0", "error": {"code": 42, "message": "Wrong!"}, "id": null}"#,
|
||||
&Message::Response(Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(RpcError::new(42, "Wrong!".to_owned(), None)),
|
||||
id: Value::Null,
|
||||
}),
|
||||
);
|
||||
// A batch
|
||||
one(
|
||||
r#"[
|
||||
{"jsonrpc": "2.0", "method": "notif"},
|
||||
{"jsonrpc": "2.0", "method": "call", "id": 42}
|
||||
]"#,
|
||||
&Message::Batch(vec![
|
||||
Message::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method: "notif".to_owned(),
|
||||
params: Value::Null,
|
||||
}),
|
||||
Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: Value::Null,
|
||||
id: json!(42),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
// Some handling of broken messages inside a batch
|
||||
let parsed = from_str(
|
||||
r#"[
|
||||
{"jsonrpc": "2.0", "method": "notif"},
|
||||
{"jsonrpc": "2.0", "method": "call", "id": 42},
|
||||
true
|
||||
]"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
Message::Batch(vec![
|
||||
Message::Notification(Notification {
|
||||
jsonrpc: Version,
|
||||
method: "notif".to_owned(),
|
||||
params: Value::Null,
|
||||
}),
|
||||
Message::Request(Request {
|
||||
jsonrpc: Version,
|
||||
method: "call".to_owned(),
|
||||
params: Value::Null,
|
||||
id: json!(42),
|
||||
}),
|
||||
Message::UnmatchedSub(Value::Bool(true)),
|
||||
]),
|
||||
parsed
|
||||
);
|
||||
to_vec(&Message::UnmatchedSub(Value::Null)).unwrap_err();
|
||||
}
|
||||
|
||||
/// A helper for the `broken` test.
|
||||
///
|
||||
/// Check that the given JSON string parses, but is not recognized as a valid RPC message.
|
||||
///
|
||||
/// Test things that are almost but not entirely JSONRPC are rejected.
|
||||
///
|
||||
/// The reject is done by returning it as Unmatched.
|
||||
#[test]
|
||||
fn broken() {
|
||||
// A helper with one test
|
||||
fn one(input: &str) {
|
||||
let msg = from_str(input);
|
||||
match msg {
|
||||
Err(Broken::Unmatched(_)) => (),
|
||||
_ => panic!("{input} recognized as an RPC message: {msg:?}!"),
|
||||
}
|
||||
}
|
||||
|
||||
// Missing the version
|
||||
one(r#"{"method": "notif"}"#);
|
||||
// Wrong version
|
||||
one(r#"{"jsonrpc": 2.0, "method": "notif"}"#);
|
||||
// A response with both result and error
|
||||
one(r#"{"jsonrpc": "2.0", "result": 42, "error": {"code": 42, "message": "!"}, "id": 1}"#);
|
||||
// A response without an id
|
||||
one(r#"{"jsonrpc": "2.0", "result": 42}"#);
|
||||
// An extra field
|
||||
one(r#"{"jsonrpc": "2.0", "method": "weird", "params": 42, "others": 43, "id": 2}"#);
|
||||
// Something completely different
|
||||
one(r#"{"x": [1, 2, 3]}"#);
|
||||
|
||||
match from_str("{]") {
|
||||
Err(Broken::SyntaxError(_)) => (),
|
||||
other => panic!("Something unexpected: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Test some non-trivial aspects of the constructors.
|
||||
///
|
||||
/// This doesn't have a full coverage, because there's not much to actually test there.
|
||||
/// Most of it is related to the ids.
|
||||
#[test]
|
||||
#[ignore = "Not a full coverage test"]
|
||||
fn constructors() {
|
||||
let msg1 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||
let msg2 = Message::request("call".to_owned(), json!([1, 2, 3]));
|
||||
// They differ, even when created with the same parameters
|
||||
assert_ne!(msg1, msg2);
|
||||
// And, specifically, they differ in the ID's
|
||||
let (req1, req2) = if let (Message::Request(req1), Message::Request(req2)) = (msg1, msg2) {
|
||||
assert_ne!(req1.id, req2.id);
|
||||
assert!(req1.id.is_string());
|
||||
assert!(req2.id.is_string());
|
||||
(req1, req2)
|
||||
} else {
|
||||
panic!("Non-request received");
|
||||
};
|
||||
let id1 = req1.id.clone();
|
||||
// When we answer a message, we get the same ID
|
||||
if let Message::Response(resp) = req1.reply(json!([1, 2, 3])) {
|
||||
assert_eq!(
|
||||
resp,
|
||||
Response {
|
||||
jsonrpc: Version,
|
||||
result: Ok(json!([1, 2, 3])),
|
||||
id: id1
|
||||
}
|
||||
);
|
||||
} else {
|
||||
panic!("Not a response");
|
||||
}
|
||||
let id2 = req2.id.clone();
|
||||
// The same with an error
|
||||
if let Message::Response(resp) = req2.error(RpcError::new(42, "Wrong!".to_owned(), None)) {
|
||||
assert_eq!(
|
||||
resp,
|
||||
Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(RpcError::new(42, "Wrong!".to_owned(), None)),
|
||||
id: id2,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
panic!("Not a response");
|
||||
}
|
||||
// When we have unmatched, we generate a top-level error with Null id.
|
||||
if let Message::Response(resp) =
|
||||
Message::error(RpcError::new(43, "Also wrong!".to_owned(), None))
|
||||
{
|
||||
assert_eq!(
|
||||
resp,
|
||||
Response {
|
||||
jsonrpc: Version,
|
||||
result: Err(RpcError::new(43, "Also wrong!".to_owned(), None)),
|
||||
id: Value::Null,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
panic!("Not a response");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use bytesize::ByteSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod errors;
|
||||
pub mod message;
|
||||
pub mod parser;
|
||||
pub mod requests;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RpcLimitsConfig {
|
||||
/// Maximum byte size of the json payload.
|
||||
pub json_payload_max_size: ByteSize,
|
||||
}
|
||||
|
||||
impl Default for RpcLimitsConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
json_payload_max_size: ByteSize::mib(10),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RpcConfig {
|
||||
pub addr: String,
|
||||
pub cors_allowed_origins: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub limits_config: RpcLimitsConfig,
|
||||
}
|
||||
|
||||
impl Default for RpcConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
addr: "0.0.0.0:3040".to_owned(),
|
||||
cors_allowed_origins: vec!["*".to_owned()],
|
||||
limits_config: RpcLimitsConfig::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RpcConfig {
|
||||
#[must_use]
|
||||
pub fn new(addr: &str) -> Self {
|
||||
Self {
|
||||
addr: addr.to_owned(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_port(port: u16) -> Self {
|
||||
Self {
|
||||
addr: format!("0.0.0.0:{port}"),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
|
||||
use super::errors::RpcParseError;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! parse_request {
|
||||
($request_name:ty) => {
|
||||
impl RpcRequest for $request_name {
|
||||
fn parse(value: Option<Value>) -> Result<Self, RpcParseError> {
|
||||
parse_params::<Self>(value)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait RpcRequest: Sized {
|
||||
fn parse(value: Option<Value>) -> Result<Self, RpcParseError>;
|
||||
}
|
||||
|
||||
pub fn parse_params<T: DeserializeOwned>(value: Option<Value>) -> Result<T, RpcParseError> {
|
||||
value.map_or_else(
|
||||
|| Err(RpcParseError("Require at least one parameter".to_owned())),
|
||||
|value| {
|
||||
serde_json::from_value(value)
|
||||
.map_err(|err| RpcParseError(format!("Failed parsing args: {err}")))
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nssa::AccountId;
|
||||
use nssa_core::program::ProgramId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
use super::{
|
||||
errors::RpcParseError,
|
||||
parser::{RpcRequest, parse_params},
|
||||
};
|
||||
use crate::{HashType, parse_request};
|
||||
|
||||
mod base64_deser {
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use serde::{self, Deserialize, Deserializer, Serializer, ser::SerializeSeq as _};
|
||||
|
||||
pub mod vec {
|
||||
use super::*;
|
||||
|
||||
pub fn serialize<S>(bytes_vec: &[Vec<u8>], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut seq = serializer.serialize_seq(Some(bytes_vec.len()))?;
|
||||
for bytes in bytes_vec {
|
||||
let s = general_purpose::STANDARD.encode(bytes);
|
||||
seq.serialize_element(&s)?;
|
||||
}
|
||||
seq.end()
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let base64_strings: Vec<String> = Deserialize::deserialize(deserializer)?;
|
||||
base64_strings
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
general_purpose::STANDARD
|
||||
.decode(&s)
|
||||
.map_err(serde::de::Error::custom)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let base64_string = general_purpose::STANDARD.encode(bytes);
|
||||
serializer.serialize_str(&base64_string)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let base64_string: String = Deserialize::deserialize(deserializer)?;
|
||||
general_purpose::STANDARD
|
||||
.decode(&base64_string)
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HelloRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RegisterAccountRequest {
|
||||
pub account_id: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SendTxRequest {
|
||||
#[serde(with = "base64_deser")]
|
||||
pub transaction: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockDataRequest {
|
||||
pub block_id: u64,
|
||||
}
|
||||
|
||||
/// Get a range of blocks from `start_block_id` to `end_block_id` (inclusive).
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockRangeDataRequest {
|
||||
pub start_block_id: u64,
|
||||
pub end_block_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetGenesisIdRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetLastBlockRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetInitialTestnetAccountsRequest;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountBalanceRequest {
|
||||
pub account_id: AccountId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetTransactionByHashRequest {
|
||||
pub hash: HashType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountsNoncesRequest {
|
||||
pub account_ids: Vec<AccountId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountRequest {
|
||||
pub account_id: AccountId,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProofForCommitmentRequest {
|
||||
pub commitment: nssa_core::Commitment,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProgramIdsRequest;
|
||||
|
||||
parse_request!(HelloRequest);
|
||||
parse_request!(RegisterAccountRequest);
|
||||
parse_request!(SendTxRequest);
|
||||
parse_request!(GetBlockDataRequest);
|
||||
parse_request!(GetBlockRangeDataRequest);
|
||||
parse_request!(GetGenesisIdRequest);
|
||||
parse_request!(GetLastBlockRequest);
|
||||
parse_request!(GetInitialTestnetAccountsRequest);
|
||||
parse_request!(GetAccountBalanceRequest);
|
||||
parse_request!(GetTransactionByHashRequest);
|
||||
parse_request!(GetAccountsNoncesRequest);
|
||||
parse_request!(GetProofForCommitmentRequest);
|
||||
parse_request!(GetAccountRequest);
|
||||
parse_request!(GetProgramIdsRequest);
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct HelloResponse {
|
||||
pub greeting: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RegisterAccountResponse {
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct SendTxResponse {
|
||||
pub status: String,
|
||||
pub tx_hash: HashType,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockDataResponse {
|
||||
#[serde(with = "base64_deser")]
|
||||
pub block: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetBlockRangeDataResponse {
|
||||
#[serde(with = "base64_deser::vec")]
|
||||
pub blocks: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetGenesisIdResponse {
|
||||
pub genesis_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetLastBlockResponse {
|
||||
pub last_block: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountBalanceResponse {
|
||||
pub balance: u128,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountsNoncesResponse {
|
||||
pub nonces: Vec<u128>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetTransactionByHashResponse {
|
||||
pub transaction: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetAccountResponse {
|
||||
pub account: nssa::Account,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProofForCommitmentResponse {
|
||||
pub membership_proof: Option<nssa_core::MembershipProof>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetProgramIdsResponse {
|
||||
pub program_ids: HashMap<String, ProgramId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct GetInitialTestnetAccountsResponse {
|
||||
/// Hex encoded account id.
|
||||
pub account_id: String,
|
||||
pub balance: u64,
|
||||
}
|
||||
@@ -12,6 +12,18 @@ pub enum NSSATransaction {
|
||||
ProgramDeployment(nssa::ProgramDeploymentTransaction),
|
||||
}
|
||||
|
||||
impl Serialize for NSSATransaction {
|
||||
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
crate::borsh_base64::serialize(self, serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for NSSATransaction {
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
crate::borsh_base64::deserialize(deserializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl NSSATransaction {
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> HashType {
|
||||
@@ -90,7 +102,7 @@ impl From<nssa::ProgramDeploymentTransaction> for NSSATransaction {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, BorshSerialize, BorshDeserialize,
|
||||
Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
||||
)]
|
||||
pub enum TxKind {
|
||||
Public,
|
||||
|
||||
Reference in New Issue
Block a user