diff --git a/Cargo.lock b/Cargo.lock index 29a3446e858e1..721d0ce59e830 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10390,6 +10390,7 @@ name = "sui-json-rpc" version = "0.0.0" dependencies = [ "anyhow", + "arc-swap", "async-trait", "bcs", "cached", diff --git a/crates/sui-json-rpc/Cargo.toml b/crates/sui-json-rpc/Cargo.toml index ad4dbe60b464b..4a28d673bef64 100644 --- a/crates/sui-json-rpc/Cargo.toml +++ b/crates/sui-json-rpc/Cargo.toml @@ -7,6 +7,7 @@ publish = false edition = "2021" [dependencies] +arc-swap.workspace = true fastcrypto.workspace = true jsonrpsee.workspace = true jsonrpsee-proc-macros.workspace = true @@ -54,3 +55,4 @@ cached.workspace = true [dev-dependencies] mockall.workspace = true expect-test.workspace = true +sui-types = { workspace = true, features = ["test-utils"] } diff --git a/crates/sui-json-rpc/src/authority_state.rs b/crates/sui-json-rpc/src/authority_state.rs new file mode 100644 index 0000000000000..1234f279e527d --- /dev/null +++ b/crates/sui-json-rpc/src/authority_state.rs @@ -0,0 +1,630 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::anyhow; +use arc_swap::Guard; +use async_trait::async_trait; +use move_core_types::language_storage::TypeTag; +use mysten_metrics::spawn_monitored_task; +use std::collections::{BTreeMap, HashMap}; +use std::sync::Arc; +use sui_core::authority::authority_per_epoch_store::AuthorityPerEpochStore; +use sui_core::authority::{AuthorityState, AuthorityStore}; +use sui_core::subscription_handler::SubscriptionHandler; +use sui_json_rpc_types::{ + Coin as SuiCoin, DevInspectResults, DryRunTransactionBlockResponse, EventFilter, SuiEvent, + SuiObjectDataFilter, TransactionFilter, +}; +use sui_storage::indexes::TotalBalance; +use sui_storage::key_value_store::{ + KVStoreCheckpointData, KVStoreTransactionData, TransactionKeyValueStore, + TransactionKeyValueStoreTrait, +}; +use sui_types::base_types::{ + MoveObjectType, ObjectID, ObjectInfo, ObjectRef, SequenceNumber, SuiAddress, +}; +use sui_types::committee::{Committee, EpochId}; +use sui_types::digests::{ChainIdentifier, TransactionDigest, TransactionEventsDigest}; +use sui_types::dynamic_field::DynamicFieldInfo; +use sui_types::effects::TransactionEffects; +use sui_types::error::{SuiError, UserInputError}; +use sui_types::event::EventID; +use sui_types::governance::StakedSui; +use sui_types::messages_checkpoint::{ + CheckpointContents, CheckpointContentsDigest, CheckpointDigest, CheckpointSequenceNumber, + VerifiedCheckpoint, +}; +use sui_types::object::{Object, ObjectRead, PastObjectRead}; +use sui_types::storage::WriteKind; +use sui_types::sui_serde::BigInt; +use sui_types::sui_system_state::SuiSystemState; +use sui_types::transaction::{Transaction, TransactionData, TransactionKind}; +use thiserror::Error; +use tokio::task::JoinError; + +#[cfg(test)] +use mockall::automock; + +use crate::ObjectProvider; + +pub type StateReadResult = Result; + +/// Trait for AuthorityState methods commonly used by at least two api. +#[cfg_attr(test, automock)] +#[async_trait] +pub trait StateRead: Send + Sync { + async fn multi_get( + &self, + transactions: &[TransactionDigest], + effects: &[TransactionDigest], + events: &[TransactionEventsDigest], + ) -> StateReadResult; + + async fn multi_get_checkpoints( + &self, + checkpoint_summaries: &[CheckpointSequenceNumber], + checkpoint_contents: &[CheckpointSequenceNumber], + checkpoint_summaries_by_digest: &[CheckpointDigest], + checkpoint_contents_by_digest: &[CheckpointContentsDigest], + ) -> StateReadResult; + + fn get_object_read(&self, object_id: &ObjectID) -> StateReadResult; + + fn get_past_object_read( + &self, + object_id: &ObjectID, + version: SequenceNumber, + ) -> StateReadResult; + + async fn get_object(&self, object_id: &ObjectID) -> StateReadResult>; + + fn load_epoch_store_one_call_per_task(&self) -> Guard>; + + fn get_dynamic_fields( + &self, + owner: ObjectID, + cursor: Option, + limit: usize, + ) -> StateReadResult>; + + fn get_db(&self) -> Arc; + + fn get_owner_objects( + &self, + owner: SuiAddress, + cursor: Option, + filter: Option, + ) -> StateReadResult>; + + async fn query_events( + &self, + kv_store: &Arc, + query: EventFilter, + // If `Some`, the query will start from the next item after the specified cursor + cursor: Option, + limit: usize, + descending: bool, + ) -> StateReadResult>; + + // transaction_execution_api + #[allow(clippy::type_complexity)] + async fn dry_exec_transaction( + &self, + transaction: TransactionData, + transaction_digest: TransactionDigest, + ) -> StateReadResult<( + DryRunTransactionBlockResponse, + BTreeMap, + TransactionEffects, + Option, + )>; + + async fn dev_inspect_transaction_block( + &self, + sender: SuiAddress, + transaction_kind: TransactionKind, + gas_price: Option, + ) -> StateReadResult; + + // indexer_api + fn get_subscription_handler(&self) -> Arc; + + fn get_owner_objects_with_limit( + &self, + owner: SuiAddress, + cursor: Option, + limit: usize, + filter: Option, + ) -> StateReadResult>; + + async fn get_transactions( + &self, + kv_store: &Arc, + filter: Option, + cursor: Option, + limit: Option, + reverse: bool, + ) -> StateReadResult>; + + fn get_dynamic_field_object_id( + &self, + owner: ObjectID, + name_type: TypeTag, + name_bcs_bytes: &[u8], + ) -> StateReadResult>; + + // governance_api + async fn get_staked_sui(&self, owner: SuiAddress) -> StateReadResult>; + fn get_system_state(&self) -> StateReadResult; + fn get_or_latest_committee(&self, epoch: Option>) -> StateReadResult; + + // coin_api + fn find_publish_txn_digest(&self, package_id: ObjectID) -> StateReadResult; + fn get_owned_coins( + &self, + owner: SuiAddress, + cursor: (String, ObjectID), + limit: usize, + one_coin_type_only: bool, + ) -> StateReadResult>; + async fn get_executed_transaction_and_effects( + &self, + digest: TransactionDigest, + kv_store: Arc, + ) -> StateReadResult<(Transaction, TransactionEffects)>; + async fn get_balance( + &self, + owner: SuiAddress, + coin_type: TypeTag, + ) -> StateReadResult; + async fn get_all_balance( + &self, + owner: SuiAddress, + ) -> StateReadResult>>; + + // read_api + fn get_verified_checkpoint_by_sequence_number( + &self, + sequence_number: CheckpointSequenceNumber, + ) -> StateReadResult; + + fn get_checkpoint_contents( + &self, + digest: CheckpointContentsDigest, + ) -> StateReadResult; + + fn get_verified_checkpoint_summary_by_digest( + &self, + digest: CheckpointDigest, + ) -> StateReadResult; + + fn deprecated_multi_get_transaction_checkpoint( + &self, + digests: &[TransactionDigest], + ) -> StateReadResult>>; + + fn deprecated_get_transaction_checkpoint( + &self, + digest: &TransactionDigest, + ) -> StateReadResult>; + + fn multi_get_checkpoint_by_sequence_number( + &self, + sequence_numbers: &[CheckpointSequenceNumber], + ) -> StateReadResult>>; + + fn get_total_transaction_blocks(&self) -> StateReadResult; + + fn get_checkpoint_by_sequence_number( + &self, + sequence_number: CheckpointSequenceNumber, + ) -> StateReadResult>; + + fn get_latest_checkpoint_sequence_number(&self) -> StateReadResult; + + fn loaded_child_object_versions( + &self, + transaction_digest: &TransactionDigest, + ) -> StateReadResult>>; + + fn get_chain_identifier(&self) -> StateReadResult; +} + +#[async_trait] +impl StateRead for AuthorityState { + async fn multi_get( + &self, + transactions: &[TransactionDigest], + effects: &[TransactionDigest], + events: &[TransactionEventsDigest], + ) -> StateReadResult { + Ok( + ::multi_get( + self, + transactions, + effects, + events, + ) + .await?, + ) + } + + async fn multi_get_checkpoints( + &self, + checkpoint_summaries: &[CheckpointSequenceNumber], + checkpoint_contents: &[CheckpointSequenceNumber], + checkpoint_summaries_by_digest: &[CheckpointDigest], + checkpoint_contents_by_digest: &[CheckpointContentsDigest], + ) -> StateReadResult { + Ok( + ::multi_get_checkpoints( + self, + checkpoint_summaries, + checkpoint_contents, + checkpoint_summaries_by_digest, + checkpoint_contents_by_digest, + ) + .await?, + ) + } + + fn get_object_read(&self, object_id: &ObjectID) -> StateReadResult { + Ok(self.get_object_read(object_id)?) + } + + async fn get_object(&self, object_id: &ObjectID) -> StateReadResult> { + Ok(self.get_object(object_id).await?) + } + + fn get_past_object_read( + &self, + object_id: &ObjectID, + version: SequenceNumber, + ) -> StateReadResult { + Ok(self.get_past_object_read(object_id, version)?) + } + + fn load_epoch_store_one_call_per_task(&self) -> Guard> { + self.load_epoch_store_one_call_per_task() + } + + fn get_dynamic_fields( + &self, + owner: ObjectID, + cursor: Option, + limit: usize, + ) -> StateReadResult> { + Ok(self.get_dynamic_fields(owner, cursor, limit)?) + } + + fn get_db(&self) -> Arc { + self.db() + } + + fn get_owner_objects( + &self, + owner: SuiAddress, + cursor: Option, + filter: Option, + ) -> StateReadResult> { + Ok(self + .get_owner_objects_iterator(owner, cursor, filter)? + .collect()) + } + + async fn query_events( + &self, + kv_store: &Arc, + query: EventFilter, + // If `Some`, the query will start from the next item after the specified cursor + cursor: Option, + limit: usize, + descending: bool, + ) -> StateReadResult> { + Ok(self + .query_events(kv_store, query, cursor, limit, descending) + .await?) + } + + #[allow(clippy::type_complexity)] + async fn dry_exec_transaction( + &self, + transaction: TransactionData, + transaction_digest: TransactionDigest, + ) -> StateReadResult<( + DryRunTransactionBlockResponse, + BTreeMap, + TransactionEffects, + Option, + )> { + Ok(self + .dry_exec_transaction(transaction, transaction_digest) + .await?) + } + + async fn dev_inspect_transaction_block( + &self, + sender: SuiAddress, + transaction_kind: TransactionKind, + gas_price: Option, + ) -> StateReadResult { + Ok(self + .dev_inspect_transaction_block(sender, transaction_kind, gas_price) + .await?) + } + + fn get_subscription_handler(&self) -> Arc { + self.subscription_handler.clone() + } + + fn get_owner_objects_with_limit( + &self, + owner: SuiAddress, + cursor: Option, + limit: usize, + filter: Option, + ) -> StateReadResult> { + Ok(self.get_owner_objects(owner, cursor, limit, filter)?) + } + + async fn get_transactions( + &self, + kv_store: &Arc, + filter: Option, + cursor: Option, + limit: Option, + reverse: bool, + ) -> StateReadResult> { + Ok(self + .get_transactions(kv_store, filter, cursor, limit, reverse) + .await?) + } + + fn get_dynamic_field_object_id( + // indexer + &self, + owner: ObjectID, + name_type: TypeTag, + name_bcs_bytes: &[u8], + ) -> StateReadResult> { + Ok(self.get_dynamic_field_object_id(owner, name_type, name_bcs_bytes)?) + } + + async fn get_staked_sui(&self, owner: SuiAddress) -> StateReadResult> { + Ok(self + .get_move_objects(owner, MoveObjectType::staked_sui()) + .await?) + } + fn get_system_state(&self) -> StateReadResult { + Ok(self.database.get_sui_system_state_object()?) + } + fn get_or_latest_committee(&self, epoch: Option>) -> StateReadResult { + Ok(self + .committee_store() + .get_or_latest_committee(epoch.map(|e| *e))?) + } + + fn find_publish_txn_digest(&self, package_id: ObjectID) -> StateReadResult { + Ok(self.find_publish_txn_digest(package_id)?) + } + fn get_owned_coins( + &self, + owner: SuiAddress, + cursor: (String, ObjectID), + limit: usize, + one_coin_type_only: bool, + ) -> StateReadResult> { + Ok(self + .get_owned_coins_iterator_with_cursor(owner, cursor, limit, one_coin_type_only)? + .map(|(coin_type, coin_object_id, coin)| SuiCoin { + coin_type, + coin_object_id, + version: coin.version, + digest: coin.digest, + balance: coin.balance, + previous_transaction: coin.previous_transaction, + }) + .collect::>()) + } + + async fn get_executed_transaction_and_effects( + &self, + digest: TransactionDigest, + kv_store: Arc, + ) -> StateReadResult<(Transaction, TransactionEffects)> { + Ok(self + .get_executed_transaction_and_effects(digest, kv_store) + .await?) + } + + async fn get_balance( + &self, + owner: SuiAddress, + coin_type: TypeTag, + ) -> StateReadResult { + Ok(self + .indexes + .as_ref() + .ok_or(SuiError::IndexStoreNotAvailable)? + .get_balance(owner, coin_type) + .await?) + } + + async fn get_all_balance( + &self, + owner: SuiAddress, + ) -> StateReadResult>> { + Ok(self + .indexes + .as_ref() + .ok_or(SuiError::IndexStoreNotAvailable)? + .get_all_balance(owner) + .await?) + } + + fn get_verified_checkpoint_by_sequence_number( + &self, + sequence_number: CheckpointSequenceNumber, + ) -> StateReadResult { + Ok(self.get_verified_checkpoint_by_sequence_number(sequence_number)?) + } + + fn get_checkpoint_contents( + &self, + digest: CheckpointContentsDigest, + ) -> StateReadResult { + Ok(self.get_checkpoint_contents(digest)?) + } + + fn get_verified_checkpoint_summary_by_digest( + &self, + digest: CheckpointDigest, + ) -> StateReadResult { + Ok(self.get_verified_checkpoint_summary_by_digest(digest)?) + } + + fn deprecated_multi_get_transaction_checkpoint( + &self, + digests: &[TransactionDigest], + ) -> StateReadResult>> { + Ok(self + .database + .deprecated_multi_get_transaction_checkpoint(digests)?) + } + + fn deprecated_get_transaction_checkpoint( + &self, + digest: &TransactionDigest, + ) -> StateReadResult> { + Ok(self + .database + .deprecated_get_transaction_checkpoint(digest)?) + } + + fn multi_get_checkpoint_by_sequence_number( + &self, + sequence_numbers: &[CheckpointSequenceNumber], + ) -> StateReadResult>> { + Ok(self.multi_get_checkpoint_by_sequence_number(sequence_numbers)?) + } + + fn get_total_transaction_blocks(&self) -> StateReadResult { + Ok(self.get_total_transaction_blocks()?) + } + + fn get_checkpoint_by_sequence_number( + &self, + sequence_number: CheckpointSequenceNumber, + ) -> StateReadResult> { + Ok(self.get_checkpoint_by_sequence_number(sequence_number)?) + } + + fn get_latest_checkpoint_sequence_number(&self) -> StateReadResult { + Ok(self.get_latest_checkpoint_sequence_number()?) + } + + fn loaded_child_object_versions( + &self, + transaction_digest: &TransactionDigest, + ) -> StateReadResult>> { + Ok(self.loaded_child_object_versions(transaction_digest)?) + } + + fn get_chain_identifier(&self) -> StateReadResult { + Ok(self + .get_chain_identifier() + .ok_or(anyhow!("Chain identifier not found"))?) + } +} + +/// This implementation allows `S` to be a dynamically sized type (DST) that implements ObjectProvider +/// Valid as `S` is referenced only, and memory management is handled by `Arc` +#[async_trait] +impl ObjectProvider for Arc { + type Error = StateReadError; + + async fn get_object( + &self, + id: &ObjectID, + version: &SequenceNumber, + ) -> Result { + Ok(self.get_past_object_read(id, *version)?.into_object()?) + } + + async fn find_object_lt_or_eq_version( + &self, + id: &ObjectID, + version: &SequenceNumber, + ) -> Result, Self::Error> { + let database = self.get_db(); + let id = *id; + let version = *version; + spawn_monitored_task!(async move { database.find_object_lt_or_eq_version(id, version) }) + .await + .map_err(StateReadError::from) + } +} + +#[derive(Debug, Error)] +pub enum StateReadInternalError { + #[error(transparent)] + SuiError(#[from] SuiError), + #[error(transparent)] + JoinError(#[from] JoinError), + #[error(transparent)] + Anyhow(#[from] anyhow::Error), +} + +#[derive(Debug, Error)] +pub enum StateReadClientError { + #[error(transparent)] + SuiError(#[from] SuiError), + #[error(transparent)] + UserInputError(#[from] UserInputError), +} + +/// `StateReadError` is the error type for callers to work with. +/// It captures all possible errors that can occur while reading state, classifying them into two categories. +/// Unless `StateReadError` is the final error state before returning to caller, the app may still want error context. +/// This context is preserved in `Internal` and `Client` variants. +#[derive(Debug, Error)] +pub enum StateReadError { + // sui_json_rpc::Error will do the final conversion to generic error message + #[error(transparent)] + Internal(#[from] StateReadInternalError), + + // Client errors + #[error(transparent)] + Client(#[from] StateReadClientError), +} + +impl From for StateReadError { + fn from(e: SuiError) -> Self { + match e { + SuiError::IndexStoreNotAvailable + | SuiError::TransactionNotFound { .. } + | SuiError::UnsupportedFeatureError { .. } + | SuiError::UserInputError { .. } + | SuiError::WrongMessageVersion { .. } => StateReadError::Client(e.into()), + _ => StateReadError::Internal(e.into()), + } + } +} + +impl From for StateReadError { + fn from(e: UserInputError) -> Self { + StateReadError::Client(e.into()) + } +} + +impl From for StateReadError { + fn from(e: JoinError) -> Self { + StateReadError::Internal(e.into()) + } +} + +impl From for StateReadError { + fn from(e: anyhow::Error) -> Self { + StateReadError::Internal(e.into()) + } +} diff --git a/crates/sui-json-rpc/src/balance_changes.rs b/crates/sui-json-rpc/src/balance_changes.rs index f8581b2453c6c..91b4b3011edaf 100644 --- a/crates/sui-json-rpc/src/balance_changes.rs +++ b/crates/sui-json-rpc/src/balance_changes.rs @@ -3,20 +3,16 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::ops::Neg; -use std::sync::Arc; use async_trait::async_trait; use move_core_types::language_storage::TypeTag; -use mysten_metrics::spawn_monitored_task; use tokio::sync::RwLock; -use sui_core::authority::AuthorityState; use sui_json_rpc_types::BalanceChange; use sui_types::base_types::{ObjectID, ObjectRef, SequenceNumber}; use sui_types::coin::Coin; use sui_types::digests::ObjectDigest; use sui_types::effects::{TransactionEffects, TransactionEffectsAPI}; -use sui_types::error::SuiError; use sui_types::execution_status::ExecutionStatus; use sui_types::gas_coin::GAS; use sui_types::object::{Object, Owner}; @@ -171,31 +167,6 @@ pub trait ObjectProvider { ) -> Result, Self::Error>; } -#[async_trait] -impl ObjectProvider for Arc { - type Error = SuiError; - async fn get_object( - &self, - id: &ObjectID, - version: &SequenceNumber, - ) -> Result { - Ok(self.get_past_object_read(id, *version)?.into_object()?) - } - - async fn find_object_lt_or_eq_version( - &self, - id: &ObjectID, - version: &SequenceNumber, - ) -> Result, Self::Error> { - let database = self.database.clone(); - let id = *id; - let version = *version; - spawn_monitored_task!(async move { database.find_object_lt_or_eq_version(id, version) }) - .await - .map_err(|e| SuiError::GenericStorageError(e.to_string())) - } -} - pub struct ObjectProviderCache

{ object_cache: RwLock>, last_version_cache: RwLock>, diff --git a/crates/sui-json-rpc/src/coin_api.rs b/crates/sui-json-rpc/src/coin_api.rs index 39113c79814ae..7e5ea91e3bacb 100644 --- a/crates/sui-json-rpc/src/coin_api.rs +++ b/crates/sui-json-rpc/src/coin_api.rs @@ -11,40 +11,31 @@ use jsonrpsee::core::RpcResult; use jsonrpsee::RpcModule; use move_core_types::language_storage::{StructTag, TypeTag}; use sui_storage::indexes::TotalBalance; -use sui_types::digests::TransactionDigest; -use sui_types::transaction::Transaction; use tap::TapFallible; use tracing::{debug, info, instrument}; use mysten_metrics::spawn_monitored_task; use sui_core::authority::AuthorityState; -use sui_json_rpc_types::{Balance, Coin as SuiCoin}; +use sui_json_rpc_types::Balance; use sui_json_rpc_types::{CoinPage, SuiCoinMetadata}; use sui_open_rpc::Module; -use sui_storage::key_value_store::{ - KVStoreCheckpointData, KVStoreTransactionData, TransactionKeyValueStore, - TransactionKeyValueStoreTrait, -}; +use sui_storage::key_value_store::TransactionKeyValueStore; use sui_types::balance::Supply; use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::coin::{CoinMetadata, TreasuryCap}; -use sui_types::digests::TransactionEventsDigest; -use sui_types::effects::{TransactionEffects, TransactionEffectsAPI}; -use sui_types::error::{SuiError, SuiResult}; +use sui_types::effects::TransactionEffectsAPI; use sui_types::gas_coin::GAS; -use sui_types::messages_checkpoint::{ - CheckpointContentsDigest, CheckpointDigest, CheckpointSequenceNumber, -}; -use sui_types::object::{Object, ObjectRead}; +use sui_types::object::Object; use sui_types::parse_sui_struct_tag; +#[cfg(test)] +use mockall::automock; + use crate::api::{cap_page_limit, CoinReadApiServer, JsonRpcMetrics}; +use crate::authority_state::StateRead; use crate::error::{Error, RpcInterimResult, SuiRpcInputError}; use crate::{with_tracing, SuiRpcModule}; -#[cfg(test)] -use mockall::automock; - fn parse_to_struct_tag(coin_type: &str) -> Result { parse_sui_struct_tag(coin_type) .map_err(|e| SuiRpcInputError::CannotParseSuiStructTag(format!("{e}"))) @@ -60,7 +51,6 @@ fn parse_to_type_tag(coin_type: Option) -> Result, - transaction_kv_store: Arc, } impl CoinReadApi { @@ -70,14 +60,13 @@ impl CoinReadApi { metrics: Arc, ) -> Self { Self { - internal: Box::new(CoinReadInternalImpl::new(state, metrics)), - transaction_kv_store, + internal: Box::new(CoinReadInternalImpl::new( + state, + transaction_kv_store, + metrics, + )), } } - - pub fn get_kv_store(&self) -> Arc { - self.transaction_kv_store.clone() - } } impl SuiRpcModule for CoinReadApi { @@ -218,7 +207,6 @@ impl CoinReadApiServer for CoinReadApi { .find_package_object( &coin_struct.address.into(), CoinMetadata::type_(coin_struct), - self.get_kv_store(), ) .await .ok(); @@ -238,7 +226,6 @@ impl CoinReadApiServer for CoinReadApi { .find_package_object( &coin_struct.address.into(), TreasuryCap::type_(coin_struct), - self.get_kv_store(), ) .await?; let treasury_cap = TreasuryCap::from_bcs_bytes( @@ -251,143 +238,6 @@ impl CoinReadApiServer for CoinReadApi { } } -/// State trait to capture subset of AuthorityState used by CoinReadApi -/// This allows us to also mock AuthorityState for testing -#[cfg_attr(test, automock)] -#[async_trait] -pub trait State { - fn get_object_read(&self, object_id: &ObjectID) -> SuiResult; - async fn get_object(&self, object_id: &ObjectID) -> SuiResult>; - fn find_publish_txn_digest(&self, package_id: ObjectID) -> SuiResult; - fn get_owned_coins( - &self, - owner: SuiAddress, - cursor: (String, ObjectID), - limit: usize, - one_coin_type_only: bool, - ) -> SuiResult>; - async fn get_executed_transaction_and_effects( - &self, - digest: TransactionDigest, - kv_store: Arc, - ) -> SuiResult<(Transaction, TransactionEffects)>; - async fn get_balance(&self, owner: SuiAddress, coin_type: TypeTag) -> SuiResult; - async fn get_all_balance( - &self, - owner: SuiAddress, - ) -> SuiResult>>; - async fn multi_get( - &self, - transactions: &[TransactionDigest], - effects: &[TransactionDigest], - events: &[TransactionEventsDigest], - ) -> SuiResult; - async fn multi_get_checkpoints( - &self, - checkpoint_summaries: &[CheckpointSequenceNumber], - checkpoint_contents: &[CheckpointSequenceNumber], - checkpoint_summaries_by_digest: &[CheckpointDigest], - checkpoint_contents_by_digest: &[CheckpointContentsDigest], - ) -> SuiResult; -} - -pub trait TransactionKeyValueStoreTraitWithState: TransactionKeyValueStoreTrait + State {} - -#[async_trait] -impl State for AuthorityState { - fn get_object_read(&self, object_id: &ObjectID) -> SuiResult { - self.get_object_read(object_id) - } - - async fn get_object(&self, object_id: &ObjectID) -> SuiResult> { - self.get_object(object_id).await - } - - fn find_publish_txn_digest(&self, package_id: ObjectID) -> SuiResult { - self.find_publish_txn_digest(package_id) - } - - fn get_owned_coins( - &self, - owner: SuiAddress, - cursor: (String, ObjectID), - limit: usize, - one_coin_type_only: bool, - ) -> SuiResult> { - Ok(self - .get_owned_coins_iterator_with_cursor(owner, cursor, limit, one_coin_type_only)? - .map(|(coin_type, coin_object_id, coin)| SuiCoin { - coin_type, - coin_object_id, - version: coin.version, - digest: coin.digest, - balance: coin.balance, - previous_transaction: coin.previous_transaction, - }) - .collect::>()) - } - - async fn get_executed_transaction_and_effects( - &self, - digest: TransactionDigest, - kv_store: Arc, - ) -> SuiResult<(Transaction, TransactionEffects)> { - self.get_executed_transaction_and_effects(digest, kv_store) - .await - } - - async fn get_balance(&self, owner: SuiAddress, coin_type: TypeTag) -> SuiResult { - self.indexes - .as_ref() - .ok_or(SuiError::IndexStoreNotAvailable)? - .get_balance(owner, coin_type) - .await - } - - async fn get_all_balance( - &self, - owner: SuiAddress, - ) -> SuiResult>> { - self.indexes - .as_ref() - .ok_or(SuiError::IndexStoreNotAvailable)? - .get_all_balance(owner) - .await - } - - async fn multi_get( - &self, - transactions: &[TransactionDigest], - effects: &[TransactionDigest], - events: &[TransactionEventsDigest], - ) -> SuiResult { - ::multi_get( - self, - transactions, - effects, - events, - ) - .await - } - - async fn multi_get_checkpoints( - &self, - checkpoint_summaries: &[CheckpointSequenceNumber], - checkpoint_contents: &[CheckpointSequenceNumber], - checkpoint_summaries_by_digest: &[CheckpointDigest], - checkpoint_contents_by_digest: &[CheckpointContentsDigest], - ) -> SuiResult { - ::multi_get_checkpoints( - self, - checkpoint_summaries, - checkpoint_contents, - checkpoint_summaries_by_digest, - checkpoint_contents_by_digest, - ) - .await - } -} - #[cached( type = "SizedCache", create = "{ SizedCache::with_size(10000) }", @@ -395,7 +245,7 @@ impl State for AuthorityState { result = true )] async fn find_package_object_id( - state: Arc, + state: Arc, package_id: ObjectID, object_struct_tag: StructTag, kv_store: Arc, @@ -430,7 +280,7 @@ async fn find_package_object_id( #[cfg_attr(test, automock)] #[async_trait] pub trait CoinReadInternal { - fn get_state(&self) -> Arc; + fn get_state(&self) -> Arc; async fn get_object(&self, object_id: &ObjectID) -> RpcInterimResult>; async fn get_balance( &self, @@ -445,7 +295,6 @@ pub trait CoinReadInternal { &self, package_id: &ObjectID, object_struct_tag: StructTag, - kv_store: Arc, ) -> RpcInterimResult; async fn get_coins_iterator( &self, @@ -458,19 +307,28 @@ pub trait CoinReadInternal { pub struct CoinReadInternalImpl { // Trait object w/ Arc as we have methods that require sharing this across multiple threads - state: Arc, + state: Arc, + transaction_kv_store: Arc, pub metrics: Arc, } impl CoinReadInternalImpl { - pub fn new(state: Arc, metrics: Arc) -> Self { - Self { state, metrics } + pub fn new( + state: Arc, + transaction_kv_store: Arc, + metrics: Arc, + ) -> Self { + Self { + state, + transaction_kv_store, + metrics, + } } } #[async_trait] impl CoinReadInternal for CoinReadInternalImpl { - fn get_state(&self) -> Arc { + fn get_state(&self) -> Arc { self.state.clone() } @@ -497,9 +355,9 @@ impl CoinReadInternal for CoinReadInternalImpl { &self, package_id: &ObjectID, object_struct_tag: StructTag, - kv_store: Arc, ) -> RpcInterimResult { let state = self.get_state(); + let kv_store = self.transaction_kv_store.clone(); let object_id = find_package_object_id(state, *package_id, object_struct_tag, kv_store).await?; Ok(self.state.get_object_read(&object_id)?.into_object()?) @@ -539,74 +397,81 @@ impl CoinReadInternal for CoinReadInternalImpl { #[cfg(test)] mod tests { use super::*; + use crate::authority_state::{MockStateRead, StateReadError}; use expect_test::expect; use jsonrpsee::types::ErrorObjectOwned; + use mockall::mock; use mockall::predicate; use move_core_types::account_address::AccountAddress; use move_core_types::language_storage::StructTag; use sui_json_rpc_types::Coin; + use sui_storage::key_value_store::{ + KVStoreCheckpointData, KVStoreTransactionData, TransactionKeyValueStoreTrait, + }; use sui_storage::key_value_store_metrics::KeyValueStoreMetrics; use sui_types::balance::Supply; use sui_types::base_types::{ObjectID, SequenceNumber, SuiAddress}; use sui_types::coin::TreasuryCap; - use sui_types::digests::{ObjectDigest, TransactionDigest}; + use sui_types::digests::{ObjectDigest, TransactionDigest, TransactionEventsDigest}; + use sui_types::effects::TransactionEffects; use sui_types::effects::TransactionEffectsV1; + use sui_types::error::{SuiError, SuiResult}; use sui_types::gas_coin::GAS; use sui_types::id::UID; + use sui_types::messages_checkpoint::{ + CheckpointContentsDigest, CheckpointDigest, CheckpointSequenceNumber, + }; use sui_types::object::Object; use sui_types::utils::create_fake_transaction; use sui_types::{parse_sui_struct_tag, TypeTag}; - - #[async_trait] - impl TransactionKeyValueStoreTrait for MockState { - async fn multi_get( - &self, - transactions: &[TransactionDigest], - effects: &[TransactionDigest], - events: &[TransactionEventsDigest], - ) -> SuiResult { - ::multi_get(self, transactions, effects, events).await - } - - async fn multi_get_checkpoints( - &self, - checkpoint_summaries: &[CheckpointSequenceNumber], - checkpoint_contents: &[CheckpointSequenceNumber], - checkpoint_summaries_by_digest: &[CheckpointDigest], - checkpoint_contents_by_digest: &[CheckpointContentsDigest], - ) -> SuiResult { - ::multi_get_checkpoints( - self, - checkpoint_summaries, - checkpoint_contents, - checkpoint_summaries_by_digest, - checkpoint_contents_by_digest, - ) - .await + use typed_store::TypedStoreError; + + mock! { + pub KeyValueStore {} + #[async_trait] + impl TransactionKeyValueStoreTrait for KeyValueStore { + async fn multi_get( + &self, + transactions: &[TransactionDigest], + effects: &[TransactionDigest], + events: &[TransactionEventsDigest], + ) -> SuiResult; + + async fn multi_get_checkpoints( + &self, + checkpoint_summaries: &[CheckpointSequenceNumber], + checkpoint_contents: &[CheckpointSequenceNumber], + checkpoint_summaries_by_digest: &[CheckpointDigest], + checkpoint_contents_by_digest: &[CheckpointContentsDigest], + ) -> SuiResult; } } impl CoinReadInternalImpl { - pub fn new_for_tests(state: Arc) -> Self { + pub fn new_for_tests( + state: Arc, + kv_store: Option>, + ) -> Self { + let kv_store = kv_store.unwrap_or_else(|| Arc::new(MockKeyValueStore::new())); + let metrics = KeyValueStoreMetrics::new_for_tests(); + let transaction_kv_store = + Arc::new(TransactionKeyValueStore::new("rocksdb", metrics, kv_store)); Self { state, + transaction_kv_store, metrics: Arc::new(JsonRpcMetrics::new_for_tests()), } } } impl CoinReadApi { - pub fn new_for_tests(state: Arc) -> Self { - let metrics = KeyValueStoreMetrics::new_for_tests(); - let transaction_kv_store = Arc::new(TransactionKeyValueStore::new( - "rocksdb", - metrics, - state.clone(), - )); - + pub fn new_for_tests( + state: Arc, + kv_store: Option>, + ) -> Self { + let kv_store = kv_store.unwrap_or_else(|| Arc::new(MockKeyValueStore::new())); Self { - internal: Box::new(CoinReadInternalImpl::new_for_tests(state)), - transaction_kv_store, + internal: Box::new(CoinReadInternalImpl::new_for_tests(state, Some(kv_store))), } } } @@ -684,7 +549,6 @@ mod tests { use super::super::*; use super::*; use jsonrpsee::types::ErrorObjectOwned; - use typed_store::TypedStoreError; // Success scenarios #[tokio::test] @@ -692,7 +556,7 @@ mod tests { let owner = get_test_owner(); let gas_coin = get_test_coin(None, CoinType::Gas); let gas_coin_clone = gas_coin.clone(); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .with( @@ -703,7 +567,7 @@ mod tests { ) .return_once(move |_, _, _, _| Ok(vec![gas_coin_clone])); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api.get_coins(owner, None, None, None).await; assert!(response.is_ok()); let result = response.unwrap(); @@ -727,7 +591,7 @@ mod tests { get_test_coin(Some("0xAAA"), CoinType::Gas), ]; let coins_clone = coins.clone(); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .with( @@ -738,7 +602,7 @@ mod tests { ) .return_once(move |_, _, _, _| Ok(coins_clone)); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coins(owner, None, Some(coins[0].coin_object_id), Some(limit)) .await; @@ -764,7 +628,7 @@ mod tests { let coin_type_tag = TypeTag::Struct(Box::new(parse_sui_struct_tag(&coin.coin_type).unwrap())); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .with( @@ -775,7 +639,7 @@ mod tests { ) .return_once(move |_, _, _, _| Ok(vec![coin_clone])); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coins(owner, Some(coin_type), None, None) .await; @@ -808,7 +672,7 @@ mod tests { let coin_type_tag = TypeTag::Struct(Box::new(parse_sui_struct_tag(&coins[0].coin_type).unwrap())); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .with( @@ -819,7 +683,7 @@ mod tests { ) .return_once(move |_, _, _, _| Ok(coins_clone)); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coins(owner, Some(coin_type), Some(cursor), Some(limit)) .await; @@ -841,8 +705,8 @@ mod tests { async fn test_invalid_coin_type() { let owner = get_test_owner(); let coin_type = "0x2::invalid::struct::tag"; - let mock_state = MockState::new(); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let mock_state = MockStateRead::new(); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coins(owner, Some(coin_type.to_string()), None, None) .await; @@ -860,8 +724,8 @@ mod tests { async fn test_unrecognized_token() { let owner = get_test_owner(); let coin_type = "0x2::sui:🤵"; - let mock_state = MockState::new(); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let mock_state = MockStateRead::new(); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coins(owner, Some(coin_type.to_string()), None, None) .await; @@ -881,11 +745,15 @@ mod tests { async fn test_get_coins_iterator_index_store_not_available() { let owner = get_test_owner(); let coin_type = get_test_coin_type(get_test_package_id()); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() - .returning(move |_, _, _, _| Err(SuiError::IndexStoreNotAvailable)); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + .returning(move |_, _, _, _| { + Err(StateReadError::Client( + SuiError::IndexStoreNotAvailable.into(), + )) + }); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coins(owner, Some(coin_type.to_string()), None, None) .await; @@ -895,7 +763,7 @@ mod tests { let error_object: ErrorObjectOwned = error_result.into(); assert_eq!( error_object.code(), - jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE + jsonrpsee::types::error::INVALID_PARAMS_CODE ); let expected = expect!["Index store not available on this Fullnode."]; expected.assert_eq(error_object.message()); @@ -905,13 +773,16 @@ mod tests { async fn test_get_coins_iterator_typed_store_error() { let owner = get_test_owner(); let coin_type = get_test_coin_type(get_test_package_id()); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .returning(move |_, _, _, _| { - Err(TypedStoreError::RocksDBError("mock rocksdb error".to_string()).into()) + Err(SuiError::StorageError(TypedStoreError::RocksDBError( + "mock rocksdb error".to_string(), + )) + .into()) }); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coins(owner, Some(coin_type.to_string()), None, None) .await; @@ -921,7 +792,7 @@ mod tests { let error_object: ErrorObjectOwned = error_result.into(); assert_eq!( error_object.code(), - jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE + jsonrpsee::types::error::INTERNAL_ERROR_CODE ); let expected = expect!["Storage error"]; expected.assert_eq(error_object.message()); @@ -940,7 +811,7 @@ mod tests { let owner = get_test_owner(); let gas_coin = get_test_coin(None, CoinType::Gas); let gas_coin_clone = gas_coin.clone(); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .with( @@ -950,7 +821,7 @@ mod tests { predicate::eq(false), ) .return_once(move |_, _, _, _| Ok(vec![gas_coin_clone])); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_all_coins(owner, None, Some(51)) .await @@ -979,7 +850,7 @@ mod tests { Owner::Immutable, coins[0].previous_transaction, ); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_object() .return_once(move |_| Ok(Some(coin_object))); @@ -992,7 +863,7 @@ mod tests { predicate::eq(false), ) .return_once(move |_, _, _, _| Ok(coins_clone)); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_all_coins(owner, Some(coins[0].coin_object_id), Some(limit)) .await @@ -1007,7 +878,7 @@ mod tests { let owner = get_test_owner(); let object_id = get_test_package_id(); let (_, _, _, _, treasury_cap_object) = get_test_treasury_cap_peripherals(object_id); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state.expect_get_object().returning(move |obj_id| { if obj_id == &object_id { Ok(Some(treasury_cap_object.clone())) @@ -1015,7 +886,7 @@ mod tests { panic!("should not be called with any other object id") } }); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_all_coins(owner, Some(object_id), None) .await; @@ -1034,10 +905,10 @@ mod tests { async fn test_object_not_found() { let owner = get_test_owner(); let object_id = get_test_package_id(); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state.expect_get_object().returning(move |_| Ok(None)); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_all_coins(owner, Some(object_id), None) .await; @@ -1062,7 +933,7 @@ mod tests { let owner = get_test_owner(); let gas_coin = get_test_coin(None, CoinType::Gas); let gas_coin_clone = gas_coin.clone(); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_balance() .with( @@ -1075,7 +946,7 @@ mod tests { num_coins: 9, }) }); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api.get_balance(owner, None).await; assert!(response.is_ok()); @@ -1096,7 +967,7 @@ mod tests { let owner = get_test_owner(); let coin = get_test_coin(None, CoinType::Usdc); let coin_clone = coin.clone(); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_balance() .with( @@ -1109,7 +980,7 @@ mod tests { num_coins: 11, }) }); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_balance(owner, Some(coin.coin_type.clone())) .await; @@ -1132,8 +1003,8 @@ mod tests { async fn test_invalid_coin_type() { let owner = get_test_owner(); let coin_type = "0x2::invalid::struct::tag"; - let mock_state = MockState::new(); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let mock_state = MockStateRead::new(); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_balance(owner, Some(coin_type.to_string())) .await; @@ -1152,11 +1023,13 @@ mod tests { async fn test_get_balance_index_store_not_available() { let owner = get_test_owner(); let coin_type = get_test_coin_type(get_test_package_id()); - let mut mock_state = MockState::new(); - mock_state - .expect_get_balance() - .returning(move |_, _| Err(SuiError::IndexStoreNotAvailable)); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let mut mock_state = MockStateRead::new(); + mock_state.expect_get_balance().returning(move |_, _| { + Err(StateReadError::Client( + SuiError::IndexStoreNotAvailable.into(), + )) + }); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_balance(owner, Some(coin_type.to_string())) .await; @@ -1166,7 +1039,7 @@ mod tests { let error_object: ErrorObjectOwned = error_result.into(); assert_eq!( error_object.code(), - jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE + jsonrpsee::types::error::INVALID_PARAMS_CODE ); let expected = expect!["Index store not available on this Fullnode."]; expected.assert_eq(error_object.message()); @@ -1177,11 +1050,11 @@ mod tests { // Validate that we handle and return an error message when we encounter an unexpected error let owner = get_test_owner(); let coin_type = get_test_coin_type(get_test_package_id()); - let mut mock_state = MockState::new(); - mock_state - .expect_get_balance() - .returning(move |_, _| Err(SuiError::ExecutionError("mock db error".to_string()))); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let mut mock_state = MockStateRead::new(); + mock_state.expect_get_balance().returning(move |_, _| { + Err(SuiError::ExecutionError("mock db error".to_string()).into()) + }); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_balance(owner, Some(coin_type.to_string())) .await; @@ -1192,7 +1065,7 @@ mod tests { assert_eq!( error_object.code(), - jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE + jsonrpsee::types::error::INTERNAL_ERROR_CODE ); let expected = expect!["Error executing mock db error"]; expected.assert_eq(error_object.message()); @@ -1212,7 +1085,7 @@ mod tests { let gas_coin_type_tag = get_test_coin_type_tag(gas_coin.coin_type.clone()); let usdc_coin = get_test_coin(None, CoinType::Usdc); let usdc_coin_type_tag = get_test_coin_type_tag(usdc_coin.coin_type.clone()); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_get_all_balance() .with(predicate::eq(owner)) @@ -1234,7 +1107,7 @@ mod tests { ); Ok(Arc::new(hash_map)) }); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api.get_all_balances(owner).await; assert!(response.is_ok()); @@ -1268,11 +1141,13 @@ mod tests { #[tokio::test] async fn test_index_store_not_available() { let owner = get_test_owner(); - let mut mock_state = MockState::new(); - mock_state - .expect_get_all_balance() - .returning(move |_| Err(SuiError::IndexStoreNotAvailable)); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let mut mock_state = MockStateRead::new(); + mock_state.expect_get_all_balance().returning(move |_| { + Err(StateReadError::Client( + SuiError::IndexStoreNotAvailable.into(), + )) + }); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api.get_all_balances(owner).await; assert!(response.is_err()); @@ -1280,7 +1155,7 @@ mod tests { let error_object: ErrorObjectOwned = error_result.into(); assert_eq!( error_object.code(), - jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE + jsonrpsee::types::error::INVALID_PARAMS_CODE ); let expected = expect!["Index store not available on this Fullnode."]; expected.assert_eq(error_object.message()); @@ -1315,12 +1190,8 @@ mod tests { // return TreasuryCap instead of CoinMetadata to set up test mock_internal .expect_find_package_object() - .with( - predicate::always(), - predicate::eq(coin_metadata_struct), - predicate::always(), - ) - .return_once(move |object_id, _, _| { + .with(predicate::always(), predicate::eq(coin_metadata_struct)) + .return_once(move |object_id, _| { if object_id == &package_id { Ok(coin_metadata_object) } else { @@ -1328,16 +1199,8 @@ mod tests { } }); - let mock_state = MockState::new(); - let metrics = KeyValueStoreMetrics::new_for_tests(); - let transaction_kv_store = Arc::new(TransactionKeyValueStore::new( - "rocksdb", - metrics, - Arc::new(mock_state), - )); let coin_read_api = CoinReadApi { internal: Box::new(mock_internal), - transaction_kv_store, }; let response = coin_read_api.get_coin_metadata(coin_name.clone()).await; @@ -1352,7 +1215,7 @@ mod tests { let transaction_effects: TransactionEffects = TransactionEffects::V1(TransactionEffectsV1::default()); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_find_publish_txn_digest() .return_once(move |_| Ok(transaction_digest)); @@ -1360,7 +1223,7 @@ mod tests { .expect_get_executed_transaction_and_effects() .return_once(move |_, _| Ok((create_fake_transaction(), transaction_effects))); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api .get_coin_metadata("0x2::sui::SUI".to_string()) .await; @@ -1386,12 +1249,8 @@ mod tests { // return TreasuryCap instead of CoinMetadata to set up test mock_internal .expect_find_package_object() - .with( - predicate::always(), - predicate::eq(coin_metadata_struct), - predicate::always(), - ) - .returning(move |object_id, _, _| { + .with(predicate::always(), predicate::eq(coin_metadata_struct)) + .returning(move |object_id, _| { if object_id == &package_id { Ok(treasury_cap_object.clone()) } else { @@ -1399,16 +1258,8 @@ mod tests { } }); - let mock_state = MockState::new(); - let metrics = KeyValueStoreMetrics::new_for_tests(); - let transaction_kv_store = Arc::new(TransactionKeyValueStore::new( - "rocksdb", - metrics, - Arc::new(mock_state), - )); let coin_read_api = CoinReadApi { internal: Box::new(mock_internal), - transaction_kv_store, }; let response = coin_read_api.get_coin_metadata(coin_name.clone()).await; @@ -1428,17 +1279,8 @@ mod tests { async fn test_success_response_for_gas_coin() { let coin_type = "0x2::sui::SUI"; let mock_internal = MockCoinReadInternal::new(); - - let mock_state = MockState::new(); - let metrics = KeyValueStoreMetrics::new_for_tests(); - let transaction_kv_store = Arc::new(TransactionKeyValueStore::new( - "rocksdb", - metrics, - Arc::new(mock_state), - )); let coin_read_api = CoinReadApi { internal: Box::new(mock_internal), - transaction_kv_store, }; let response = coin_read_api.get_total_supply(coin_type.to_string()).await; @@ -1456,29 +1298,16 @@ mod tests { let mut mock_internal = MockCoinReadInternal::new(); mock_internal .expect_find_package_object() - .with( - predicate::always(), - predicate::eq(treasury_cap_struct), - predicate::always(), - ) - .returning(move |object_id, _, _| { + .with(predicate::always(), predicate::eq(treasury_cap_struct)) + .returning(move |object_id, _| { if object_id == &package_id { Ok(treasury_cap_object.clone()) } else { panic!("should not be called with any other object id") } }); - - let mock_state = MockState::new(); - let metrics = KeyValueStoreMetrics::new_for_tests(); - let transaction_kv_store = Arc::new(TransactionKeyValueStore::new( - "rocksdb", - metrics, - Arc::new(mock_state), - )); let coin_read_api = CoinReadApi { internal: Box::new(mock_internal), - transaction_kv_store, }; let response = coin_read_api.get_total_supply(coin_name.clone()).await; @@ -1497,7 +1326,7 @@ mod tests { let transaction_effects: TransactionEffects = TransactionEffects::V1(TransactionEffectsV1::default()); - let mut mock_state = MockState::new(); + let mut mock_state = MockStateRead::new(); mock_state .expect_find_publish_txn_digest() .return_once(move |_| Ok(transaction_digest)); @@ -1505,7 +1334,7 @@ mod tests { .expect_get_executed_transaction_and_effects() .return_once(move |_, _| Ok((create_fake_transaction(), transaction_effects))); - let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state)); + let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api.get_total_supply(coin_name.clone()).await; assert!(response.is_err()); @@ -1535,12 +1364,8 @@ mod tests { let mut mock_internal = MockCoinReadInternal::new(); mock_internal .expect_find_package_object() - .with( - predicate::always(), - predicate::eq(treasury_cap_struct), - predicate::always(), - ) - .returning(move |object_id, _, _| { + .with(predicate::always(), predicate::eq(treasury_cap_struct)) + .returning(move |object_id, _| { if object_id == &package_id { Ok(coin_metadata_object.clone()) } else { @@ -1548,16 +1373,8 @@ mod tests { } }); - let mock_state = MockState::new(); - let metrics = KeyValueStoreMetrics::new_for_tests(); - let transaction_kv_store = Arc::new(TransactionKeyValueStore::new( - "rocksdb", - metrics, - Arc::new(mock_state), - )); let coin_read_api = CoinReadApi { internal: Box::new(mock_internal), - transaction_kv_store, }; let response = coin_read_api.get_total_supply(coin_name.clone()).await; diff --git a/crates/sui-json-rpc/src/error.rs b/crates/sui-json-rpc/src/error.rs index c6b218cc89708..ff9691959208a 100644 --- a/crates/sui-json-rpc/src/error.rs +++ b/crates/sui-json-rpc/src/error.rs @@ -11,6 +11,10 @@ use sui_types::quorum_driver_types::{QuorumDriverError, NON_RECOVERABLE_ERROR_MS use thiserror::Error; use tokio::task::JoinError; +use crate::authority_state::StateReadError; + +pub const TRANSIENT_ERROR_CODE: i32 = -32001; + pub type RpcInterimResult = Result; #[derive(Debug, Error)] @@ -23,7 +27,6 @@ pub enum Error { #[error("Deserialization error: {0}")] BcsError(#[from] bcs::Error), - #[error("Unexpected error: {0}")] UnexpectedError(String), @@ -53,6 +56,10 @@ pub enum Error { #[error(transparent)] SuiRpcInputError(#[from] SuiRpcInputError), + + // TODO(wlmyng): convert StateReadError::Internal message to generic internal error message. + #[error(transparent)] + StateReadError(#[from] StateReadError), } impl From for RpcError { @@ -85,7 +92,7 @@ impl Error { } _ => RpcError::Call(CallError::Failed(err.into())), }, - Error::SuiRpcInputError(err) => err.into(), + Error::SuiRpcInputError(err) => RpcError::Call(CallError::InvalidParams(err.into())), Error::SuiError(sui_error) => match sui_error { SuiError::TransactionNotFound { .. } | SuiError::TransactionsNotFound { .. } @@ -107,6 +114,17 @@ impl Error { } _ => RpcError::Call(CallError::Failed(err.into())), }, + Error::StateReadError(err) => match err { + StateReadError::Client(_) => RpcError::Call(CallError::InvalidParams(err.into())), + _ => { + let error_object = ErrorObject::owned( + jsonrpsee::types::error::INTERNAL_ERROR_CODE, + err.to_string(), + None::<()>, + ); + RpcError::Call(CallError::Custom(error_object)) + } + }, _ => RpcError::Call(CallError::Failed(self.into())), } } @@ -150,9 +168,3 @@ pub enum SuiRpcInputError { #[error(transparent)] UserInputError(#[from] UserInputError), } - -impl From for RpcError { - fn from(e: SuiRpcInputError) -> Self { - RpcError::Call(CallError::InvalidParams(e.into())) - } -} diff --git a/crates/sui-json-rpc/src/governance_api.rs b/crates/sui-json-rpc/src/governance_api.rs index af7b6e8ef5346..eb518a36debb0 100644 --- a/crates/sui-json-rpc/src/governance_api.rs +++ b/crates/sui-json-rpc/src/governance_api.rs @@ -17,10 +17,10 @@ use sui_core::authority::AuthorityState; use sui_json_rpc_types::{DelegatedStake, Stake, StakeStatus}; use sui_json_rpc_types::{SuiCommittee, ValidatorApy, ValidatorApys}; use sui_open_rpc::Module; -use sui_types::base_types::{MoveObjectType, ObjectID, SuiAddress}; +use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::committee::EpochId; use sui_types::dynamic_field::get_dynamic_field_from_store; -use sui_types::error::{SuiError, SuiResult, UserInputError}; +use sui_types::error::{SuiError, UserInputError}; use sui_types::governance::StakedSui; use sui_types::id::ID; use sui_types::object::ObjectRead; @@ -31,12 +31,13 @@ use sui_types::sui_system_state::SuiSystemStateTrait; use sui_types::sui_system_state::{get_validator_from_table, SuiSystemState}; use crate::api::{GovernanceReadApiServer, JsonRpcMetrics}; -use crate::error::{Error, SuiRpcInputError}; +use crate::authority_state::StateRead; +use crate::error::{Error, RpcInterimResult, SuiRpcInputError}; use crate::{with_tracing, ObjectProvider, SuiRpcModule}; #[derive(Clone)] pub struct GovernanceReadApi { - state: Arc, + state: Arc, pub metrics: Arc, } @@ -47,12 +48,8 @@ impl GovernanceReadApi { async fn get_staked_sui(&self, owner: SuiAddress) -> Result, Error> { let state = self.state.clone(); - let result = spawn_monitored_task!(async move { - state - .get_move_objects(owner, MoveObjectType::staked_sui()) - .await - }) - .await??; + let result = + spawn_monitored_task!(async move { state.get_staked_sui(owner).await }).await??; self.metrics .get_stake_sui_result_size @@ -209,7 +206,7 @@ impl GovernanceReadApi { } fn get_system_state(&self) -> Result { - Ok(self.state.database.get_sui_system_state_object()?) + Ok(self.state.get_system_state()?) } } @@ -232,8 +229,7 @@ impl GovernanceReadApiServer for GovernanceReadApi { async fn get_committee_info(&self, epoch: Option>) -> RpcResult { with_tracing!(async move { self.state - .committee_store() - .get_or_latest_committee(epoch.map(|e| *e)) + .get_or_latest_committee(epoch) .map(|committee| committee.into()) .map_err(Error::from) }) @@ -244,8 +240,7 @@ impl GovernanceReadApiServer for GovernanceReadApi { with_tracing!(async move { Ok(self .state - .database - .get_sui_system_state_object() + .get_system_state() .map_err(Error::from)? .into_sui_system_state_summary()) }) @@ -327,10 +322,10 @@ fn calculate_apy((rate_e, rate_e_1): (&PoolTokenExchangeRate, &PoolTokenExchange result = true )] async fn exchange_rates( - state: &Arc, + state: &Arc, _current_epoch: EpochId, -) -> SuiResult> { - let system_state = state.database.get_sui_system_state_object()?; +) -> RpcInterimResult> { + let system_state = state.get_system_state()?; let system_state_summary: SuiSystemStateSummary = system_state.into_sui_system_state_summary(); // Get validator rate tables @@ -357,10 +352,10 @@ async fn exchange_rates( error: e.to_string(), })?; let validator = get_validator_from_table( - state.database.as_ref(), + state.get_db().as_ref(), system_state_summary.inactive_pools_id, &pool_id, - )?; + )?; // TODO(wlmyng): roll this into StateReadError tables.push(( validator.sui_address, validator.staking_pool_id, @@ -383,8 +378,11 @@ async fn exchange_rates( } })?; - let exchange_rate: PoolTokenExchangeRate = - get_dynamic_field_from_store(state.db().as_ref(), exchange_rates_id, &epoch)?; + let exchange_rate: PoolTokenExchangeRate = get_dynamic_field_from_store( + state.get_db().as_ref(), + exchange_rates_id, + &epoch, + )?; Ok::<_, SuiError>((epoch, exchange_rate)) }) diff --git a/crates/sui-json-rpc/src/indexer_api.rs b/crates/sui-json-rpc/src/indexer_api.rs index b0290fd547228..ca5f08f392603 100644 --- a/crates/sui-json-rpc/src/indexer_api.rs +++ b/crates/sui-json-rpc/src/indexer_api.rs @@ -4,6 +4,7 @@ use anyhow::bail; use std::str::FromStr; use std::sync::Arc; +use sui_core::authority::AuthorityState; use async_trait::async_trait; use futures::Stream; @@ -22,7 +23,6 @@ use move_core_types::ident_str; use move_core_types::identifier::IdentStr; use move_core_types::language_storage::{StructTag, TypeTag}; use mysten_metrics::spawn_monitored_task; -use sui_core::authority::AuthorityState; use sui_json_rpc_types::{ DynamicFieldPage, EventFilter, EventPage, ObjectsPage, Page, SuiMoveValue, SuiObjectDataOptions, SuiObjectResponse, SuiObjectResponseQuery, SuiParsedMoveObject, @@ -41,6 +41,7 @@ use crate::api::{ cap_page_limit, validate_limit, IndexerApiServer, JsonRpcMetrics, ReadApiServer, QUERY_MAX_RESULT_LIMIT, }; +use crate::authority_state::StateRead; use crate::error::{Error, SuiRpcInputError}; use crate::name_service::Domain; use crate::with_tracing; @@ -86,7 +87,7 @@ pub fn spawn_subscription( const DEFAULT_MAX_SUBSCRIPTIONS: usize = 100; pub struct IndexerApi { - state: Arc, + state: Arc, read_api: R, transaction_kv_store: Arc, ns_package_addr: Option, @@ -110,8 +111,8 @@ impl IndexerApi { let max_subscriptions = max_subscriptions.unwrap_or(DEFAULT_MAX_SUBSCRIPTIONS); Self { state, - read_api, transaction_kv_store, + read_api, ns_registry_id, ns_package_addr, ns_reverse_registry_id, @@ -128,7 +129,7 @@ impl IndexerApi { type_: name_type, value, } = name; - let layout = TypeLayoutBuilder::build_with_types(&name_type, &self.state.database)?; + let layout = TypeLayoutBuilder::build_with_types(&name_type, &self.state.get_db())?; let sui_json_value = SuiJsonValue::new(value)?; let name_bcs_value = sui_json_value.to_bcs_bytes(&layout)?; Ok((name_type, name_bcs_value)) @@ -160,7 +161,7 @@ impl IndexerApiServer for IndexerApi { let options = options.unwrap_or_default(); let mut objects = self .state - .get_owner_objects(address, cursor, limit + 1, filter) + .get_owner_objects_with_limit(address, cursor, limit + 1, filter) .map_err(Error::from)?; // objects here are of size (limit + 1), where the last one is the cursor for the next page @@ -302,7 +303,9 @@ impl IndexerApiServer for IndexerApi { let permit = self.acquire_subscribe_permit()?; spawn_subscription( sink, - self.state.subscription_handler.subscribe_events(filter), + self.state + .get_subscription_handler() + .subscribe_events(filter), Some(permit), ); Ok(()) @@ -317,7 +320,7 @@ impl IndexerApiServer for IndexerApi { spawn_subscription( sink, self.state - .subscription_handler + .get_subscription_handler() .subscribe_transactions(filter), Some(permit), ); diff --git a/crates/sui-json-rpc/src/lib.rs b/crates/sui-json-rpc/src/lib.rs index d921f3c52a0c4..6b784f160adf2 100644 --- a/crates/sui-json-rpc/src/lib.rs +++ b/crates/sui-json-rpc/src/lib.rs @@ -28,6 +28,7 @@ use crate::metrics::MetricsLogger; use crate::routing_layer::RoutingLayer; pub mod api; +pub mod authority_state; mod balance_changes; pub mod coin_api; pub mod error; diff --git a/crates/sui-json-rpc/src/metrics.rs b/crates/sui-json-rpc/src/metrics.rs index 9f5774e1ba1ad..dd625849aa456 100644 --- a/crates/sui-json-rpc/src/metrics.rs +++ b/crates/sui-json-rpc/src/metrics.rs @@ -221,7 +221,9 @@ impl Logger for MetricsLogger { .observe(req_latency_secs); if let Some(code) = error_code { - if code == jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE { + if code == jsonrpsee::types::error::CALL_EXECUTION_FAILED_CODE + || code == jsonrpsee::types::error::INTERNAL_ERROR_CODE + { self.metrics .server_errors_by_route .with_label_values(&[method_name]) diff --git a/crates/sui-json-rpc/src/move_utils.rs b/crates/sui-json-rpc/src/move_utils.rs index 175eafe83e296..8fe40675c5fa7 100644 --- a/crates/sui-json-rpc/src/move_utils.rs +++ b/crates/sui-json-rpc/src/move_utils.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::api::MoveUtilsServer; +use crate::authority_state::StateRead; use crate::error::{Error, SuiRpcInputError}; use crate::{with_tracing, SuiRpcModule}; use async_trait::async_trait; @@ -31,7 +32,7 @@ use tracing::{error, instrument, warn}; #[cfg_attr(test, automock)] #[async_trait] pub trait MoveUtilsInternalTrait { - fn get_state(&self) -> &AuthorityState; + fn get_state(&self) -> &dyn StateRead; async fn get_move_module( &self, @@ -48,7 +49,7 @@ pub trait MoveUtilsInternalTrait { } pub struct MoveUtilsInternal { - state: Arc, + state: Arc, } impl MoveUtilsInternal { @@ -59,8 +60,8 @@ impl MoveUtilsInternal { #[async_trait] impl MoveUtilsInternalTrait for MoveUtilsInternal { - fn get_state(&self) -> &AuthorityState { - &self.state + fn get_state(&self) -> &dyn StateRead { + Arc::as_ref(&self.state) } async fn get_move_module( @@ -116,9 +117,7 @@ impl MoveUtilsInternalTrait for MoveUtilsInternal { } fn get_object_read(&self, package: ObjectID) -> Result { - self.get_state() - .get_object_read(&package) - .map_err(Error::from) + self.state.get_object_read(&package).map_err(Error::from) } } diff --git a/crates/sui-json-rpc/src/read_api.rs b/crates/sui-json-rpc/src/read_api.rs index 8e7020843df76..18ad72de72a29 100644 --- a/crates/sui-json-rpc/src/read_api.rs +++ b/crates/sui-json-rpc/src/read_api.rs @@ -36,7 +36,7 @@ use sui_types::crypto::AggregateAuthoritySignature; use sui_types::digests::TransactionEventsDigest; use sui_types::display::DisplayVersionUpdatedEvent; use sui_types::effects::{TransactionEffects, TransactionEffectsAPI, TransactionEvents}; -use sui_types::error::{SuiError, SuiObjectResponseError, SuiResult}; +use sui_types::error::{SuiError, SuiObjectResponseError}; use sui_types::messages_checkpoint::{ CheckpointContents, CheckpointContentsDigest, CheckpointSequenceNumber, CheckpointSummary, CheckpointTimestamp, @@ -49,6 +49,7 @@ use sui_types::transaction::TransactionDataAPI; use crate::api::JsonRpcMetrics; use crate::api::{validate_limit, ReadApiServer}; use crate::api::{QUERY_MAX_RESULT_LIMIT, QUERY_MAX_RESULT_LIMIT_CHECKPOINTS}; +use crate::authority_state::{StateRead, StateReadError, StateReadResult}; use crate::error::{Error, RpcInterimResult, SuiRpcInputError}; use crate::with_tracing; use crate::{ @@ -61,7 +62,7 @@ const MAX_DISPLAY_NESTED_LEVEL: usize = 10; // Fullnodes. #[derive(Clone)] pub struct ReadApi { - pub state: Arc, + pub state: Arc, pub transaction_kv_store: Arc, pub metrics: Arc, } @@ -138,13 +139,13 @@ impl ReadApi { } pub async fn get_checkpoints_internal( - state: Arc, + state: Arc, transaction_kv_store: Arc, // If `Some`, the query will start from the next item after the specified cursor cursor: Option, limit: u64, descending_order: bool, - ) -> SuiResult> { + ) -> StateReadResult> { let max_checkpoint = state.get_latest_checkpoint_sequence_number()?; let checkpoint_numbers = calculate_checkpoint_numbers(cursor, limit, descending_order, max_checkpoint); @@ -259,7 +260,6 @@ impl ReadApi { // when we can tolerate returning None for old txes. let checkpoint_seq_list = state - .database .deprecated_multi_get_transaction_checkpoint(&digests_clone) .tap_err( |err| debug!(digests=?digests_clone, "Failed to multi get checkpoint sequence number: {:?}", err))?; @@ -733,7 +733,7 @@ impl ReadApiServer for ReadApi { // is in the epoch store, and thus we risk breaking the read API for txes // from old epochs. Should be migrated once we have indexer support, or // when we can tolerate returning None for old txes. - state.database.deprecated_get_transaction_checkpoint(&digest) + state.deprecated_get_transaction_checkpoint(&digest) .map_err(|e| { error!("Failed to retrieve checkpoint sequence for transaction {digest:?} with error: {e:?}"); Error::from(e) @@ -874,7 +874,7 @@ impl ReadApiServer for ReadApi { .map_err( |e| { error!("Failed to get transaction events for event digest {event_digest:?} with error: {e:?}"); - Error::SuiError(e) + Error::StateReadError(e.into()) })? .data .into_iter() @@ -998,7 +998,7 @@ impl ReadApiServer for ReadApi { error!( "Failed to get loaded child objects at {digest:?} with error: {e:?}" ); - Error::SuiError(e) + Error::StateReadError(e) })? { Some(v) => v .into_iter() @@ -1020,10 +1020,7 @@ impl ReadApiServer for ReadApi { .map(|v| { ProtocolConfig::get_for_version_if_supported( (*v).into(), - self.state - .get_chain_identifier() - .ok_or(anyhow!("Chain identifier not found"))? - .chain(), + self.state.get_chain_identifier()?.chain(), ) .ok_or(SuiRpcInputError::ProtocolVersionUnsupported( ProtocolVersion::MIN.as_u64(), @@ -1043,10 +1040,7 @@ impl ReadApiServer for ReadApi { #[instrument(skip(self))] async fn get_chain_identifier(&self) -> RpcResult { with_tracing!(async move { - let ci = self - .state - .get_chain_identifier() - .ok_or(anyhow!("Chain identifier not found"))?; + let ci = self.state.get_chain_identifier()?; Ok(ci.to_string()) }) } @@ -1100,6 +1094,9 @@ pub enum ObjectDisplayError { #[error("Failed to deserialize 'VersionUpdatedEvent': {0}")] Bcs(#[from] bcs::Error), + + #[error(transparent)] + StateReadError(#[from] StateReadError), } async fn get_display_fields( diff --git a/crates/sui-json-rpc/src/transaction_builder_api.rs b/crates/sui-json-rpc/src/transaction_builder_api.rs index 510eb268d7378..ab67559788172 100644 --- a/crates/sui-json-rpc/src/transaction_builder_api.rs +++ b/crates/sui-json-rpc/src/transaction_builder_api.rs @@ -23,6 +23,7 @@ use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::sui_serde::BigInt; use crate::api::TransactionBuilderServer; +use crate::authority_state::StateRead; use crate::SuiRpcModule; pub struct TransactionBuilderApi(TransactionBuilder); @@ -34,7 +35,7 @@ impl TransactionBuilderApi { } } -pub struct AuthorityStateDataReader(Arc); +pub struct AuthorityStateDataReader(Arc); impl AuthorityStateDataReader { pub fn new(state: Arc) -> Self { @@ -52,12 +53,11 @@ impl DataReader for AuthorityStateDataReader { Ok(self .0 // DataReader is used internally, don't need a limit - .get_owner_objects_iterator( + .get_owner_objects( address, None, Some(SuiObjectDataFilter::StructType(object_type)), - )? - .collect()) + )?) } async fn get_object_with_options( diff --git a/crates/sui-json-rpc/src/transaction_execution_api.rs b/crates/sui-json-rpc/src/transaction_execution_api.rs index 72ddc72fa2695..68e2d1c594004 100644 --- a/crates/sui-json-rpc/src/transaction_execution_api.rs +++ b/crates/sui-json-rpc/src/transaction_execution_api.rs @@ -36,6 +36,7 @@ use tracing::instrument; use crate::api::JsonRpcMetrics; use crate::api::WriteApiServer; +use crate::authority_state::StateRead; use crate::error::{Error, SuiRpcInputError}; use crate::{ get_balance_changes_from_effect, get_object_changes, with_tracing, ObjectProviderCache, @@ -43,7 +44,7 @@ use crate::{ }; pub struct TransactionExecutionApi { - state: Arc, + state: Arc, transaction_orchestrator: Arc>, metrics: Arc, }