fix: encode transactions with borsh in sequencer rpc

This commit is contained in:
Daniil Polyakov 2026-03-19 00:42:30 +03:00
parent 7b20a83379
commit 05c2c3a56d
28 changed files with 137 additions and 93 deletions

15
Cargo.lock generated
View File

@ -1498,6 +1498,7 @@ name = "common"
version = "0.1.0"
dependencies = [
"anyhow",
"base64 0.22.1",
"borsh",
"hex",
"log",
@ -5244,7 +5245,6 @@ name = "nssa"
version = "0.1.0"
dependencies = [
"anyhow",
"base64 0.22.1",
"borsh",
"env_logger",
"hex",
@ -7177,23 +7177,30 @@ dependencies = [
"log",
"mempool",
"nssa",
"nssa_core",
"sequencer_core",
"sequencer_service_protocol",
"sequencer_service_rpc",
"tokio",
"tokio-util",
]
[[package]]
name = "sequencer_service_rpc"
name = "sequencer_service_protocol"
version = "0.1.0"
dependencies = [
"common",
"jsonrpsee",
"nssa",
"nssa_core",
]
[[package]]
name = "sequencer_service_rpc"
version = "0.1.0"
dependencies = [
"jsonrpsee",
"sequencer_service_protocol",
]
[[package]]
name = "serde"
version = "1.0.228"

View File

@ -19,6 +19,7 @@ members = [
"programs/token",
"sequencer/core",
"sequencer/service",
"sequencer/service/protocol",
"sequencer/service/rpc",
"indexer/core",
"indexer/service",
@ -43,6 +44,7 @@ mempool = { path = "mempool" }
storage = { path = "storage" }
key_protocol = { path = "key_protocol" }
sequencer_core = { path = "sequencer/core" }
sequencer_service_protocol = { path = "sequencer/service/protocol" }
sequencer_service_rpc = { path = "sequencer/service/rpc" }
sequencer_service = { path = "sequencer/service" }
indexer_core = { path = "indexer/core" }

View File

@ -15,6 +15,7 @@ anyhow.workspace = true
thiserror.workspace = true
serde.workspace = true
serde_with.workspace = true
base64.workspace = true
sha2.workspace = true
log.workspace = true
hex.workspace = true

View File

@ -10,7 +10,7 @@ pub type BlockHash = HashType;
pub type BlockId = u64;
pub type TimeStamp = u64;
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct BlockMeta {
pub id: BlockId,
pub hash: BlockHash,
@ -31,7 +31,7 @@ impl OwnHasher {
}
}
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct BlockHeader {
pub block_id: BlockId,
pub prev_block_hash: BlockHash,
@ -40,19 +40,19 @@ pub struct BlockHeader {
pub signature: nssa::Signature,
}
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct BlockBody {
pub transactions: Vec<NSSATransaction>,
}
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub enum BedrockStatus {
Pending,
Safe,
Finalized,
}
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct Block {
pub header: BlockHeader,
pub body: BlockBody,
@ -60,7 +60,19 @@ pub struct Block {
pub bedrock_parent_id: MantleMsgId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
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,
pub prev_block_hash: BlockHash,

View File

@ -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)
}

View File

@ -4,6 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
use serde_with::{DeserializeFromStr, SerializeDisplay};
pub mod block;
mod borsh_base64;
pub mod config;
pub mod transaction;

View File

@ -5,13 +5,25 @@ use serde::{Deserialize, Serialize};
use crate::HashType;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub enum NSSATransaction {
Public(nssa::PublicTransaction),
PrivacyPreserving(nssa::PrivacyPreservingTransaction),
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 {

View File

@ -19,7 +19,6 @@ sha2.workspace = true
rand.workspace = true
borsh.workspace = true
hex.workspace = true
base64.workspace = true
secp256k1 = "0.31.1"
risc0-binfmt = "3.0.2"
log.workspace = true

View File

@ -1,14 +0,0 @@
use base64::prelude::{BASE64_STANDARD, Engine as _};
use serde::{Deserialize as _, Deserializer, Serialize as _, Serializer};
pub fn serialize<S: Serializer>(v: &[u8], s: S) -> Result<S::Ok, S::Error> {
let base64 = BASE64_STANDARD.encode(v);
String::serialize(&base64, s)
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<u8>, D::Error> {
let base64 = String::deserialize(d)?;
BASE64_STANDARD
.decode(base64.as_bytes())
.map_err(serde::de::Error::custom)
}

View File

@ -18,7 +18,6 @@ pub use public_transaction::PublicTransaction;
pub use signature::{PrivateKey, PublicKey, Signature};
pub use state::V02State;
mod base64;
pub mod encoding;
pub mod error;
mod merkle_tree;

View File

@ -8,7 +8,6 @@ use nssa_core::{
program::{ChainedCall, InstructionData, ProgramId, ProgramOutput},
};
use risc0_zkvm::{ExecutorEnv, InnerReceipt, ProverOpts, Receipt, default_prover};
use serde::{Deserialize, Serialize};
use crate::{
error::NssaError,
@ -18,8 +17,8 @@ use crate::{
};
/// Proof of the privacy preserving execution circuit.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct Proof(#[serde(with = "crate::base64")] pub(crate) Vec<u8>);
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Proof(pub(crate) Vec<u8>);
impl Proof {
#[must_use]

View File

@ -4,14 +4,13 @@ use nssa_core::{
account::{Account, Nonce},
encryption::{Ciphertext, EphemeralPublicKey, ViewingPublicKey},
};
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, Sha256};
use crate::{AccountId, error::NssaError};
pub type ViewTag = u8;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct EncryptedAccountData {
pub ciphertext: Ciphertext,
pub epk: EphemeralPublicKey,
@ -45,7 +44,7 @@ impl EncryptedAccountData {
}
}
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Message {
pub public_account_ids: Vec<AccountId>,
pub nonces: Vec<Nonce>,

View File

@ -8,7 +8,6 @@ use nssa_core::{
Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput,
account::{Account, AccountWithMetadata},
};
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, digest::FixedOutput as _};
use super::{message::Message, witness_set::WitnessSet};
@ -18,7 +17,7 @@ use crate::{
privacy_preserving_transaction::{circuit::Proof, message::EncryptedAccountData},
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct PrivacyPreservingTransaction {
pub message: Message,
pub witness_set: WitnessSet,

View File

@ -1,12 +1,11 @@
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use crate::{
PrivateKey, PublicKey, Signature,
privacy_preserving_transaction::{circuit::Proof, message::Message},
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct WitnessSet {
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
pub(crate) proof: Proof,

View File

@ -1,9 +1,7 @@
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Message {
#[serde(with = "crate::base64")]
pub(crate) bytecode: Vec<u8>,
}

View File

@ -1,13 +1,12 @@
use borsh::{BorshDeserialize, BorshSerialize};
use nssa_core::account::AccountId;
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, digest::FixedOutput as _};
use crate::{
V02State, error::NssaError, program::Program, program_deployment_transaction::message::Message,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct ProgramDeploymentTransaction {
pub message: Message,
}

View File

@ -3,11 +3,11 @@ use nssa_core::{
account::Nonce,
program::{InstructionData, ProgramId},
};
use serde::{Deserialize, Serialize};
use serde::Serialize;
use crate::{AccountId, error::NssaError, program::Program};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Message {
pub program_id: ProgramId,
pub account_ids: Vec<AccountId>,

View File

@ -6,7 +6,6 @@ use nssa_core::{
account::{Account, AccountId, AccountWithMetadata},
program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution},
};
use serde::{Deserialize, Serialize};
use sha2::{Digest as _, digest::FixedOutput as _};
use crate::{
@ -16,7 +15,7 @@ use crate::{
state::MAX_NUMBER_CHAINED_CALLS,
};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct PublicTransaction {
pub message: Message,
pub witness_set: WitnessSet,

View File

@ -1,9 +1,8 @@
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use crate::{PrivateKey, PublicKey, Signature, public_transaction::Message};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct WitnessSet {
pub(crate) signatures_and_public_keys: Vec<(Signature, PublicKey)>,
}

View File

@ -4,14 +4,11 @@ use borsh::{BorshDeserialize, BorshSerialize};
pub use private_key::PrivateKey;
pub use public_key::PublicKey;
use rand::{RngCore as _, rngs::OsRng};
use serde_with::{DeserializeFromStr, SerializeDisplay};
mod private_key;
mod public_key;
#[derive(
Clone, PartialEq, Eq, SerializeDisplay, DeserializeFromStr, BorshSerialize, BorshDeserialize,
)]
#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
pub struct Signature {
pub value: [u8; 64],
}

View File

@ -10,10 +10,9 @@ workspace = true
[dependencies]
common.workspace = true
nssa.workspace = true
nssa_core.workspace = true
mempool.workspace = true
borsh.workspace = true
sequencer_core = { workspace = true, features = ["testnet"] }
sequencer_service_protocol.workspace = true
sequencer_service_rpc = { workspace = true, features = ["server"] }
indexer_service_rpc = { workspace = true, features = ["client"] }
@ -26,6 +25,7 @@ tokio-util.workspace = true
jsonrpsee.workspace = true
futures.workspace = true
bytesize.workspace = true
borsh.workspace = true
[features]
default = []

View File

@ -0,0 +1,13 @@
[package]
name = "sequencer_service_protocol"
version = "0.1.0"
edition = "2024"
license = { workspace = true }
[lints]
workspace = true
[dependencies]
common.workspace = true
nssa.workspace = true
nssa_core.workspace = true

View File

@ -0,0 +1,9 @@
//! Reexports of types used by sequencer rpc specification.
pub use common::{
HashType,
block::{Block, BlockId},
transaction::NSSATransaction,
};
pub use nssa::{Account, AccountId, ProgramId};
pub use nssa_core::{Commitment, MembershipProof, account::Nonce};

View File

@ -8,9 +8,7 @@ license = { workspace = true }
workspace = true
[dependencies]
common.workspace = true
nssa.workspace = true
nssa_core.workspace = true
sequencer_service_protocol.workspace = true
jsonrpsee = { workspace = true, features = ["macros"] }

View File

@ -1,17 +1,14 @@
use std::collections::BTreeMap;
use common::{
HashType,
block::{Block, BlockId},
transaction::NSSATransaction,
};
use jsonrpsee::proc_macros::rpc;
#[cfg(feature = "server")]
use jsonrpsee::types::ErrorObjectOwned;
#[cfg(feature = "client")]
pub use jsonrpsee::{core::ClientError, http_client::HttpClientBuilder as SequencerClientBuilder};
use nssa::{Account, AccountId, ProgramId};
use nssa_core::{Commitment, MembershipProof, account::Nonce};
use sequencer_service_protocol::{
Account, AccountId, Block, BlockId, Commitment, HashType, MembershipProof, NSSATransaction,
Nonce, ProgramId,
};
#[cfg(all(not(feature = "server"), not(feature = "client")))]
compile_error!("At least one of `server` or `client` features must be enabled.");
@ -22,10 +19,14 @@ compile_error!("At least one of `server` or `client` features must be enabled.")
///
/// # Example
///
/// ```ignore
/// use sequencer_service_rpc::{SequencerClientBuilder, RpcClient as _};
/// ```no_run
/// use common::transaction::NSSATransaction;
/// use sequencer_service_rpc::{RpcClient as _, SequencerClientBuilder};
///
/// let url = "http://localhost:3040".parse()?;
/// let client = SequencerClientBuilder::default().build(url)?;
///
/// let tx: NSSATransaction = unimplemented!("Construct your transaction here");
/// let tx_hash = client.send_transaction(tx).await?;
/// ```
#[cfg(feature = "client")]

View File

@ -1,10 +1,6 @@
use std::{collections::BTreeMap, sync::Arc};
use common::{
HashType,
block::{Block, BlockId},
transaction::NSSATransaction,
};
use common::transaction::NSSATransaction;
use jsonrpsee::{
core::async_trait,
types::{ErrorCode, ErrorObjectOwned},
@ -12,15 +8,13 @@ use jsonrpsee::{
use log::warn;
use mempool::MemPoolHandle;
use nssa::{self, program::Program};
use nssa_core::{
Commitment, MembershipProof,
account::{Account, AccountId, Nonce},
program::ProgramId,
};
use sequencer_core::{
DbError, SequencerCore, block_settlement_client::BlockSettlementClientTrait,
indexer_client::IndexerClientTrait,
};
use sequencer_service_protocol::{
Account, AccountId, Block, BlockId, Commitment, HashType, MembershipProof, Nonce, ProgramId,
};
use tokio::sync::Mutex;
const NOT_FOUND_ERROR_CODE: i32 = -31999;
@ -55,12 +49,9 @@ impl<BC: BlockSettlementClientTrait + Send + 'static, IC: IndexerClientTrait + S
let tx_hash = tx.hash();
let tx_size = u64::try_from(
borsh::to_vec(&tx)
.expect("NSSATransaction BorshSerialize should never fail")
.len(),
)
.expect("Transaction size should fit in u64");
let encoded_tx =
borsh::to_vec(&tx).expect("Transaction borsh serialization should not fail");
let tx_size = u64::try_from(encoded_tx.len()).expect("Transaction size should fit in u64");
let max_tx_size = self.max_block_size.saturating_sub(BLOCK_HEADER_OVERHEAD);

View File

@ -25,7 +25,7 @@ impl WalletCore {
let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]);
let tx = nssa::PublicTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_transaction(NSSATransaction::Public(tx)).await?)
Ok(self.sequencer_client.send_transaction(NSSATransaction::Public(tx).into()).await?)
}
pub async fn claim_pinata_private_owned_account_already_initialized(
@ -91,7 +91,7 @@ impl WalletCore {
);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret_winner],
))
}
@ -158,7 +158,7 @@ impl WalletCore {
);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret_winner],
))
}

View File

@ -142,7 +142,7 @@ impl WalletCore {
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret_from, shared_secret_to],
))
}
@ -216,7 +216,7 @@ impl WalletCore {
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret_from, shared_secret_to],
))
}
@ -287,7 +287,7 @@ impl WalletCore {
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret_from, shared_secret_to],
))
}
@ -347,7 +347,7 @@ impl WalletCore {
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret],
))
}
@ -414,7 +414,7 @@ impl WalletCore {
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret],
))
}
@ -480,7 +480,7 @@ impl WalletCore {
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret],
))
}
@ -540,7 +540,7 @@ impl WalletCore {
let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]);
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok(self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?)
Ok(self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?)
}
pub async fn register_account_under_authenticated_transfers_programs_private(
@ -587,7 +587,7 @@ impl WalletCore {
let tx = PrivacyPreservingTransaction::new(message, witness_set);
Ok((
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx)).await?,
self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?,
[shared_secret_from],
))
}