From 6f3605544bfa02f68242e334b4834f62dd9b286f Mon Sep 17 00:00:00 2001 From: Patrick Kuo Date: Mon, 6 Jun 2022 16:04:08 +0100 Subject: [PATCH] [RPC API] - add raw BCS API for RPC (#2376) * add raw bcs get object endpoint * encode bcs bytes in b64 * fixup after rebase * fixup after rebase --- Cargo.lock | 2 + crates/generate-json-rpc-spec/src/main.rs | 2 + crates/sui-core/Cargo.toml | 3 +- crates/sui-core/src/gateway_state.rs | 26 ++- crates/sui-core/src/gateway_types.rs | 163 ++++++++++++------ crates/sui-faucet/src/faucet/mod.rs | 10 +- crates/sui-faucet/src/faucet/simple_faucet.rs | 6 +- crates/sui-gateway/src/api.rs | 11 +- crates/sui-gateway/src/bcs_api.rs | 74 ++++++++ crates/sui-gateway/src/lib.rs | 1 + crates/sui-gateway/src/rpc_gateway.rs | 17 +- .../sui-gateway/src/rpc_gateway/responses.rs | 4 +- crates/sui-gateway/src/rpc_gateway_client.rs | 8 +- crates/sui-node/src/lib.rs | 2 + crates/sui-open-rpc/spec/openrpc.json | 73 +++++++- crates/sui/src/bin/rpc-server.rs | 5 +- crates/sui/src/unit_tests/cli_tests.rs | 6 +- crates/sui/src/wallet_commands.rs | 12 +- crates/workspace-hack/Cargo.toml | 4 +- 19 files changed, 329 insertions(+), 100 deletions(-) create mode 100644 crates/sui-gateway/src/bcs_api.rs diff --git a/Cargo.lock b/Cargo.lock index ea24224576962..b9cc512fbc3a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5258,6 +5258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed" dependencies = [ "dyn-clone", + "either", "schemars_derive", "serde 1.0.137", "serde_json", @@ -5952,6 +5953,7 @@ dependencies = [ "crypto", "curve25519-dalek", "ed25519-dalek", + "either", "executor", "futures", "hex", diff --git a/crates/generate-json-rpc-spec/src/main.rs b/crates/generate-json-rpc-spec/src/main.rs index f913b917c1fc4..52708d70ee4b2 100644 --- a/crates/generate-json-rpc-spec/src/main.rs +++ b/crates/generate-json-rpc-spec/src/main.rs @@ -16,6 +16,7 @@ use sui_core::gateway_types::{ GetObjectDataResponse, SuiObjectInfo, TransactionEffectsResponse, TransactionResponse, }; use sui_gateway::api::SuiRpcModule; +use sui_gateway::bcs_api::BcsApiImpl; use sui_gateway::json_rpc::sui_rpc_doc; use sui_gateway::read_api::{FullNodeApi, ReadApi}; use sui_gateway::rpc_gateway::{GatewayReadApiImpl, RpcGatewayImpl, TransactionBuilderImpl}; @@ -66,6 +67,7 @@ async fn main() { open_rpc.add_module(GatewayReadApiImpl::rpc_doc_module()); open_rpc.add_module(ReadApi::rpc_doc_module()); open_rpc.add_module(FullNodeApi::rpc_doc_module()); + open_rpc.add_module(BcsApiImpl::rpc_doc_module()); match options.action { Action::Print => { diff --git a/crates/sui-core/Cargo.toml b/crates/sui-core/Cargo.toml index 2e5f46f208e1d..88a24ea2471bc 100644 --- a/crates/sui-core/Cargo.toml +++ b/crates/sui-core/Cargo.toml @@ -31,7 +31,8 @@ ed25519-dalek = "1.0.1" scopeguard = "1.1.0" clap = { version = "3.1.17", features = ["derive"] } bincode = "1.3.3" -schemars = "0.8.10" +schemars = {version = "0.8.10", features = ["either"]} +either = "1.6.1" multiaddr = "0.14.0" mysten-network = { git = "https://github.com/MystenLabs/mysten-infra", rev = "ff5c1d69057fe93be658377462ca2875a57a0223" } prometheus_exporter = "0.8.4" diff --git a/crates/sui-core/src/gateway_state.rs b/crates/sui-core/src/gateway_state.rs index 03b0c23c5b370..e49ae93081ddd 100644 --- a/crates/sui-core/src/gateway_state.rs +++ b/crates/sui-core/src/gateway_state.rs @@ -306,6 +306,12 @@ pub trait GatewayAPI { async fn get_object(&self, object_id: ObjectID) -> Result; + /// Get the object data + async fn get_raw_object( + &self, + object_id: ObjectID, + ) -> Result; + /// Get refs of all objects we own from local cache. async fn get_objects_owned_by_address( &self, @@ -366,15 +372,21 @@ where }) } - async fn get_sui_object(&self, object_id: &ObjectID) -> Result { + async fn get_sui_object( + &self, + object_id: &ObjectID, + ) -> Result, anyhow::Error> { let object = self.get_object_internal(object_id).await?; self.to_sui_object(object) } - fn to_sui_object(&self, object: Object) -> Result { + fn to_sui_object( + &self, + object: Object, + ) -> Result, anyhow::Error> { let cache = ModuleCache::new(&*self.store); let layout = object.get_layout(ObjectFormatOptions::default(), &cache)?; - SuiObject::try_from(object, layout) + SuiObject::::try_from(object, layout) } async fn get_object_ref(&self, object_id: &ObjectID) -> SuiResult { @@ -1111,6 +1123,14 @@ where Ok(result.try_into()?) } + async fn get_raw_object( + &self, + object_id: ObjectID, + ) -> Result { + let result = self.download_object_from_authorities(object_id).await?; + Ok(result.try_into()?) + } + async fn get_objects_owned_by_address( &self, account_addr: SuiAddress, diff --git a/crates/sui-core/src/gateway_types.rs b/crates/sui-core/src/gateway_types.rs index 1ef961cf814fe..d2a9441006100 100644 --- a/crates/sui-core/src/gateway_types.rs +++ b/crates/sui-core/src/gateway_types.rs @@ -10,6 +10,7 @@ use std::fmt::Write; use std::fmt::{Display, Formatter}; use colored::Colorize; +use either::Either; use itertools::Itertools; use move_core_types::identifier::Identifier; use move_core_types::language_storage::StructTag; @@ -20,6 +21,8 @@ use serde::Deserialize; use serde::Serialize; use serde_json::Value; +use serde_with::serde_as; +use sui_json::SuiJsonValue; use sui_types::base_types::{ ObjectDigest, ObjectID, ObjectInfo, ObjectRef, SequenceNumber, SuiAddress, TransactionDigest, }; @@ -34,11 +37,9 @@ use sui_types::messages::{ SingleTransactionKind, TransactionData, TransactionEffects, TransactionKind, }; use sui_types::move_package::disassemble_modules; -use sui_types::object::{Data, Object, ObjectRead, Owner}; +use sui_types::object::{Data, MoveObject, Object, ObjectRead, Owner}; use sui_types::sui_serde::{Base64, Encoding}; -use sui_json::SuiJsonValue; - #[cfg(test)] #[path = "unit_tests/gateway_types_tests.rs"] mod gateway_types_tests; @@ -93,11 +94,11 @@ pub struct SplitCoinResponse { /// Certificate of the transaction pub certificate: SuiCertifiedTransaction, /// The updated original coin object after split - pub updated_coin: SuiObject, + pub updated_coin: SuiParsedObject, /// All the newly created coin objects generated from the split - pub new_coins: Vec, + pub new_coins: Vec, /// The updated gas payment object after deducting payment - pub updated_gas: SuiObject, + pub updated_gas: SuiParsedObject, } impl Display for SplitCoinResponse { @@ -131,9 +132,9 @@ pub struct MergeCoinResponse { /// Certificate of the transaction pub certificate: SuiCertifiedTransaction, /// The updated original coin object after merge - pub updated_coin: SuiObject, + pub updated_coin: SuiParsedObject, /// The updated gas payment object after deducting payment - pub updated_gas: SuiObject, + pub updated_gas: SuiParsedObject, } impl Display for MergeCoinResponse { @@ -151,11 +152,14 @@ impl Display for MergeCoinResponse { } } +pub type SuiRawObject = SuiObject; +pub type SuiParsedObject = SuiObject; + #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq)] #[serde(rename_all = "camelCase", rename = "Object")] -pub struct SuiObject { +pub struct SuiObject { /// The meat of the object - pub data: SuiData, + pub data: SuiData, /// The owner that unlocks this object pub owner: Owner, /// The digest of the transaction that created or last mutated this object @@ -194,7 +198,7 @@ impl From for SuiObjectRef { } } -impl Display for SuiObject { +impl Display for SuiParsedObject { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let type_ = if self.data.type_().is_some() { "Move Object" @@ -237,7 +241,7 @@ impl Display for SuiObject { } } -impl SuiObject { +impl SuiObject { pub fn id(&self) -> ObjectID { self.reference.object_id } @@ -248,25 +252,7 @@ impl SuiObject { pub fn try_from(o: Object, layout: Option) -> Result { let oref = o.compute_object_reference(); let data = match o.data { - Data::Move(m) => { - let move_struct = m - .to_move_struct(&layout.ok_or(SuiError::ObjectSerializationError { - error: "Layout is required to convert Move object to json".to_owned(), - })?)? - .into(); - - if let SuiMoveStruct::WithTypes { type_, fields } = move_struct { - SuiData::MoveObject(SuiMoveObject { - type_, - fields: SuiMoveStruct::WithFields(fields), - }) - } else { - SuiData::MoveObject(SuiMoveObject { - type_: m.type_.to_string(), - fields: move_struct, - }) - } - } + Data::Move(m) => SuiData::MoveObject(T::try_from(m, layout)?), Data::Package(p) => SuiData::Package(SuiMovePackage { disassembled: p.disassemble()?, }), @@ -283,12 +269,13 @@ impl SuiObject { #[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)] #[serde(tag = "dataType", rename_all = "camelCase", rename = "Data")] -pub enum SuiData { - MoveObject(SuiMoveObject), +pub enum SuiData { + // Manually handle generic schema generation + MoveObject(#[schemars(with = "Either")] T), Package(SuiMovePackage), } -impl Display for SuiData { +impl Display for SuiData { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let mut writer = String::new(); match self { @@ -316,17 +303,84 @@ fn indent(d: &T, indent: usize) -> String { .join("\n") } +pub trait SuiMoveObject: Sized { + fn try_from( + object: MoveObject, + layout: Option, + ) -> Result; + + fn type_(&self) -> &str; +} + #[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)] #[serde(rename = "MoveObject")] -pub struct SuiMoveObject { +pub struct SuiParsedMoveObject { #[serde(rename = "type")] pub type_: String, pub fields: SuiMoveStruct, } -impl TryFrom<&SuiObject> for GasCoin { +impl SuiMoveObject for SuiParsedMoveObject { + fn try_from( + object: MoveObject, + layout: Option, + ) -> Result { + let move_struct = object + .to_move_struct(&layout.ok_or(SuiError::ObjectSerializationError { + error: "Layout is required to convert Move object to json".to_owned(), + })?)? + .into(); + + Ok( + if let SuiMoveStruct::WithTypes { type_, fields } = move_struct { + SuiParsedMoveObject { + type_, + fields: SuiMoveStruct::WithFields(fields), + } + } else { + SuiParsedMoveObject { + type_: object.type_.to_string(), + fields: move_struct, + } + }, + ) + } + + fn type_(&self) -> &str { + &self.type_ + } +} + +#[serde_as] +#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)] +#[serde(rename = "RawMoveObject")] +pub struct SuiRawMoveObject { + #[serde(rename = "type")] + pub type_: String, + #[serde_as(as = "Base64")] + #[schemars(with = "Base64")] + pub bcs_bytes: Vec, +} + +impl SuiMoveObject for SuiRawMoveObject { + fn try_from( + object: MoveObject, + _layout: Option, + ) -> Result { + Ok(Self { + type_: object.type_.to_string(), + bcs_bytes: object.into_contents(), + }) + } + + fn type_(&self) -> &str { + &self.type_ + } +} + +impl TryFrom<&SuiParsedObject> for GasCoin { type Error = SuiError; - fn try_from(object: &SuiObject) -> Result { + fn try_from(object: &SuiParsedObject) -> Result { match &object.data { SuiData::MoveObject(o) => { if GasCoin::type_().to_string() == o.type_ { @@ -364,8 +418,8 @@ impl TryFrom<&SuiMoveStruct> for GasCoin { } } -impl SuiData { - pub fn try_as_move(&self) -> Option<&SuiMoveObject> { +impl SuiData { + pub fn try_as_move(&self) -> Option<&T> { match self { SuiData::MoveObject(o) => Some(o), SuiData::Package(_) => None, @@ -379,7 +433,7 @@ impl SuiData { } pub fn type_(&self) -> Option<&str> { match self { - SuiData::MoveObject(m) => Some(&m.type_), + SuiData::MoveObject(m) => Some(m.type_()), SuiData::Package(_) => None, } } @@ -393,9 +447,9 @@ pub struct PublishResponse { /// The newly published package object reference. pub package: SuiObjectRef, /// List of Move objects created as part of running the module initializers in the package - pub created_objects: Vec, + pub created_objects: Vec, /// The updated gas payment object after deducting payment - pub updated_gas: SuiObject, + pub updated_gas: SuiParsedObject, } impl Display for PublishResponse { @@ -424,18 +478,21 @@ impl Display for PublishResponse { } } +pub type GetObjectDataResponse = SuiObjectRead; +pub type GetRawObjectDataResponse = SuiObjectRead; + #[derive(Serialize, Deserialize, JsonSchema)] #[serde(tag = "status", content = "details", rename = "ObjectRead")] -pub enum GetObjectDataResponse { - Exists(SuiObject), +pub enum SuiObjectRead { + Exists(SuiObject), NotExists(ObjectID), Deleted(SuiObjectRef), } -impl GetObjectDataResponse { +impl SuiObjectRead { /// Returns a reference to the object if there is any, otherwise an Err if /// the object does not exist or is deleted. - pub fn object(&self) -> Result<&SuiObject, SuiError> { + pub fn object(&self) -> Result<&SuiObject, SuiError> { match &self { Self::Deleted(oref) => Err(SuiError::ObjectDeleted { object_ref: oref.to_object_ref(), @@ -447,7 +504,7 @@ impl GetObjectDataResponse { /// Returns the object value if there is any, otherwise an Err if /// the object does not exist or is deleted. - pub fn into_object(self) -> Result { + pub fn into_object(self) -> Result, SuiError> { match self { Self::Deleted(oref) => Err(SuiError::ObjectDeleted { object_ref: oref.to_object_ref(), @@ -458,16 +515,16 @@ impl GetObjectDataResponse { } } -impl TryFrom for GetObjectDataResponse { +impl TryFrom for SuiObjectRead { type Error = anyhow::Error; fn try_from(value: ObjectRead) -> Result { match value { - ObjectRead::NotExists(id) => Ok(GetObjectDataResponse::NotExists(id)), - ObjectRead::Exists(_, o, layout) => Ok(GetObjectDataResponse::Exists( - SuiObject::try_from(o, layout)?, - )), - ObjectRead::Deleted(oref) => Ok(GetObjectDataResponse::Deleted(oref.into())), + ObjectRead::NotExists(id) => Ok(SuiObjectRead::NotExists(id)), + ObjectRead::Exists(_, o, layout) => { + Ok(SuiObjectRead::Exists(SuiObject::try_from(o, layout)?)) + } + ObjectRead::Deleted(oref) => Ok(SuiObjectRead::Deleted(oref.into())), } } } diff --git a/crates/sui-faucet/src/faucet/mod.rs b/crates/sui-faucet/src/faucet/mod.rs index 9e8103e1e45fd..8ef7b442b9c1f 100644 --- a/crates/sui-faucet/src/faucet/mod.rs +++ b/crates/sui-faucet/src/faucet/mod.rs @@ -3,7 +3,7 @@ use crate::FaucetError; use async_trait::async_trait; use serde::{Deserialize, Serialize}; -use sui_core::gateway_types::SuiObject; +use sui_core::gateway_types::SuiParsedObject; use sui_types::{ base_types::{ObjectID, SuiAddress}, gas_coin::GasCoin, @@ -33,16 +33,16 @@ pub trait Faucet { ) -> Result; } -impl<'a> FromIterator<&'a SuiObject> for FaucetReceipt { - fn from_iter>(iter: T) -> Self { +impl<'a> FromIterator<&'a SuiParsedObject> for FaucetReceipt { + fn from_iter>(iter: T) -> Self { FaucetReceipt { sent: iter.into_iter().map(|o| o.into()).collect(), } } } -impl From<&SuiObject> for CoinInfo { - fn from(v: &SuiObject) -> Self { +impl From<&SuiParsedObject> for CoinInfo { + fn from(v: &SuiParsedObject) -> Self { let gas_coin = GasCoin::try_from(v).unwrap(); Self { amount: gas_coin.value(), diff --git a/crates/sui-faucet/src/faucet/simple_faucet.rs b/crates/sui-faucet/src/faucet/simple_faucet.rs index 93491a8b79e80..b1d68c39d9967 100644 --- a/crates/sui-faucet/src/faucet/simple_faucet.rs +++ b/crates/sui-faucet/src/faucet/simple_faucet.rs @@ -4,7 +4,7 @@ use anyhow::anyhow; use async_trait::async_trait; use sui::wallet_commands::WalletContext; -use sui_core::gateway_types::{SuiExecutionStatus, SuiObject}; +use sui_core::gateway_types::{SuiExecutionStatus, SuiParsedObject}; use sui_types::{ base_types::{ObjectID, SuiAddress}, gas_coin::GasCoin, @@ -66,7 +66,7 @@ impl SimpleFaucet { }) } - async fn get_coins(&self, amounts: &[u64]) -> Result, FaucetError> { + async fn get_coins(&self, amounts: &[u64]) -> Result, FaucetError> { let result = self .split_coins( amounts, @@ -107,7 +107,7 @@ impl SimpleFaucet { gas_object_id: ObjectID, signer: SuiAddress, budget: u64, - ) -> Result, anyhow::Error> { + ) -> Result, anyhow::Error> { // TODO: move this function to impl WalletContext{} and reuse in wallet_commands let context = &self.wallet; let data = context diff --git a/crates/sui-gateway/src/api.rs b/crates/sui-gateway/src/api.rs index ef5c7ac55bdd2..3267e6a5ce900 100644 --- a/crates/sui-gateway/src/api.rs +++ b/crates/sui-gateway/src/api.rs @@ -10,7 +10,8 @@ use serde_with::serde_as; use sui_core::gateway_state::GatewayTxSeqNumber; use sui_core::gateway_types::{ - GetObjectDataResponse, SuiInputObjectKind, SuiObjectInfo, SuiObjectRef, + GetObjectDataResponse, GetRawObjectDataResponse, SuiInputObjectKind, SuiObjectInfo, + SuiObjectRef, }; use sui_core::gateway_types::{TransactionEffectsResponse, TransactionResponse}; use sui_json::SuiJsonValue; @@ -172,6 +173,14 @@ pub trait RpcTransactionBuilder { ) -> RpcResult; } +#[open_rpc(namespace = "sui", tag = "BCS API")] +#[rpc(server, client, namespace = "sui")] +pub trait RpcBcsApi { + /// Return the raw BCS serialised move object bytes for a specified object + #[method(name = "getRawObject")] + async fn get_raw_object(&self, object_id: ObjectID) -> RpcResult; +} + #[serde_as] #[derive(Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] diff --git a/crates/sui-gateway/src/bcs_api.rs b/crates/sui-gateway/src/bcs_api.rs new file mode 100644 index 0000000000000..34cfa0a7ed5e7 --- /dev/null +++ b/crates/sui-gateway/src/bcs_api.rs @@ -0,0 +1,74 @@ +// Copyright (c) 2022, Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::sync::Arc; + +use anyhow::anyhow; +use async_trait::async_trait; +use jsonrpsee_core::server::rpc_module::RpcModule; +use jsonrpsee_core::RpcResult; + +use sui_core::authority::AuthorityState; +use sui_core::gateway_state::GatewayClient; +use sui_core::gateway_types::GetRawObjectDataResponse; +use sui_open_rpc::Module; +use sui_types::base_types::ObjectID; + +use crate::api::RpcBcsApiServer; +use crate::api::SuiRpcModule; + +pub struct BcsApiImpl { + client: ClientStateAdaptor, +} + +impl BcsApiImpl { + pub fn new_with_gateway(client: GatewayClient) -> Self { + Self { + client: ClientStateAdaptor::Gateway(client), + } + } + + pub fn new(client: Arc) -> Self { + Self { + client: ClientStateAdaptor::FullNode(client), + } + } +} + +enum ClientStateAdaptor { + Gateway(GatewayClient), + FullNode(Arc), +} + +impl ClientStateAdaptor { + async fn get_raw_object( + &self, + object_id: ObjectID, + ) -> Result { + match self { + ClientStateAdaptor::Gateway(client) => client.get_raw_object(object_id).await, + ClientStateAdaptor::FullNode(client) => client + .get_object_read(&object_id) + .await + .map_err(|e| anyhow!("{e}"))? + .try_into(), + } + } +} + +#[async_trait] +impl RpcBcsApiServer for BcsApiImpl { + async fn get_raw_object(&self, object_id: ObjectID) -> RpcResult { + Ok(self.client.get_raw_object(object_id).await?) + } +} + +impl SuiRpcModule for BcsApiImpl { + fn rpc(self) -> RpcModule { + self.into_rpc() + } + + fn rpc_doc_module() -> Module { + crate::api::RpcBcsApiOpenRpc::module_doc() + } +} diff --git a/crates/sui-gateway/src/lib.rs b/crates/sui-gateway/src/lib.rs index ae2c02b1b6cf4..56519bda56185 100644 --- a/crates/sui-gateway/src/lib.rs +++ b/crates/sui-gateway/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 pub mod api; +pub mod bcs_api; pub mod config; pub mod json_rpc; pub mod read_api; diff --git a/crates/sui-gateway/src/rpc_gateway.rs b/crates/sui-gateway/src/rpc_gateway.rs index 4b532d12343be..c239b303c3472 100644 --- a/crates/sui-gateway/src/rpc_gateway.rs +++ b/crates/sui-gateway/src/rpc_gateway.rs @@ -11,6 +11,14 @@ use jsonrpsee::core::RpcResult; use jsonrpsee_core::server::rpc_module::RpcModule; use tracing::debug; +use crate::rpc_gateway::responses::SuiTypeTag; +use crate::{ + api::{ + RpcGatewayApiServer, RpcReadApiServer, RpcTransactionBuilderServer, SuiRpcModule, + TransactionBytes, + }, + config::GatewayConfig, +}; use sui_config::PersistedConfig; use sui_core::gateway_state::{GatewayClient, GatewayState, GatewayTxSeqNumber}; use sui_core::gateway_types::{ @@ -26,15 +34,6 @@ use sui_types::{ messages::{Transaction, TransactionData}, }; -use crate::rpc_gateway::responses::SuiTypeTag; -use crate::{ - api::{ - RpcGatewayApiServer, RpcReadApiServer, RpcTransactionBuilderServer, SuiRpcModule, - TransactionBytes, - }, - config::GatewayConfig, -}; - pub mod responses; pub struct RpcGatewayImpl { diff --git a/crates/sui-gateway/src/rpc_gateway/responses.rs b/crates/sui-gateway/src/rpc_gateway/responses.rs index 57b283fc12247..91207a0c11c1d 100644 --- a/crates/sui-gateway/src/rpc_gateway/responses.rs +++ b/crates/sui-gateway/src/rpc_gateway/responses.rs @@ -6,7 +6,7 @@ use move_core_types::parser::parse_type_tag; use schemars::JsonSchema; use serde::Deserialize; use serde::Serialize; -use sui_core::gateway_types::{SuiData, SuiObjectRef}; +use sui_core::gateway_types::{SuiData, SuiObjectRef, SuiParsedMoveObject}; use sui_types::base_types::TransactionDigest; use sui_types::object::Owner; @@ -17,7 +17,7 @@ pub struct ObjectExistsResponse { object_ref: SuiObjectRef, owner: Owner, previous_transaction: TransactionDigest, - data: SuiData, + data: SuiData, } #[derive(Serialize, Deserialize, JsonSchema)] diff --git a/crates/sui-gateway/src/rpc_gateway_client.rs b/crates/sui-gateway/src/rpc_gateway_client.rs index b2bbb9d6032a6..9f4136d451dbd 100644 --- a/crates/sui-gateway/src/rpc_gateway_client.rs +++ b/crates/sui-gateway/src/rpc_gateway_client.rs @@ -9,13 +9,15 @@ use tokio::runtime::Handle; use sui_core::gateway_state::{GatewayAPI, GatewayTxSeqNumber}; use sui_core::gateway_types::{ - GetObjectDataResponse, SuiObjectInfo, TransactionEffectsResponse, TransactionResponse, + GetObjectDataResponse, GetRawObjectDataResponse, SuiObjectInfo, TransactionEffectsResponse, + TransactionResponse, }; use sui_json::SuiJsonValue; use sui_types::base_types::{ObjectID, SuiAddress, TransactionDigest}; use sui_types::messages::{Transaction, TransactionData}; use sui_types::sui_serde::Base64; +use crate::api::RpcBcsApiClient; use crate::api::RpcReadApiClient; use crate::api::RpcTransactionBuilderClient; use crate::api::{RpcGatewayApiClient, TransactionBytes}; @@ -147,6 +149,10 @@ impl GatewayAPI for RpcGatewayClient { Ok(self.client.get_object(object_id).await?) } + async fn get_raw_object(&self, object_id: ObjectID) -> Result { + Ok(self.client.get_raw_object(object_id).await?) + } + async fn get_objects_owned_by_address( &self, address: SuiAddress, diff --git a/crates/sui-node/src/lib.rs b/crates/sui-node/src/lib.rs index df4297f5eb242..0aae0caf2252b 100644 --- a/crates/sui-node/src/lib.rs +++ b/crates/sui-node/src/lib.rs @@ -13,6 +13,7 @@ use sui_core::{ authority_client::NetworkAuthorityClient, checkpoints::CheckpointStore, }; +use sui_gateway::bcs_api::BcsApiImpl; use sui_gateway::json_rpc::JsonRpcServerBuilder; use sui_gateway::read_api::{FullNodeApi, ReadApi}; use sui_network::api::ValidatorServer; @@ -138,6 +139,7 @@ impl SuiNode { let mut server = JsonRpcServerBuilder::new()?; server.register_module(ReadApi::new(state.clone()))?; server.register_module(FullNodeApi::new(state.clone()))?; + server.register_module(BcsApiImpl::new(state.clone()))?; let server_handle = server.start(config.json_rpc_address).await?; Some(server_handle) diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index 8571385490e7b..de788cdb040f2 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -214,6 +214,31 @@ } } }, + { + "name": "sui_getRawObject", + "tags": [ + { + "name": "BCS API" + } + ], + "description": "Return the raw BCS serialised move object bytes for a specified object", + "params": [ + { + "name": "object_id", + "required": true, + "schema": { + "$ref": "#/components/schemas/ObjectID" + } + } + ], + "result": { + "name": "GetRawObjectDataResponse", + "required": true, + "schema": { + "$ref": "#/components/schemas/ObjectRead" + } + } + }, { "name": "sui_getRecentTransactions", "tags": [ @@ -1039,10 +1064,16 @@ "oneOf": [ { "type": "object", + "anyOf": [ + { + "$ref": "#/components/schemas/MoveObject" + }, + { + "$ref": "#/components/schemas/RawMoveObject" + } + ], "required": [ - "dataType", - "fields", - "type" + "dataType" ], "properties": { "dataType": { @@ -1050,12 +1081,6 @@ "enum": [ "moveObject" ] - }, - "fields": { - "$ref": "#/components/schemas/MoveStruct" - }, - "type": { - "type": "string" } } }, @@ -1277,6 +1302,21 @@ } } }, + "MoveObject": { + "type": "object", + "required": [ + "fields", + "type" + ], + "properties": { + "fields": { + "$ref": "#/components/schemas/MoveStruct" + }, + "type": { + "type": "string" + } + } + }, "MovePackage": { "type": "object", "required": [ @@ -1636,6 +1676,21 @@ } } }, + "RawMoveObject": { + "type": "object", + "required": [ + "bcs_bytes", + "type" + ], + "properties": { + "bcs_bytes": { + "$ref": "#/components/schemas/Base64" + }, + "type": { + "type": "string" + } + } + }, "SequenceNumber": { "type": "integer", "format": "uint64", diff --git a/crates/sui/src/bin/rpc-server.rs b/crates/sui/src/bin/rpc-server.rs index 60773d1894736..44666d4db32e0 100644 --- a/crates/sui/src/bin/rpc-server.rs +++ b/crates/sui/src/bin/rpc-server.rs @@ -8,9 +8,11 @@ use std::{ }; use sui_config::sui_config_dir; use sui_config::SUI_GATEWAY_CONFIG; +use sui_gateway::bcs_api::BcsApiImpl; use sui_gateway::rpc_gateway::{create_client, GatewayReadApiImpl, TransactionBuilderImpl}; use sui_gateway::{json_rpc::JsonRpcServerBuilder, rpc_gateway::RpcGatewayImpl}; use tracing::info; + const DEFAULT_RPC_SERVER_PORT: &str = "5001"; const DEFAULT_RPC_SERVER_ADDR_IPV4: &str = "127.0.0.1"; const PROM_PORT_ADDR: &str = "0.0.0.0:9184"; @@ -58,7 +60,8 @@ async fn main() -> anyhow::Result<()> { let mut server = JsonRpcServerBuilder::new()?; server.register_module(RpcGatewayImpl::new(client.clone()))?; server.register_module(GatewayReadApiImpl::new(client.clone()))?; - server.register_module(TransactionBuilderImpl::new(client))?; + server.register_module(TransactionBuilderImpl::new(client.clone()))?; + server.register_module(BcsApiImpl::new_with_gateway(client))?; let server_handle = server.start(address).await?; diff --git a/crates/sui/src/unit_tests/cli_tests.rs b/crates/sui/src/unit_tests/cli_tests.rs index c1ff9eee3fc7e..0b944cbfd5e6c 100644 --- a/crates/sui/src/unit_tests/cli_tests.rs +++ b/crates/sui/src/unit_tests/cli_tests.rs @@ -18,7 +18,7 @@ use sui_config::{ Config, NetworkConfig, PersistedConfig, SUI_FULLNODE_CONFIG, SUI_GATEWAY_CONFIG, SUI_GENESIS_FILENAME, SUI_NETWORK_CONFIG, SUI_WALLET_CONFIG, }; -use sui_core::gateway_types::{GetObjectDataResponse, SuiObject, SuiTransactionEffects}; +use sui_core::gateway_types::{GetObjectDataResponse, SuiParsedObject, SuiTransactionEffects}; use sui_json::SuiJsonValue; use sui_types::{ base_types::{ObjectID, SuiAddress}, @@ -889,11 +889,11 @@ async fn test_active_address_command() -> Result<(), anyhow::Error> { Ok(()) } -fn get_gas_value(o: &SuiObject) -> u64 { +fn get_gas_value(o: &SuiParsedObject) -> u64 { GasCoin::try_from(o).unwrap().value() } -async fn get_object(id: ObjectID, context: &mut WalletContext) -> Option { +async fn get_object(id: ObjectID, context: &mut WalletContext) -> Option { if let GetObjectDataResponse::Exists(o) = context.gateway.get_object(id).await.unwrap() { Some(o) } else { diff --git a/crates/sui/src/wallet_commands.rs b/crates/sui/src/wallet_commands.rs index dc08b02be6afc..94a14bc643c20 100644 --- a/crates/sui/src/wallet_commands.rs +++ b/crates/sui/src/wallet_commands.rs @@ -16,15 +16,13 @@ use move_core_types::{language_storage::TypeTag, parser::parse_type_tag}; use serde::Serialize; use serde_json::json; use sui_core::gateway_types::{ - MergeCoinResponse, PublishResponse, SplitCoinResponse, SuiObjectInfo, + GetObjectDataResponse, MergeCoinResponse, PublishResponse, SplitCoinResponse, SuiObjectInfo, + SuiParsedObject, }; use tracing::info; use sui_core::gateway_state::GatewayClient; -use sui_core::gateway_types::{ - GetObjectDataResponse, SuiCertifiedTransaction, SuiExecutionStatus, SuiObject, - SuiTransactionEffects, -}; +use sui_core::gateway_types::{SuiCertifiedTransaction, SuiExecutionStatus, SuiTransactionEffects}; use sui_framework::build_move_package_to_bytes; use sui_json::SuiJsonValue; use sui_types::object::Owner; @@ -518,7 +516,7 @@ impl WalletContext { pub async fn gas_objects( &self, address: SuiAddress, - ) -> Result, anyhow::Error> { + ) -> Result, anyhow::Error> { let object_refs = self.gateway.get_objects_owned_by_address(address).await?; // TODO: We should ideally fetch the objects from local cache @@ -561,7 +559,7 @@ impl WalletContext { address: SuiAddress, budget: u64, forbidden_gas_objects: BTreeSet, - ) -> Result<(u64, SuiObject), anyhow::Error> { + ) -> Result<(u64, SuiParsedObject), anyhow::Error> { for o in self.gas_objects(address).await.unwrap() { if o.0 >= budget && !forbidden_gas_objects.contains(&o.1.id()) { return Ok(o); diff --git a/crates/workspace-hack/Cargo.toml b/crates/workspace-hack/Cargo.toml index 0fdddb6c4910e..0ca91e1ef0200 100644 --- a/crates/workspace-hack/Cargo.toml +++ b/crates/workspace-hack/Cargo.toml @@ -368,7 +368,7 @@ rustls-pemfile = { version = "1", default-features = false } rustyline = { version = "9", features = ["dirs-next", "with-dirs"] } ryu = { version = "1", default-features = false } same-file = { version = "1", default-features = false } -schemars = { version = "0.8", features = ["derive", "schemars_derive"] } +schemars = { version = "0.8", features = ["derive", "either", "schemars_derive"] } scopeguard = { version = "1", features = ["use_std"] } sct = { version = "0.7", default-features = false } semver-dff4ba8e3ae991db = { package = "semver", version = "1", features = ["serde", "std"] } @@ -916,7 +916,7 @@ rustyline = { version = "9", features = ["dirs-next", "with-dirs"] } rustyline-derive = { version = "0.6", default-features = false } ryu = { version = "1", default-features = false } same-file = { version = "1", default-features = false } -schemars = { version = "0.8", features = ["derive", "schemars_derive"] } +schemars = { version = "0.8", features = ["derive", "either", "schemars_derive"] } schemars_derive = { version = "0.8", default-features = false } scopeguard = { version = "1", features = ["use_std"] } sct = { version = "0.7", default-features = false }