From eec605acbf22ac466cf52b819fe56e23d3eabef9 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 13 Dec 2024 09:34:21 -0600 Subject: [PATCH 01/22] rpc: add support for grpc-reflection --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 1 + crates/sui-rpc-api/Cargo.toml | 2 ++ crates/sui-rpc-api/src/lib.rs | 12 ++++++++++++ .../src/proto/generated/sui.node.v2.fds.bin | Bin 0 -> 99259 bytes crates/sui-rpc-api/src/proto/node.rs | 17 +++++++++++++++++ crates/sui-rpc-api/tests/bootstrap.rs | 2 ++ 7 files changed, 48 insertions(+) create mode 100644 crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin diff --git a/Cargo.lock b/Cargo.lock index ab2efc209f0cf..32f695a1a2201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15047,6 +15047,7 @@ dependencies = [ "tokio", "tonic 0.12.3", "tonic-build", + "tonic-reflection", "tower 0.4.13", "url", ] @@ -16859,6 +16860,19 @@ dependencies = [ "tonic 0.12.3", ] +[[package]] +name = "tonic-reflection" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "878d81f52e7fcfd80026b7fdb6a9b578b3c3653ba987f87f0dce4b64043cba27" +dependencies = [ + "prost 0.13.3", + "prost-types 0.13.3", + "tokio", + "tokio-stream", + "tonic 0.12.3", +] + [[package]] name = "tonic-rustls" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 658f22f8703c3..697b0aa0226ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -503,6 +503,7 @@ toml_edit = { version = "0.19.10" } tonic = { version = "0.12", features = ["transport", "tls-webpki-roots"] } tonic-build = { version = "0.12", features = ["prost", "transport"] } tonic-health = "0.12" +tonic-reflection = "0.12" tonic-rustls = "0.1.0" tower = { version = "0.4.12", features = [ "full", diff --git a/crates/sui-rpc-api/Cargo.toml b/crates/sui-rpc-api/Cargo.toml index 2f3b444ad12f0..58669459d0e6d 100644 --- a/crates/sui-rpc-api/Cargo.toml +++ b/crates/sui-rpc-api/Cargo.toml @@ -44,6 +44,8 @@ prost.workspace = true prost-types = "0.13.3" bytes.workspace = true +tonic-reflection.workspace = true + [dev-dependencies] diffy = "0.3" prost-build = "0.13.3" diff --git a/crates/sui-rpc-api/src/lib.rs b/crates/sui-rpc-api/src/lib.rs index 44a4dd3239db4..81246ab968888 100644 --- a/crates/sui-rpc-api/src/lib.rs +++ b/crates/sui-rpc-api/src/lib.rs @@ -82,7 +82,19 @@ impl RpcService { let rest_router = build_rest_router(self.clone()); let grpc_router = { + let reflection_v1 = tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(crate::proto::node::FILE_DESCRIPTOR_SET) + .build_v1() + .unwrap(); + + let reflection_v1alpha = tonic_reflection::server::Builder::configure() + .register_encoded_file_descriptor_set(crate::proto::node::FILE_DESCRIPTOR_SET) + .build_v1alpha() + .unwrap(); + grpc::Services::new() + .add_service(reflection_v1) + .add_service(reflection_v1alpha) .add_service(crate::proto::node::node_server::NodeServer::new( self.clone(), )) diff --git a/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin b/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin new file mode 100644 index 0000000000000000000000000000000000000000..54c4dab440c4884a88b3e3cb98548e5262467022 GIT binary patch literal 99259 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 DMSB$3 literal 0 HcmV?d00001 diff --git a/crates/sui-rpc-api/src/proto/node.rs b/crates/sui-rpc-api/src/proto/node.rs index 38cacb8506c04..94d867980fc4d 100644 --- a/crates/sui-rpc-api/src/proto/node.rs +++ b/crates/sui-rpc-api/src/proto/node.rs @@ -8,6 +8,23 @@ use tap::Pipe; mod generated; pub use generated::*; +mod file_descriptor_set { + /// Byte encoded FILE_DESCRIPTOR_SET. + pub const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("generated/sui.node.v2.fds.bin"); + + #[cfg(test)] + mod tests { + use super::FILE_DESCRIPTOR_SET; + use prost::Message as _; + + #[test] + fn file_descriptor_set_is_valid() { + prost_types::FileDescriptorSet::decode(FILE_DESCRIPTOR_SET).unwrap(); + } + } +} +pub use file_descriptor_set::FILE_DESCRIPTOR_SET; + // // BalanceChange // diff --git a/crates/sui-rpc-api/tests/bootstrap.rs b/crates/sui-rpc-api/tests/bootstrap.rs index 08e3669ccd42e..d2c23f4cd1f38 100644 --- a/crates/sui-rpc-api/tests/bootstrap.rs +++ b/crates/sui-rpc-api/tests/bootstrap.rs @@ -34,12 +34,14 @@ fn bootstrap() { }; let out_dir = root_dir.join("src").join("proto").join("generated"); + let file_descriptor_set_path = out_dir.join("sui.node.v2.fds.bin"); if let Err(error) = tonic_build::configure() .build_client(true) .build_server(true) .bytes(["."]) .out_dir(&out_dir) + .file_descriptor_set_path(file_descriptor_set_path) .compile_protos(&proto_files[..], &[proto_dir]) { panic!("failed to compile `sui` protos: {}", error); From 2faf6b12f2a7932b09f3f314443cf8d2595bc697 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 13 Dec 2024 10:01:40 -0600 Subject: [PATCH 02/22] rpc: add support for grpc-health service --- Cargo.lock | 1 + crates/sui-node/src/lib.rs | 2 +- crates/sui-rpc-api/Cargo.toml | 1 + crates/sui-rpc-api/src/lib.rs | 28 +++++++++++++++++++++++----- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32f695a1a2201..924e0fd25c4af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15047,6 +15047,7 @@ dependencies = [ "tokio", "tonic 0.12.3", "tonic-build", + "tonic-health", "tonic-reflection", "tower 0.4.13", "url", diff --git a/crates/sui-node/src/lib.rs b/crates/sui-node/src/lib.rs index 5340fbf0833c6..47ecefb3c201f 100644 --- a/crates/sui-node/src/lib.rs +++ b/crates/sui-node/src/lib.rs @@ -2125,7 +2125,7 @@ pub async fn build_http_server( rest_service.with_executor(transaction_orchestrator.clone()) } - router = router.merge(rest_service.into_router()); + router = router.merge(rest_service.into_router().await); } // TODO: Remove this health check when experimental REST API becomes default // This is a copy of the health check in crates/sui-rpc-api/src/health.rs diff --git a/crates/sui-rpc-api/Cargo.toml b/crates/sui-rpc-api/Cargo.toml index 58669459d0e6d..bdddd9c25f2af 100644 --- a/crates/sui-rpc-api/Cargo.toml +++ b/crates/sui-rpc-api/Cargo.toml @@ -44,6 +44,7 @@ prost.workspace = true prost-types = "0.13.3" bytes.workspace = true +tonic-health.workspace = true tonic-reflection.workspace = true [dev-dependencies] diff --git a/crates/sui-rpc-api/src/lib.rs b/crates/sui-rpc-api/src/lib.rs index 81246ab968888..2b504d6b53667 100644 --- a/crates/sui-rpc-api/src/lib.rs +++ b/crates/sui-rpc-api/src/lib.rs @@ -76,28 +76,44 @@ impl RpcService { self.software_version } - pub fn into_router(self) -> axum::Router { + pub async fn into_router(self) -> axum::Router { let metrics = self.metrics.clone(); let rest_router = build_rest_router(self.clone()); let grpc_router = { + let node_service = crate::proto::node::node_server::NodeServer::new(self.clone()); + + let (mut health_reporter, health_service) = tonic_health::server::health_reporter(); + let reflection_v1 = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(crate::proto::node::FILE_DESCRIPTOR_SET) + .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) .build_v1() .unwrap(); let reflection_v1alpha = tonic_reflection::server::Builder::configure() .register_encoded_file_descriptor_set(crate::proto::node::FILE_DESCRIPTOR_SET) + .register_encoded_file_descriptor_set(tonic_health::pb::FILE_DESCRIPTOR_SET) .build_v1alpha() .unwrap(); + fn service_name(_service: &S) -> &'static str { + S::NAME + } + + health_reporter + .set_service_status( + service_name(&node_service), + tonic_health::ServingStatus::Serving, + ) + .await; + grpc::Services::new() + .add_service(health_service) .add_service(reflection_v1) .add_service(reflection_v1alpha) - .add_service(crate::proto::node::node_server::NodeServer::new( - self.clone(), - )) + .add_service(node_service) .into_router() }; @@ -120,7 +136,9 @@ impl RpcService { pub async fn start_service(self, socket_address: std::net::SocketAddr) { let listener = tokio::net::TcpListener::bind(socket_address).await.unwrap(); - axum::serve(listener, self.into_router()).await.unwrap(); + axum::serve(listener, self.into_router().await) + .await + .unwrap(); } } From 3496436d05a971651147ededd0d43d6af61cef47 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Fri, 13 Dec 2024 11:28:37 -0600 Subject: [PATCH 03/22] config: add helper for retrieving rpc config --- crates/sui-config/src/node.rs | 4 ++++ crates/sui-node/src/lib.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/sui-config/src/node.rs b/crates/sui-config/src/node.rs index 0ac93c96586b4..41884b4702a68 100644 --- a/crates/sui-config/src/node.rs +++ b/crates/sui-config/src/node.rs @@ -637,6 +637,10 @@ impl NodeConfig { pub fn jsonrpc_server_type(&self) -> ServerType { self.jsonrpc_server_type.unwrap_or(ServerType::Http) } + + pub fn rpc(&self) -> Option<&sui_rpc_api::Config> { + self.rpc.as_ref() + } } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/crates/sui-node/src/lib.rs b/crates/sui-node/src/lib.rs index 47ecefb3c201f..aa02a92659a44 100644 --- a/crates/sui-node/src/lib.rs +++ b/crates/sui-node/src/lib.rs @@ -597,7 +597,7 @@ impl SuiNode { let rpc_index = if is_full_node && config.enable_experimental_rest_api - && config.rpc.as_ref().is_some_and(|rpc| rpc.enable_indexing()) + && config.rpc().is_some_and(|rpc| rpc.enable_indexing()) { Some(Arc::new(RpcIndexStore::new( &config.db_path(), From f8bfb03616d0db66755610041f79f085296dfae5 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 16 Dec 2024 08:28:57 -0600 Subject: [PATCH 04/22] rpc: ensure strings are string types in proto files Update all proto fields that have their corresponding protocol type encoded as utf8 to `string` but were previously listed as `bytes`. This is a wire-format compatible change and is safe to make since all previous uses of these fields were as valid utf8 strings. --- crates/sui-rpc-api/proto/sui.types.proto | 22 ++++----- .../src/proto/generated/sui.node.v2.fds.bin | Bin 99259 -> 99259 bytes .../src/proto/generated/sui.types.rs | 44 +++++++++--------- crates/sui-rpc-api/src/proto/mod.rs | 6 --- .../sui-rpc-api/src/proto/types/move_types.rs | 4 +- .../sui-rpc-api/src/proto/types/signatures.rs | 37 ++++++++------- .../src/proto/types/transaction_convert.rs | 28 ++++------- 7 files changed, 61 insertions(+), 80 deletions(-) diff --git a/crates/sui-rpc-api/proto/sui.types.proto b/crates/sui-rpc-api/proto/sui.types.proto index 2404383a89b72..e2c65cb53c62e 100644 --- a/crates/sui-rpc-api/proto/sui.types.proto +++ b/crates/sui-rpc-api/proto/sui.types.proto @@ -204,7 +204,7 @@ message GasCostSummary { // Move {{{ message Identifier { - optional bytes identifier = 1; + optional string identifier = 1; } message StructTag { @@ -482,19 +482,19 @@ message ActiveJwk { } message JwkId { - optional bytes iss = 1; - optional bytes kid = 2; + optional string iss = 1; + optional string kid = 2; } message Jwk { // Key type parameter, - optional bytes kty = 1; + optional string kty = 1; // RSA public exponent, - optional bytes e = 2; + optional string e = 2; // RSA modulus, - optional bytes n = 3; + optional string n = 3; // Algorithm parameter, - optional bytes alg = 4; + optional string alg = 4; } message EndOfEpochTransaction { @@ -925,7 +925,7 @@ message SimpleSignature { } message ZkLoginPublicIdentifier { - optional bytes iss = 1; + optional string iss = 1; optional Bn254FieldElement address_seed = 2; } @@ -973,7 +973,7 @@ message ZkLoginAuthenticator { message ZkLoginInputs { optional ZkLoginProof proof_points = 1; optional ZkLoginClaim iss_base64_details = 2; - optional bytes header_base64 = 3; + optional string header_base64 = 3; optional Bn254FieldElement address_seed = 4; } @@ -984,7 +984,7 @@ message ZkLoginProof { } message ZkLoginClaim { - optional bytes value = 1; + optional string value = 1; optional uint32 index_mod_4 = 2; } @@ -1011,7 +1011,7 @@ message Bn254FieldElement { message PasskeyAuthenticator { optional bytes authenticator_data = 1; - optional bytes client_data_json = 2; + optional string client_data_json = 2; optional SimpleSignature signature = 3; } diff --git a/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin b/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin index 54c4dab440c4884a88b3e3cb98548e5262467022..5e35cfdc9635c9756c36c1416ab4502d91bc9b57 100644 GIT binary patch delta 684 zcmYL_J4*vW6otJro2*&2aD7b@Lv(#)MN&wxG@w;l1EMH`!H9&2D6z2=ga{%QBg95i z+X-82ksn}Zs;JnSG$BMSQdsGo$?jSWXYO~-xigibzf$x+wi6JrJ`=_y3#59|G*>8G z0O%tc&F8X=On@2Sb8GRW9Oh3>#c)uAz3wOlY1nhF4jC16ilpJtiO3x6GeullH)|jB zy>0GAn5^Jc?Nyll~l;+gvB4u z=OM&dEt2b^OA@V9++8?PB3h>`sP$_KO3Z0c5^{#1NU6EfCuE^TiFp1yS zOzeEB9mTt3Qg$|75+#yN(Yb@2NzvJ&gz@alvF8xkEvgU6W<=1@qN8KlhI9~JX$uE= z&3O(H)Bbl5bhL$|+kxr}-^FiVkDX6T5{izm|E;KGSkN-xepet=GQ;40@TPo0S#{ou z64|oKf_63xEk8_`Rt*o%5V1;C)uVEGln7TSGc{iSWA` j3fqg*@J%6BikTj~PsF!$RNas;N`yP=%&fg@!p7Dgx*B%> delta 684 zcmYL_u}cC`9LM{;cY1LdbYgjzsA-lL;t z8*cCqXv@hA*J=qd8yuSI_nvpx^!UEt=llJ>->a9s^|JS|y8u4(Yat%feA%HKEtEKoDH*0UH zVW)sCmjfm{zw==8e0dx2xUM8A9@b?o3{!0VAsii3Miq~wlOga+&!C1JNx6(fP4;k+ z0-b5iaAANh3G|(+0fC}K^qmGZ#ivNp62Y7gN_2Y&l8}lcJzS1BNw_3RHwiH51C!W& z)4%{)F458@g{lAI|Ca9Ul>+cHaRU-t>2XBfEYO=j6 zN@QztP_yzO=y+kex*B)kUBaKzkkyEIJW7Ncax89L#i0lgs{Ty7@SaF+%j$%HQ6k)y k^+{_n0pAp6t4YI!4+#I3zO2Rsj1u9#oS3op4A|KE1H{^P-T(jq diff --git a/crates/sui-rpc-api/src/proto/generated/sui.types.rs b/crates/sui-rpc-api/src/proto/generated/sui.types.rs index fd14675edd166..35d500071279e 100644 --- a/crates/sui-rpc-api/src/proto/generated/sui.types.rs +++ b/crates/sui-rpc-api/src/proto/generated/sui.types.rs @@ -262,8 +262,8 @@ pub struct GasCostSummary { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Identifier { - #[prost(bytes = "bytes", optional, tag = "1")] - pub identifier: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "1")] + pub identifier: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct StructTag { @@ -725,25 +725,25 @@ pub struct ActiveJwk { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct JwkId { - #[prost(bytes = "bytes", optional, tag = "1")] - pub iss: ::core::option::Option<::prost::bytes::Bytes>, - #[prost(bytes = "bytes", optional, tag = "2")] - pub kid: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "1")] + pub iss: ::core::option::Option<::prost::alloc::string::String>, + #[prost(string, optional, tag = "2")] + pub kid: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct Jwk { /// Key type parameter, <> - #[prost(bytes = "bytes", optional, tag = "1")] - pub kty: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "1")] + pub kty: ::core::option::Option<::prost::alloc::string::String>, /// RSA public exponent, <> - #[prost(bytes = "bytes", optional, tag = "2")] - pub e: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "2")] + pub e: ::core::option::Option<::prost::alloc::string::String>, /// RSA modulus, <> - #[prost(bytes = "bytes", optional, tag = "3")] - pub n: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "3")] + pub n: ::core::option::Option<::prost::alloc::string::String>, /// Algorithm parameter, <> - #[prost(bytes = "bytes", optional, tag = "4")] - pub alg: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "4")] + pub alg: ::core::option::Option<::prost::alloc::string::String>, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct EndOfEpochTransaction { @@ -1336,8 +1336,8 @@ pub struct SimpleSignature { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ZkLoginPublicIdentifier { - #[prost(bytes = "bytes", optional, tag = "1")] - pub iss: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "1")] + pub iss: ::core::option::Option<::prost::alloc::string::String>, #[prost(message, optional, tag = "2")] pub address_seed: ::core::option::Option, } @@ -1419,8 +1419,8 @@ pub struct ZkLoginInputs { pub proof_points: ::core::option::Option, #[prost(message, optional, tag = "2")] pub iss_base64_details: ::core::option::Option, - #[prost(bytes = "bytes", optional, tag = "3")] - pub header_base64: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "3")] + pub header_base64: ::core::option::Option<::prost::alloc::string::String>, #[prost(message, optional, tag = "4")] pub address_seed: ::core::option::Option, } @@ -1435,8 +1435,8 @@ pub struct ZkLoginProof { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ZkLoginClaim { - #[prost(bytes = "bytes", optional, tag = "1")] - pub value: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "1")] + pub value: ::core::option::Option<::prost::alloc::string::String>, #[prost(uint32, optional, tag = "2")] pub index_mod_4: ::core::option::Option, } @@ -1473,8 +1473,8 @@ pub struct Bn254FieldElement { pub struct PasskeyAuthenticator { #[prost(bytes = "bytes", optional, tag = "1")] pub authenticator_data: ::core::option::Option<::prost::bytes::Bytes>, - #[prost(bytes = "bytes", optional, tag = "2")] - pub client_data_json: ::core::option::Option<::prost::bytes::Bytes>, + #[prost(string, optional, tag = "2")] + pub client_data_json: ::core::option::Option<::prost::alloc::string::String>, #[prost(message, optional, tag = "3")] pub signature: ::core::option::Option, } diff --git a/crates/sui-rpc-api/src/proto/mod.rs b/crates/sui-rpc-api/src/proto/mod.rs index 56fbc4af904cb..8934a3f852c82 100644 --- a/crates/sui-rpc-api/src/proto/mod.rs +++ b/crates/sui-rpc-api/src/proto/mod.rs @@ -52,12 +52,6 @@ impl From for TryFromProtoError { } } -impl From for TryFromProtoError { - fn from(value: std::str::Utf8Error) -> Self { - Self::from_error(value) - } -} - impl From for TryFromProtoError { fn from(value: std::array::TryFromSliceError) -> Self { Self::from_error(value) diff --git a/crates/sui-rpc-api/src/proto/types/move_types.rs b/crates/sui-rpc-api/src/proto/types/move_types.rs index 7862c63b981ab..5d704fa94e06b 100644 --- a/crates/sui-rpc-api/src/proto/types/move_types.rs +++ b/crates/sui-rpc-api/src/proto/types/move_types.rs @@ -8,7 +8,7 @@ use tap::Pipe; impl From for super::Identifier { fn from(value: sui_sdk_types::types::Identifier) -> Self { Self { - identifier: Some(value.into_inner().into_boxed_bytes().into()), + identifier: Some(value.into_inner().into()), } } } @@ -21,8 +21,6 @@ impl TryFrom<&super::Identifier> for sui_sdk_types::types::Identifier { .identifier .as_ref() .ok_or_else(|| TryFromProtoError::missing("identifier"))? - .as_ref() - .pipe(std::str::from_utf8)? .pipe(Self::new) .map_err(TryFromProtoError::from_error) } diff --git a/crates/sui-rpc-api/src/proto/types/signatures.rs b/crates/sui-rpc-api/src/proto/types/signatures.rs index faaa3f3175e72..8996e84b801a6 100644 --- a/crates/sui-rpc-api/src/proto/types/signatures.rs +++ b/crates/sui-rpc-api/src/proto/types/signatures.rs @@ -278,10 +278,12 @@ impl TryFrom<&super::CircomG2> for sui_sdk_types::types::CircomG2 { // impl From for super::ZkLoginClaim { - fn from(value: sui_sdk_types::types::Claim) -> Self { + fn from( + sui_sdk_types::types::Claim { value, index_mod_4 }: sui_sdk_types::types::Claim, + ) -> Self { Self { - value: Some(value.value.into_bytes().into()), - index_mod_4: Some(value.index_mod_4.into()), + value: Some(value), + index_mod_4: Some(index_mod_4.into()), } } } @@ -295,8 +297,6 @@ impl TryFrom<&super::ZkLoginClaim> for sui_sdk_types::types::Claim { let value = value .as_ref() .ok_or_else(|| TryFromProtoError::missing("value"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let index_mod_4 = index_mod_4 .ok_or_else(|| TryFromProtoError::missing("index_mod_4"))? @@ -349,12 +349,19 @@ impl TryFrom<&super::ZkLoginProof> for sui_sdk_types::types::ZkLoginProof { // impl From for super::ZkLoginInputs { - fn from(value: sui_sdk_types::types::ZkLoginInputs) -> Self { + fn from( + sui_sdk_types::types::ZkLoginInputs { + proof_points, + iss_base64_details, + header_base64, + address_seed, + }: sui_sdk_types::types::ZkLoginInputs, + ) -> Self { Self { - proof_points: Some(value.proof_points.into()), - iss_base64_details: Some(value.iss_base64_details.into()), - header_base64: Some(value.header_base64.into_bytes().into()), - address_seed: Some(value.address_seed.into()), + proof_points: Some(proof_points.into()), + iss_base64_details: Some(iss_base64_details.into()), + header_base64: Some(header_base64), + address_seed: Some(address_seed.into()), } } } @@ -377,8 +384,6 @@ impl TryFrom<&super::ZkLoginInputs> for sui_sdk_types::types::ZkLoginInputs { .header_base64 .as_ref() .ok_or_else(|| TryFromProtoError::missing("header_base64"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let address_seed = value .address_seed @@ -442,7 +447,7 @@ impl TryFrom<&super::ZkLoginAuthenticator> for sui_sdk_types::types::ZkLoginAuth impl From<&sui_sdk_types::types::ZkLoginPublicIdentifier> for super::ZkLoginPublicIdentifier { fn from(value: &sui_sdk_types::types::ZkLoginPublicIdentifier) -> Self { Self { - iss: Some(value.iss().to_owned().into_bytes().into()), + iss: Some(value.iss().to_owned()), address_seed: Some(value.address_seed().to_owned().into()), } } @@ -456,8 +461,6 @@ impl TryFrom<&super::ZkLoginPublicIdentifier> for sui_sdk_types::types::ZkLoginP .iss .as_ref() .ok_or_else(|| TryFromProtoError::missing("iss"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let address_seed = value .address_seed @@ -595,7 +598,7 @@ impl From for super::PasskeyAuthenti fn from(value: sui_sdk_types::types::PasskeyAuthenticator) -> Self { Self { authenticator_data: Some(value.authenticator_data().to_vec().into()), - client_data_json: Some(value.client_data_json().as_bytes().to_vec().into()), + client_data_json: Some(value.client_data_json().to_owned()), signature: Some(value.signature().into()), } } @@ -614,8 +617,6 @@ impl TryFrom<&super::PasskeyAuthenticator> for sui_sdk_types::types::PasskeyAuth .client_data_json .as_ref() .ok_or_else(|| TryFromProtoError::missing("client_data_json"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let signature = value diff --git a/crates/sui-rpc-api/src/proto/types/transaction_convert.rs b/crates/sui-rpc-api/src/proto/types/transaction_convert.rs index 8e75493060744..38914a2433dd1 100644 --- a/crates/sui-rpc-api/src/proto/types/transaction_convert.rs +++ b/crates/sui-rpc-api/src/proto/types/transaction_convert.rs @@ -610,12 +610,12 @@ impl TryFrom<&super::AuthenticatorStateUpdate> for sui_sdk_types::types::Authent // impl From for super::Jwk { - fn from(value: sui_sdk_types::types::Jwk) -> Self { + fn from(sui_sdk_types::types::Jwk { kty, e, n, alg }: sui_sdk_types::types::Jwk) -> Self { Self { - kty: Some(value.kty.into_bytes().into()), - e: Some(value.e.into_bytes().into()), - n: Some(value.n.into_bytes().into()), - alg: Some(value.alg.into_bytes().into()), + kty: Some(kty), + e: Some(e), + n: Some(n), + alg: Some(alg), } } } @@ -627,26 +627,18 @@ impl TryFrom<&super::Jwk> for sui_sdk_types::types::Jwk { let kty = kty .as_ref() .ok_or_else(|| TryFromProtoError::missing("kty"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let e = e .as_ref() .ok_or_else(|| TryFromProtoError::missing("e"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let n = n .as_ref() .ok_or_else(|| TryFromProtoError::missing("n"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let alg = alg .as_ref() .ok_or_else(|| TryFromProtoError::missing("alg"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); Ok(Self { kty, e, n, alg }) } @@ -657,10 +649,10 @@ impl TryFrom<&super::Jwk> for sui_sdk_types::types::Jwk { // impl From for super::JwkId { - fn from(value: sui_sdk_types::types::JwkId) -> Self { + fn from(sui_sdk_types::types::JwkId { iss, kid }: sui_sdk_types::types::JwkId) -> Self { Self { - iss: Some(value.iss.into_bytes().into()), - kid: Some(value.kid.into_bytes().into()), + iss: Some(iss), + kid: Some(kid), } } } @@ -672,14 +664,10 @@ impl TryFrom<&super::JwkId> for sui_sdk_types::types::JwkId { let iss = iss .as_ref() .ok_or_else(|| TryFromProtoError::missing("iss"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); let kid = kid .as_ref() .ok_or_else(|| TryFromProtoError::missing("kid"))? - .as_ref() - .pipe(std::str::from_utf8)? .into(); Ok(Self { iss, kid }) } From e5b12a474b04d396432e09faaffd678d9882640c Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 16 Dec 2024 09:17:43 -0600 Subject: [PATCH 05/22] rpc: support requesting and sending UserSignatures encoded as bytes --- crates/sui-rpc-api/openapi/openapi.json | 26 ++++++++++ crates/sui-rpc-api/proto/sui.node.v2.proto | 14 +++++- crates/sui-rpc-api/src/client/mod.rs | 11 ++--- crates/sui-rpc-api/src/grpc.rs | 45 +++++++++++++----- .../src/proto/generated/sui.node.v2.fds.bin | Bin 99259 -> 100037 bytes .../src/proto/generated/sui.node.v2.rs | 18 ++++++- crates/sui-rpc-api/src/proto/node.rs | 20 ++++++++ .../src/service/transactions/mod.rs | 8 ++++ crates/sui-rpc-api/src/types.rs | 15 ++++++ 9 files changed, 136 insertions(+), 21 deletions(-) diff --git a/crates/sui-rpc-api/openapi/openapi.json b/crates/sui-rpc-api/openapi/openapi.json index 0de242c3bd673..fb4a7e316882a 100644 --- a/crates/sui-rpc-api/openapi/openapi.json +++ b/crates/sui-rpc-api/openapi/openapi.json @@ -606,6 +606,16 @@ }, "style": "form" }, + { + "in": "query", + "name": "signatures_bytes", + "description": "Request `Vec` encoded as bytes be included in the response\n\nDefaults to `false` if not provided.", + "schema": { + "description": "Request `Vec` encoded as bytes be included in the response\n\nDefaults to `false` if not provided.", + "type": "boolean" + }, + "style": "form" + }, { "in": "query", "name": "transaction", @@ -728,6 +738,16 @@ }, "style": "form" }, + { + "in": "query", + "name": "signatures_bytes", + "description": "Request `Vec` encoded as bytes be included in the response\n\nDefaults to `false` if not provided.", + "schema": { + "description": "Request `Vec` encoded as bytes be included in the response\n\nDefaults to `false` if not provided.", + "type": "boolean" + }, + "style": "form" + }, { "in": "query", "name": "transaction", @@ -5515,6 +5535,12 @@ "$ref": "#/components/schemas/UserSignature" } }, + "signatures_bytes": { + "type": "array", + "items": { + "type": "string" + } + }, "timestamp_ms": { "description": "Radix-10 encoded 64-bit unsigned integer", "type": "string", diff --git a/crates/sui-rpc-api/proto/sui.node.v2.proto b/crates/sui-rpc-api/proto/sui.node.v2.proto index 8cf251479a2e9..b4b681210319b 100644 --- a/crates/sui-rpc-api/proto/sui.node.v2.proto +++ b/crates/sui-rpc-api/proto/sui.node.v2.proto @@ -74,6 +74,10 @@ message GetTransactionOptions { // // Defaults to true if not included optional bool signatures = 3; + // Include the set of UserSignature's encoded as bytes in the response. + // + // Defaults to false if not included + optional bool signatures_bytes = 8; // Include the sui.types.TransactionEffects message in the response. // // Defaults to true if not included @@ -98,6 +102,7 @@ message GetTransactionResponse { optional sui.types.Transaction transaction = 2; optional sui.types.Bcs transaction_bcs = 3; optional UserSignatures signatures = 4; + optional UserSignaturesBytes signatures_bytes = 11; optional sui.types.TransactionEffects effects = 5; optional sui.types.Bcs effects_bcs = 6; optional sui.types.TransactionEvents events = 7; @@ -110,6 +115,10 @@ message UserSignatures { repeated sui.types.UserSignature signatures = 1; } +message UserSignaturesBytes { + repeated bytes signatures = 1; +} + message GetObjectRequest { optional sui.types.ObjectId object_id = 1; optional uint64 version = 2; @@ -322,9 +331,10 @@ message ExecuteTransactionResponse { message ExecuteTransactionRequest { optional sui.types.Transaction transaction = 1; optional sui.types.Bcs transaction_bcs = 2; - repeated sui.types.UserSignature signatures = 3; + optional UserSignatures signatures = 3; + optional UserSignaturesBytes signatures_bytes = 4; - optional ExecuteTransactionOptions options = 4; + optional ExecuteTransactionOptions options = 5; } message ExecuteTransactionOptions { diff --git a/crates/sui-rpc-api/src/client/mod.rs b/crates/sui-rpc-api/src/client/mod.rs index 07d0c76a2050c..966a7ca813635 100644 --- a/crates/sui-rpc-api/src/client/mod.rs +++ b/crates/sui-rpc-api/src/client/mod.rs @@ -185,11 +185,9 @@ impl Client { let signatures = transaction .inner() .tx_signatures - .clone() - .into_iter() - .map(sui_sdk_types::types::UserSignature::try_from) - .collect::, _>>() - .map_err(|e| Status::from_error(e.into()))?; + .iter() + .map(|signature| signature.as_ref().to_vec().into()) + .collect(); let request = crate::proto::node::ExecuteTransactionRequest { transaction: None, @@ -197,7 +195,8 @@ impl Client { crate::proto::types::Bcs::serialize(&transaction.inner().intent_message.value) .map_err(|e| Status::from_error(e.into()))?, ), - signatures: signatures.into_iter().map(Into::into).collect(), + signatures: None, + signatures_bytes: Some(crate::proto::node::UserSignaturesBytes { signatures }), options: Some(crate::proto::node::ExecuteTransactionOptions { effects: Some(false), diff --git a/crates/sui-rpc-api/src/grpc.rs b/crates/sui-rpc-api/src/grpc.rs index a9308dc425046..0d32eeaabc38e 100644 --- a/crates/sui-rpc-api/src/grpc.rs +++ b/crates/sui-rpc-api/src/grpc.rs @@ -226,17 +226,40 @@ impl crate::proto::node::node_server::Node for crate::RpcService { )) } }; - let signatures = request - .signatures - .iter() - .map(TryInto::try_into) - .collect::>() - .map_err(|e| { - tonic::Status::new( - tonic::Code::InvalidArgument, - format!("invalid signature: {e}"), - ) - })?; + let mut signatures: Vec = Vec::new(); + + if let Some(proto_signatures) = request.signatures { + let from_proto_signatures = proto_signatures + .signatures + .iter() + .map(TryInto::try_into) + .collect::, _>>() + .map_err(|e| { + tonic::Status::new( + tonic::Code::InvalidArgument, + format!("invalid signature: {e}"), + ) + })?; + + signatures.extend(from_proto_signatures); + } + + if let Some(signatures_bytes) = request.signatures_bytes { + let from_bytes_signatures = signatures_bytes + .signatures + .iter() + .map(|bytes| sui_sdk_types::types::UserSignature::from_bytes(bytes)) + .collect::, _>>() + .map_err(|e| { + tonic::Status::new( + tonic::Code::InvalidArgument, + format!("invalid signature: {e}"), + ) + })?; + + signatures.extend(from_bytes_signatures); + } + let signed_transaction = sui_sdk_types::types::SignedTransaction { transaction, signatures, diff --git a/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin b/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin index 5e35cfdc9635c9756c36c1416ab4502d91bc9b57..ae6b1f91bbcbf768b635c16ef3a771699cb393ce 100644 GIT binary patch delta 8931 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

b z{!GL|L`*g!l8D=V!XUwH7nd+dB5wByQ>!_!LwLW_-k~5NJxNnOz8%{`1cjW_~WIU$`s|as9dQL8DPZ&Ej{&y?>zSFVaRq z66GCnpR!1rz9a5a7DG$~-lw_iwQdTe?}=`T_x+y77t;F)N$c_Z z35li$eEQW%P_o*ibWO53KY_#v`8ItC`h^nhor@R zgd!mwk_RWbPO%1uMeKLh(D-~jETu9^i_~G^X9CR(%%BcSvn_u}P=|$Ii8RTFg_$Fg z8gxS+hUXFC5m}Rbm=67@uY@T2i?sbk66C0#U!r-DEQLF;e4gyKXKJ|A6WbEt?5hSEf#T_CII;OKel~K`P$qXymMVqjcAv_gRG*)Oo*a zk)X~eGsAjb5V3325Z|JZo}?$)1-}WfLb~9W3<>Fiuwo@$vc@p;Yf1gaEmru@*vkZ=|&-H|()#G}?(dD@=?yDJRIM;_)=T z#knEw#~hsD+z@`uu^iORXxYm#M>pNggrkorHxrJo%uWCC*A#EMo5C+Sv*Z5I^ma7* q74yQ(rug!BJK^Xf%I$=s>vCJ%qXo|N;I@nzJ14WOcWM99>Hh;^5AWyz delta 8294 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 pUGmJL&h)IinQ`oW, + /// Include the set of UserSignature's encoded as bytes in the response. + /// + /// Defaults to false if not included + #[prost(bool, optional, tag = "8")] + pub signatures_bytes: ::core::option::Option, /// Include the sui.types.TransactionEffects message in the response. /// /// Defaults to true if not included @@ -91,6 +96,8 @@ pub struct GetTransactionResponse { pub transaction_bcs: ::core::option::Option, #[prost(message, optional, tag = "4")] pub signatures: ::core::option::Option, + #[prost(message, optional, tag = "11")] + pub signatures_bytes: ::core::option::Option, #[prost(message, optional, tag = "5")] pub effects: ::core::option::Option, #[prost(message, optional, tag = "6")] @@ -110,6 +117,11 @@ pub struct UserSignatures { pub signatures: ::prost::alloc::vec::Vec, } #[derive(Clone, PartialEq, ::prost::Message)] +pub struct UserSignaturesBytes { + #[prost(bytes = "bytes", repeated, tag = "1")] + pub signatures: ::prost::alloc::vec::Vec<::prost::bytes::Bytes>, +} +#[derive(Clone, PartialEq, ::prost::Message)] pub struct GetObjectRequest { #[prost(message, optional, tag = "1")] pub object_id: ::core::option::Option, @@ -400,9 +412,11 @@ pub struct ExecuteTransactionRequest { pub transaction: ::core::option::Option, #[prost(message, optional, tag = "2")] pub transaction_bcs: ::core::option::Option, - #[prost(message, repeated, tag = "3")] - pub signatures: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag = "3")] + pub signatures: ::core::option::Option, #[prost(message, optional, tag = "4")] + pub signatures_bytes: ::core::option::Option, + #[prost(message, optional, tag = "5")] pub options: ::core::option::Option, } #[derive(Clone, Copy, PartialEq, ::prost::Message)] diff --git a/crates/sui-rpc-api/src/proto/node.rs b/crates/sui-rpc-api/src/proto/node.rs index 94d867980fc4d..1e6893bd8b985 100644 --- a/crates/sui-rpc-api/src/proto/node.rs +++ b/crates/sui-rpc-api/src/proto/node.rs @@ -280,6 +280,7 @@ impl From for GetTransactionOptions { transaction, transaction_bcs, signatures, + signatures_bytes, effects, effects_bcs, events, @@ -290,6 +291,7 @@ impl From for GetTransactionOptions { transaction, transaction_bcs, signatures, + signatures_bytes, effects, effects_bcs, events, @@ -304,6 +306,7 @@ impl From for crate::types::GetTransactionOptions { transaction, transaction_bcs, signatures, + signatures_bytes, effects, effects_bcs, events, @@ -314,6 +317,7 @@ impl From for crate::types::GetTransactionOptions { transaction, transaction_bcs, signatures, + signatures_bytes, effects, effects_bcs, events, @@ -461,6 +465,7 @@ impl From for GetTransactionResponse { transaction, transaction_bcs, signatures, + signatures_bytes, effects, effects_bcs, events, @@ -473,11 +478,16 @@ impl From for GetTransactionResponse { signatures: signatures.into_iter().map(Into::into).collect(), }); + let signatures_bytes = signatures_bytes.map(|signatures| UserSignaturesBytes { + signatures: signatures.into_iter().map(Into::into).collect(), + }); + Self { digest: Some(digest.into()), transaction: transaction.map(Into::into), transaction_bcs: transaction_bcs.map(Into::into), signatures, + signatures_bytes, effects: effects.map(Into::into), effects_bcs: effects_bcs.map(Into::into), events: events.map(Into::into), @@ -497,6 +507,7 @@ impl TryFrom<&GetTransactionResponse> for crate::types::TransactionResponse { transaction, transaction_bcs, signatures, + signatures_bytes, effects, effects_bcs, events, @@ -524,6 +535,14 @@ impl TryFrom<&GetTransactionResponse> for crate::types::TransactionResponse { }) .transpose()?; + let signatures_bytes = signatures_bytes.as_ref().map(|signatures| { + signatures + .signatures + .iter() + .map(|bytes| bytes.to_vec()) + .collect() + }); + let effects = effects.as_ref().map(TryInto::try_into).transpose()?; let effects_bcs = effects_bcs.as_ref().map(Into::into); @@ -537,6 +556,7 @@ impl TryFrom<&GetTransactionResponse> for crate::types::TransactionResponse { transaction, transaction_bcs, signatures, + signatures_bytes, effects, effects_bcs, events, diff --git a/crates/sui-rpc-api/src/service/transactions/mod.rs b/crates/sui-rpc-api/src/service/transactions/mod.rs index 130874b074720..b43f17e736877 100644 --- a/crates/sui-rpc-api/src/service/transactions/mod.rs +++ b/crates/sui-rpc-api/src/service/transactions/mod.rs @@ -42,11 +42,19 @@ impl RpcService { .flatten() .transpose()?; + let signatures_bytes = options.include_signatures_bytes().then(|| { + signatures + .iter() + .map(|signature| signature.to_bytes()) + .collect() + }); + TransactionResponse { digest, transaction: options.include_transaction().then_some(transaction), transaction_bcs, signatures: options.include_signatures().then_some(signatures), + signatures_bytes, effects: options.include_effects().then_some(effects), effects_bcs, events: options.include_events().then_some(events).flatten(), diff --git a/crates/sui-rpc-api/src/types.rs b/crates/sui-rpc-api/src/types.rs index f7c1345750d5b..1e2c361c31843 100644 --- a/crates/sui-rpc-api/src/types.rs +++ b/crates/sui-rpc-api/src/types.rs @@ -216,6 +216,11 @@ pub struct TransactionResponse { #[serde(skip_serializing_if = "Option::is_none")] pub signatures: Option>, + #[serde_as(as = "Option>")] + #[schemars(with = "Option>")] + #[serde(skip_serializing_if = "Option::is_none")] + pub signatures_bytes: Option>>, + #[serde(skip_serializing_if = "Option::is_none")] pub effects: Option, @@ -267,6 +272,12 @@ pub struct GetTransactionOptions { #[serde(skip_serializing_if = "Option::is_none")] pub signatures: Option, + /// Request `Vec` encoded as bytes be included in the response + /// + /// Defaults to `false` if not provided. + #[serde(skip_serializing_if = "Option::is_none")] + pub signatures_bytes: Option, + /// Request `TransactionEffects` be included in the response /// /// Defaults to `true` if not provided. @@ -305,6 +316,10 @@ impl GetTransactionOptions { self.signatures.unwrap_or(true) } + pub fn include_signatures_bytes(&self) -> bool { + self.signatures.unwrap_or(false) + } + pub fn include_effects(&self) -> bool { self.effects.unwrap_or(true) } From e6414a9a098b32c3104c432ffeda45c8e43de342 Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Mon, 16 Dec 2024 11:55:08 -0600 Subject: [PATCH 06/22] rpc: use protox to build proto files Use protox to build proto files so that the fds' can have a deterministic serialized format for comparison in CI. --- Cargo.lock | 108 +++++++++++++----- crates/sui-rpc-api/Cargo.toml | 2 +- .../src/proto/generated/sui.node.v2.fds.bin | Bin 100037 -> 38210 bytes crates/sui-rpc-api/tests/bootstrap.rs | 25 +++- 4 files changed, 102 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 924e0fd25c4af..0be7c0d3cf709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2486,7 +2486,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -7367,12 +7367,13 @@ dependencies = [ [[package]] name = "miette" -version = "7.0.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a72adfa0c7ae88ba0abcbd00047a476616c66b831d628b8ac7f1e9de0cfd67" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" dependencies = [ "backtrace", "backtrace-ext", + "cfg-if", "miette-derive", "owo-colors 4.0.0", "supports-color", @@ -7386,9 +7387,9 @@ dependencies = [ [[package]] name = "miette-derive" -version = "7.0.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279def6bf114a34b3cf887489eb440d4dfcf709ab3ce9955e4a6f957ce5cce77" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2 1.0.87", "quote 1.0.37", @@ -10289,6 +10290,19 @@ dependencies = [ "syn 2.0.79", ] +[[package]] +name = "prost-reflect" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20ae544fca2892fd4b7e9ff26cba1090cedf1d4d95c2aded1af15d2f93f270b8" +dependencies = [ + "logos", + "miette", + "once_cell", + "prost 0.13.3", + "prost-types 0.13.3", +] + [[package]] name = "prost-types" version = "0.12.3" @@ -10316,6 +10330,33 @@ dependencies = [ "bytes", ] +[[package]] +name = "protox" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "873f359bdecdfe6e353752f97cb9ee69368df55b16363ed2216da85e03232a58" +dependencies = [ + "bytes", + "miette", + "prost 0.13.3", + "prost-reflect", + "prost-types 0.13.3", + "protox-parse", + "thiserror", +] + +[[package]] +name = "protox-parse" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a462d115462c080ae000c29a47f0b3985737e5d3a995fcdbcaa5c782068dde" +dependencies = [ + "logos", + "miette", + "prost-types 0.13.3", + "thiserror", +] + [[package]] name = "psm" version = "0.1.21" @@ -15028,8 +15069,8 @@ dependencies = [ "prometheus", "proptest", "prost 0.13.3", - "prost-build", "prost-types 0.13.3", + "protox", "rand 0.8.5", "reqwest 0.12.5", "roaring", @@ -17964,7 +18005,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", ] [[package]] @@ -17999,17 +18040,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -18026,9 +18068,9 @@ checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -18044,9 +18086,9 @@ checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -18062,9 +18104,15 @@ checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -18080,9 +18128,9 @@ checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -18098,9 +18146,9 @@ checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -18116,9 +18164,9 @@ checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -18134,9 +18182,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" diff --git a/crates/sui-rpc-api/Cargo.toml b/crates/sui-rpc-api/Cargo.toml index bdddd9c25f2af..1c03e8c3b753c 100644 --- a/crates/sui-rpc-api/Cargo.toml +++ b/crates/sui-rpc-api/Cargo.toml @@ -49,7 +49,7 @@ tonic-reflection.workspace = true [dev-dependencies] diffy = "0.3" -prost-build = "0.13.3" +protox = "0.7" tonic-build = "0.12.3" sui-sdk-types = { workspace = true, features = ["proptest"] } test-strategy = "0.4.0" diff --git a/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin b/crates/sui-rpc-api/src/proto/generated/sui.node.v2.fds.bin index ae6b1f91bbcbf768b635c16ef3a771699cb393ce..a164c2dd18ba188a12f418170a520a43427f26e9 100644 GIT binary patch delta 79 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 dS59v2rwZj%`<_b70ZU-=@2O Date: Mon, 16 Dec 2024 15:07:17 -0800 Subject: [PATCH 07/22] change all checkpoints.mainnet|testnet.sui.io references to gcp endpoint (#20634) --- crates/sui-bridge-indexer/README.md | 2 +- .../developer/advanced/custom-indexer.mdx | 8 ++++---- examples/custom-indexer/rust/remote_reader.rs | 18 +++++++++++------- nre/helm/indexer/values.reader.yaml | 2 +- nre/helm/indexer/values.writer.yaml | 2 +- nre/helm/indexer/values.yaml | 2 +- 6 files changed, 19 insertions(+), 15 deletions(-) diff --git a/crates/sui-bridge-indexer/README.md b/crates/sui-bridge-indexer/README.md index c6ce103c0d73e..eea6cb4f5bfce 100644 --- a/crates/sui-bridge-indexer/README.md +++ b/crates/sui-bridge-indexer/README.md @@ -21,7 +21,7 @@ bridge-indexer --config-path config.yaml ```yaml --- -remote_store_url: https://checkpoints.mainnet.sui.io +remote_store_url: https://storage.googleapis/mysten-mainnet-checkpoints eth_rpc_url: {eth rpc url} sui_rpc_url: {sui rpc url} diff --git a/docs/content/guides/developer/advanced/custom-indexer.mdx b/docs/content/guides/developer/advanced/custom-indexer.mdx index 5db814da1e1cd..24561b4084608 100644 --- a/docs/content/guides/developer/advanced/custom-indexer.mdx +++ b/docs/content/guides/developer/advanced/custom-indexer.mdx @@ -34,10 +34,10 @@ Data ingestion for your indexer supports several checkpoint stream sources. The most straightforward stream source is to subscribe to a remote store of checkpoint contents. Mysten Labs provides the following buckets: -- Testnet: `https://checkpoints.testnet.sui.io` -- Mainnet: `https://checkpoints.mainnet.sui.io` +- Testnet: `https://storage.googleapis.com/mysten-testnet-checkpoints` +- Mainnet: `https://storage.googleapis.com/mysten-mainnet-checkpoints` -The checkpoint files are stored in the following format: `https://checkpoints.testnet.sui.io/.chk`. You can download the checkpoint file by sending an HTTP GET request to the relevant URL. Try it yourself for checkpoint 1 at [https://checkpoints.testnet.sui.io/1.chk](https://checkpoints.testnet.sui.io/1.chk). +The checkpoint files are stored in the following format: `https://storage.googleapis.com/mysten-testnet-checkpoints/.chk`. You can download the checkpoint file by sending an HTTP GET request to the relevant URL. Try it yourself for checkpoint 1 at [https://storage.googleapis/mysten-testnet-checkpoints/1.chk](https://storage.googleapis/mysten-testnet-checkpoints/1.chk). ```mermaid flowchart LR @@ -112,7 +112,7 @@ Specify both a local and remote store as a fallback to ensure constant data flow ```rust executor.run( PathBuf::from("./chk".to_string()), // path to a local directory - Some("https://checkpoints.testnet.sui.io".to_string()), // Remote Checkpoint Store + Some("https://storage.googleapis/mysten-testnet-checkpoints".to_string()), // Remote Checkpoint Store vec![], // optional remote store access options ReaderOptions::default(), exit_receiver, diff --git a/examples/custom-indexer/rust/remote_reader.rs b/examples/custom-indexer/rust/remote_reader.rs index 65cd99f5c32ff..53f4b0a299978 100644 --- a/examples/custom-indexer/rust/remote_reader.rs +++ b/examples/custom-indexer/rust/remote_reader.rs @@ -3,8 +3,8 @@ use anyhow::Result; use async_trait::async_trait; +use sui_data_ingestion_core::{setup_single_workflow, Worker}; use sui_types::full_checkpoint_content::CheckpointData; -use sui_data_ingestion_core::{Worker, setup_single_workflow}; struct CustomWorker; @@ -14,20 +14,24 @@ impl Worker for CustomWorker { async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result<()> { // custom processing logic // print out the checkpoint number - println!("Processing checkpoint: {}", checkpoint.checkpoint_summary.to_string()); + println!( + "Processing checkpoint: {}", + checkpoint.checkpoint_summary.to_string() + ); Ok(()) } } - + #[tokio::main] async fn main() -> Result<()> { let (executor, term_sender) = setup_single_workflow( CustomWorker, - "https://checkpoints.testnet.sui.io".to_string(), - 0, /* initial checkpoint number */ - 5, /* concurrency */ + "https://storage.googleapis.com/mysten-testnet-checkpoints".to_string(), + 0, /* initial checkpoint number */ + 5, /* concurrency */ None, /* extra reader options */ - ).await?; + ) + .await?; executor.await?; Ok(()) } diff --git a/nre/helm/indexer/values.reader.yaml b/nre/helm/indexer/values.reader.yaml index b4afa0c734399..43ffc5acedd87 100644 --- a/nre/helm/indexer/values.reader.yaml +++ b/nre/helm/indexer/values.reader.yaml @@ -2,7 +2,7 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. -args: ["--db-url", "$(DB_URL)", "--rpc-client-url", "https://public-rpc.mainnet.sui.io", "--remote-store-url", "https://checkpoints.mainnet.sui.io", "--rpc-server-worker"] +args: ["--db-url", "$(DB_URL)", "--rpc-client-url", "https://public-rpc.mainnet.sui.io", "--remote-store-url", "https://storage.googleapis/mysten-mainnet-checkpoints", "--rpc-server-worker"] podAnnotations: app: sui-indexer-reader diff --git a/nre/helm/indexer/values.writer.yaml b/nre/helm/indexer/values.writer.yaml index 0cc286c4b4165..b09c19c05f028 100644 --- a/nre/helm/indexer/values.writer.yaml +++ b/nre/helm/indexer/values.writer.yaml @@ -2,7 +2,7 @@ # This is a YAML-formatted file. # Declare variables to be passed into your templates. -args: ["--db-url", "$(DB_URL)", "--rpc-client-url", "https://public-rpc.mainnet.sui.io", "--remote-store-url", "https://checkpoints.mainnet.sui.io", "--fullnode-sync-worker"] +args: ["--db-url", "$(DB_URL)", "--rpc-client-url", "https://public-rpc.mainnet.sui.io", "--remote-store-url", "https://storage.googleapis/mysten-mainnet-checkpoints", "--fullnode-sync-worker"] # only 1 writer needed replicaCount: 1 diff --git a/nre/helm/indexer/values.yaml b/nre/helm/indexer/values.yaml index 1ddeafcf94a66..c84df72e827e2 100644 --- a/nre/helm/indexer/values.yaml +++ b/nre/helm/indexer/values.yaml @@ -30,7 +30,7 @@ env: RPC_CLIENT: value: "https://public-rpc.mainnet.sui.io" REMOTE_STORE: - value: "https://checkpoints.mainnet.sui.io" + value: "https://storage.googleapis/mysten-mainnet-checkpoints" DB_URL: valueFrom: secretKeyRef: From a1c79df65c92db7abcf802580d0af8465b94cfaf Mon Sep 17 00:00:00 2001 From: William Smith Date: Mon, 16 Dec 2024 20:25:43 -0500 Subject: [PATCH 08/22] [tests] Fix expected failure type random selector (#20652) ## Description The change of this code block from `Ok(rand::random())` to a custom selector logic overlooked the fact that we must avoid re-selecting variant `0`, as variant `0` is the random variant, causing the code to hit an `unreachable` block meant to avoid infinte recursion. ## Test plan Re-run test --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- crates/sui-benchmark/src/workloads/workload.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sui-benchmark/src/workloads/workload.rs b/crates/sui-benchmark/src/workloads/workload.rs index 9d9f766f48157..739da005849be 100644 --- a/crates/sui-benchmark/src/workloads/workload.rs +++ b/crates/sui-benchmark/src/workloads/workload.rs @@ -47,7 +47,7 @@ impl TryFrom for ExpectedFailureType { match value { 0 => { let mut rng = rand::thread_rng(); - let n = rng.gen_range(0..ExpectedFailureType::COUNT - 1); + let n = rng.gen_range(1..ExpectedFailureType::COUNT - 1); Ok(ExpectedFailureType::iter().nth(n).unwrap()) } _ => ExpectedFailureType::iter() From 53dc8023773f9da045dd2191e84c520f2a7b5327 Mon Sep 17 00:00:00 2001 From: King Date: Tue, 17 Dec 2024 12:13:28 +0800 Subject: [PATCH 09/22] add build_mainnet for easy use sui-sdk (#20640) ## Description This PR adds a new function `build_mainnet` corresponding to the mainnet RPC node for the `SuiClientBuilder` type. ## Test plan How did you test the new or updated feature? ```rust let sui_mainnet = SuiClientBuilder::default().build_mainnet().await?; println!("Sui mainnet version: {}", sui_mainnet.api_version()); ``` --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [x] Rust SDK: Added a `build_mainnet` function to the `SuiClientBuilder`. - [ ] REST API: --- crates/sui-sdk/README.md | 4 ++++ crates/sui-sdk/examples/sui_client.rs | 11 +++++++++-- crates/sui-sdk/src/lib.rs | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/sui-sdk/README.md b/crates/sui-sdk/README.md index fc33635a66140..b39d6c976f0a8 100644 --- a/crates/sui-sdk/README.md +++ b/crates/sui-sdk/README.md @@ -27,6 +27,10 @@ async fn main() -> Result<(), anyhow::Error> { let sui_devnet = SuiClientBuilder::default().build_devnet().await?; println!("Sui devnet version: {}", sui_devnet.api_version()); + // Sui mainnet -- https://fullnode.mainnet.sui.io:443 + let sui_mainnet = SuiClientBuilder::default().build_mainnet().await?; + println!("Sui mainnet version: {}", sui_mainnet.api_version()); + Ok(()) } diff --git a/crates/sui-sdk/examples/sui_client.rs b/crates/sui-sdk/examples/sui_client.rs index 2d4a3c7d4c4c0..7813cf5f0d6df 100644 --- a/crates/sui-sdk/examples/sui_client.rs +++ b/crates/sui-sdk/examples/sui_client.rs @@ -32,8 +32,15 @@ async fn main() -> Result<(), anyhow::Error> { let sui_testnet = SuiClientBuilder::default().build_testnet().await?; println!("Sui testnet version: {}", sui_testnet.api_version()); - println!("{:?}", sui_local.available_rpc_methods()); - println!("{:?}", sui_local.available_subscriptions()); + // Sui mainnet -- https://fullnode.mainnet.sui.io:443 + let sui_mainnet = SuiClientBuilder::default().build_mainnet().await?; + println!("Sui mainnet version: {}", sui_mainnet.api_version()); + + println!("rpc methods: {:?}", sui_testnet.available_rpc_methods()); + println!( + "available subscriptions: {:?}", + sui_testnet.available_subscriptions() + ); Ok(()) } diff --git a/crates/sui-sdk/src/lib.rs b/crates/sui-sdk/src/lib.rs index 9200d8451e5fc..2ac15a4bfd021 100644 --- a/crates/sui-sdk/src/lib.rs +++ b/crates/sui-sdk/src/lib.rs @@ -112,6 +112,7 @@ pub const SUI_LOCAL_NETWORK_URL_0: &str = "http://0.0.0.0:9000"; pub const SUI_LOCAL_NETWORK_GAS_URL: &str = "http://127.0.0.1:5003/gas"; pub const SUI_DEVNET_URL: &str = "https://fullnode.devnet.sui.io:443"; pub const SUI_TESTNET_URL: &str = "https://fullnode.testnet.sui.io:443"; +pub const SUI_MAINNET_URL: &str = "https://fullnode.mainnet.sui.io:443"; /// A Sui client builder for connecting to the Sui network /// @@ -344,6 +345,29 @@ impl SuiClientBuilder { self.build(SUI_TESTNET_URL).await } + /// Returns a [SuiClient] object that is ready to interact with the Sui mainnet. + /// + /// For connecting to a custom URI, use the `build` function instead. + /// + /// # Examples + /// + /// ```rust,no_run + /// use sui_sdk::SuiClientBuilder; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), anyhow::Error> { + /// let sui = SuiClientBuilder::default() + /// .build_mainnet() + /// .await?; + /// + /// println!("{:?}", sui.api_version()); + /// Ok(()) + /// } + /// ``` + pub async fn build_mainnet(self) -> SuiRpcResult { + self.build(SUI_MAINNET_URL).await + } + /// Return the server information as a `ServerInfo` structure. /// /// Fails with an error if it cannot call the RPC discover. From 29f17da5e070113d9d624ec265aa5155829bac9a Mon Sep 17 00:00:00 2001 From: Brandon Williams Date: Tue, 17 Dec 2024 10:38:53 -0600 Subject: [PATCH 10/22] jsonrpc: correctly serialize bcsName field Correctly serialize bcsName field which was unintentially changed to `bcs_name` in 8a992f0f7f (jsonrpc: introduce bcsEncoding tag to ease migration to base64, 2024-12-12) --- crates/sui-json-rpc-types/src/lib.rs | 14 ++++++++++++++ crates/sui-json-rpc-types/src/sui_event.rs | 12 ++++++++++++ crates/sui-open-rpc/spec/openrpc.json | 14 +++++++------- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/crates/sui-json-rpc-types/src/lib.rs b/crates/sui-json-rpc-types/src/lib.rs index 3e4b17f242417..8e5b40d31a825 100644 --- a/crates/sui-json-rpc-types/src/lib.rs +++ b/crates/sui-json-rpc-types/src/lib.rs @@ -104,11 +104,13 @@ pub enum BcsName { Base64 { #[serde_as(as = "Base64")] #[schemars(with = "Base64")] + #[serde(rename = "bcsName")] bcs_name: Vec, }, Base58 { #[serde_as(as = "Base58")] #[schemars(with = "Base58")] + #[serde(rename = "bcsName")] bcs_name: Vec, }, } @@ -212,5 +214,17 @@ mod test { .unwrap() .into_bytes() ); + + // Roundtrip base64 + let name = serde_json::from_str::(tagged_base64).unwrap(); + let json = serde_json::to_string(&name).unwrap(); + let from_json = serde_json::from_str::(&json).unwrap(); + assert_eq!(name, from_json); + + // Roundtrip base58 + let name = serde_json::from_str::(tagged_base58).unwrap(); + let json = serde_json::to_string(&name).unwrap(); + let from_json = serde_json::from_str::(&json).unwrap(); + assert_eq!(name, from_json); } } diff --git a/crates/sui-json-rpc-types/src/sui_event.rs b/crates/sui-json-rpc-types/src/sui_event.rs index eb9aace4ac409..abb84301f0353 100644 --- a/crates/sui-json-rpc-types/src/sui_event.rs +++ b/crates/sui-json-rpc-types/src/sui_event.rs @@ -403,5 +403,17 @@ mod test { .unwrap() .into_bytes() ); + + // Roundtrip base64 + let event = serde_json::from_str::(tagged_base64).unwrap(); + let json = serde_json::to_string(&event).unwrap(); + let from_json = serde_json::from_str::(&json).unwrap(); + assert_eq!(event, from_json); + + // Roundtrip base58 + let event = serde_json::from_str::(tagged_base58).unwrap(); + let json = serde_json::to_string(&event).unwrap(); + let from_json = serde_json::from_str::(&json).unwrap(); + assert_eq!(event, from_json); } } diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index 027082a9bc8df..3ebaf3efbb2a3 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -3557,7 +3557,7 @@ "value": "some_value" }, "bcsEncoding": "base64", - "bcs_name": "FDB4OTo6dGVzdDo6VGVzdEZpZWxk", + "bcsName": "FDB4OTo6dGVzdDo6VGVzdEZpZWxk", "type": "DynamicField", "objectType": "test", "objectId": "0x82b2fd67344691abd0efc771941b948ad35360b08e449fbbc28b0641175bf60b", @@ -3570,7 +3570,7 @@ "value": "some_value" }, "bcsEncoding": "base64", - "bcs_name": "FDB4OTo6dGVzdDo6VGVzdEZpZWxk", + "bcsName": "FDB4OTo6dGVzdDo6VGVzdEZpZWxk", "type": "DynamicField", "objectType": "test", "objectId": "0x21564fc5a68ace997461b098c1d1f3ccbde241d8fdf562db36bc1423ee10cecb", @@ -3583,7 +3583,7 @@ "value": "some_value" }, "bcsEncoding": "base64", - "bcs_name": "FDB4OTo6dGVzdDo6VGVzdEZpZWxk", + "bcsName": "FDB4OTo6dGVzdDo6VGVzdEZpZWxk", "type": "DynamicField", "objectType": "test", "objectId": "0x7e00acf5386662fa062483ba507b1e9e3039750f0a270f2e12441ad7f611a5f7", @@ -5964,7 +5964,7 @@ "type": "object", "required": [ "bcsEncoding", - "bcs_name" + "bcsName" ], "properties": { "bcsEncoding": { @@ -5973,7 +5973,7 @@ "base64" ] }, - "bcs_name": { + "bcsName": { "$ref": "#/components/schemas/Base64" } } @@ -5982,7 +5982,7 @@ "type": "object", "required": [ "bcsEncoding", - "bcs_name" + "bcsName" ], "properties": { "bcsEncoding": { @@ -5991,7 +5991,7 @@ "base58" ] }, - "bcs_name": { + "bcsName": { "$ref": "#/components/schemas/Base58" } } From cb5dd8e4e458e315e70b0a2b43fb019148cf37e8 Mon Sep 17 00:00:00 2001 From: Ge Gao <106119108+gegaowp@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:29:47 -0500 Subject: [PATCH 11/22] snapshot restore: add option to reuse downloaded files (#20451) ## Description title, this can be problematic when a file is half-downloaded but useful when all file downloading is complete, which helps to speed up iteration over restore, as each time the .ref files download would take about 30min ## Test plan local run restorer and verify that it can reuse downloaded files first run ``` 104 out of 673 missing .ref files done ``` second run ``` 8 out of 569 missing .ref files done ``` --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- .../src/restorer/formal_snapshot.rs | 13 +----- .../src/restorer/formal_snapshot.rs | 1 + crates/sui-snapshot/src/reader.rs | 43 ++++++++++++++----- crates/sui-snapshot/src/tests.rs | 2 + crates/sui-tool/src/lib.rs | 1 + 5 files changed, 38 insertions(+), 22 deletions(-) diff --git a/crates/sui-indexer/src/restorer/formal_snapshot.rs b/crates/sui-indexer/src/restorer/formal_snapshot.rs index bab43c7303f38..e6737c3ae315e 100644 --- a/crates/sui-indexer/src/restorer/formal_snapshot.rs +++ b/crates/sui-indexer/src/restorer/formal_snapshot.rs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use std::collections::BTreeMap; -use std::fs; use std::num::NonZeroUsize; use std::path::PathBuf; use std::sync::Arc; @@ -55,17 +54,6 @@ impl IndexerFormalSnapshotRestorer { let base_path = PathBuf::from(restore_config.snapshot_download_dir.clone()); let snapshot_dir = base_path.join("snapshot"); - if snapshot_dir.exists() { - fs::remove_dir_all(snapshot_dir.clone()).unwrap(); - info!( - "Deleted all files from snapshot directory: {:?}", - snapshot_dir - ); - } else { - fs::create_dir(snapshot_dir.clone()).unwrap(); - info!("Created snapshot directory: {:?}", snapshot_dir); - } - let local_store_config = ObjectStoreConfig { object_store: Some(ObjectStoreType::File), directory: Some(snapshot_dir.clone().to_path_buf()), @@ -80,6 +68,7 @@ impl IndexerFormalSnapshotRestorer { usize::MAX, NonZeroUsize::new(restore_config.object_store_concurrent_limit).unwrap(), m.clone(), + true, // skip_reset_local_store ) .await .unwrap_or_else(|err| panic!("Failed to create reader: {}", err)); diff --git a/crates/sui-mvr-indexer/src/restorer/formal_snapshot.rs b/crates/sui-mvr-indexer/src/restorer/formal_snapshot.rs index bab43c7303f38..c5a992212e2c8 100644 --- a/crates/sui-mvr-indexer/src/restorer/formal_snapshot.rs +++ b/crates/sui-mvr-indexer/src/restorer/formal_snapshot.rs @@ -80,6 +80,7 @@ impl IndexerFormalSnapshotRestorer { usize::MAX, NonZeroUsize::new(restore_config.object_store_concurrent_limit).unwrap(), m.clone(), + true, // skip_reset_local_store ) .await .unwrap_or_else(|err| panic!("Failed to create reader: {}", err)); diff --git a/crates/sui-snapshot/src/reader.rs b/crates/sui-snapshot/src/reader.rs index bdb4cd35f989c..befe6a857a42d 100644 --- a/crates/sui-snapshot/src/reader.rs +++ b/crates/sui-snapshot/src/reader.rs @@ -29,7 +29,7 @@ use sui_core::authority::AuthorityStore; use sui_storage::blob::{Blob, BlobEncoding}; use sui_storage::object_store::http::HttpDownloaderBuilder; use sui_storage::object_store::util::{copy_file, copy_files, path_to_filesystem}; -use sui_storage::object_store::{ObjectStoreGetExt, ObjectStorePutExt}; +use sui_storage::object_store::{ObjectStoreGetExt, ObjectStoreListExt, ObjectStorePutExt}; use sui_types::accumulator::Accumulator; use sui_types::base_types::{ObjectDigest, ObjectID, ObjectRef, SequenceNumber}; use tokio::sync::Mutex; @@ -61,6 +61,7 @@ impl StateSnapshotReaderV1 { indirect_objects_threshold: usize, download_concurrency: NonZeroUsize, m: MultiProgress, + skip_reset_local_store: bool, ) -> Result { let epoch_dir = format!("epoch_{}", epoch); let remote_object_store = if remote_store_config.no_sign_request { @@ -70,16 +71,20 @@ impl StateSnapshotReaderV1 { }; let local_object_store: Arc = local_store_config.make().map(Arc::new)?; + let local_object_store_list: Arc = + local_store_config.make().map(Arc::new)?; let local_staging_dir_root = local_store_config .directory .as_ref() .context("No directory specified")? .clone(); - let local_epoch_dir_path = local_staging_dir_root.join(&epoch_dir); - if local_epoch_dir_path.exists() { - fs::remove_dir_all(&local_epoch_dir_path)?; + if !skip_reset_local_store { + let local_epoch_dir_path = local_staging_dir_root.join(&epoch_dir); + if local_epoch_dir_path.exists() { + fs::remove_dir_all(&local_epoch_dir_path)?; + } + fs::create_dir_all(&local_epoch_dir_path)?; } - fs::create_dir_all(&local_epoch_dir_path)?; // Download MANIFEST first let manifest_file_path = Path::from(epoch_dir.clone()).child("MANIFEST"); copy_file( @@ -136,24 +141,42 @@ impl StateSnapshotReaderV1 { }) .collect(); + let files_to_download = if skip_reset_local_store { + let mut list_stream = local_object_store_list + .list_objects(Some(&epoch_dir_path)) + .await; + let mut existing_files = std::collections::HashSet::new(); + while let Some(Ok(meta)) = list_stream.next().await { + existing_files.insert(meta.location); + } + let mut missing_files = Vec::new(); + for file in &files { + if !existing_files.contains(file) { + missing_files.push(file.clone()); + } + } + missing_files + } else { + files + }; let progress_bar = m.add( - ProgressBar::new(files.len() as u64).with_style( + ProgressBar::new(files_to_download.len() as u64).with_style( ProgressStyle::with_template( - "[{elapsed_precise}] {wide_bar} {pos} out of {len} .ref files done ({msg})", + "[{elapsed_precise}] {wide_bar} {pos} out of {len} missing .ref files done ({msg})", ) .unwrap(), ), ); copy_files( - &files, - &files, + &files_to_download, + &files_to_download, &remote_object_store, &local_object_store, download_concurrency, Some(progress_bar.clone()), ) .await?; - progress_bar.finish_with_message("ref files download complete"); + progress_bar.finish_with_message("Missing ref files download complete"); Ok(StateSnapshotReaderV1 { epoch, local_staging_dir_root, diff --git a/crates/sui-snapshot/src/tests.rs b/crates/sui-snapshot/src/tests.rs index bd85731373c5e..4fd0f5fada8bf 100644 --- a/crates/sui-snapshot/src/tests.rs +++ b/crates/sui-snapshot/src/tests.rs @@ -112,6 +112,7 @@ async fn test_snapshot_basic() -> Result<(), anyhow::Error> { usize::MAX, NonZeroUsize::new(1).unwrap(), MultiProgress::new(), + false, // skip_reset_local_store ) .await?; let restored_perpetual_db = AuthorityPerpetualTables::open(&restored_db_path, None); @@ -167,6 +168,7 @@ async fn test_snapshot_empty_db() -> Result<(), anyhow::Error> { usize::MAX, NonZeroUsize::new(1).unwrap(), MultiProgress::new(), + false, // skip_reset_local_store ) .await?; let restored_perpetual_db = AuthorityPerpetualTables::open(&restored_db_path, None); diff --git a/crates/sui-tool/src/lib.rs b/crates/sui-tool/src/lib.rs index a9fe1198f0fa2..24cb716af3464 100644 --- a/crates/sui-tool/src/lib.rs +++ b/crates/sui-tool/src/lib.rs @@ -890,6 +890,7 @@ pub async fn download_formal_snapshot( usize::MAX, NonZeroUsize::new(num_parallel_downloads).unwrap(), m_clone, + false, // skip_reset_local_store ) .await .unwrap_or_else(|err| panic!("Failed to create reader: {}", err)); From 2e1773081fddb14b1abbc4352bf880f2ee9f443d Mon Sep 17 00:00:00 2001 From: Zhe Wu Date: Tue, 17 Dec 2024 11:07:07 -0800 Subject: [PATCH 12/22] Turn off randomized tx workload (#20660) Seems causing high error rate. ## Description Describe the changes or additions included in this PR. ## Test plan How did you test the new or updated feature? --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- crates/sui-benchmark/tests/simtest.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sui-benchmark/tests/simtest.rs b/crates/sui-benchmark/tests/simtest.rs index de18808a3af94..415a8e920e17c 100644 --- a/crates/sui-benchmark/tests/simtest.rs +++ b/crates/sui-benchmark/tests/simtest.rs @@ -1012,7 +1012,7 @@ mod test { shared_deletion_weight: 1, shared_counter_hotness_factor: 50, randomness_weight: 1, - randomized_transaction_weight: 1, + randomized_transaction_weight: 0, num_shared_counters: Some(1), use_shared_counter_max_tip: false, shared_counter_max_tip: 0, From d2a9cfe0ec142bea3ce627c09421652810a966e3 Mon Sep 17 00:00:00 2001 From: Xun Li Date: Mon, 16 Dec 2024 14:30:15 -0800 Subject: [PATCH 13/22] [indexer-alt] Add CheckpointLagMetricReporter --- .../src/ingestion/client.rs | 41 +++++------- .../sui-indexer-alt-framework/src/metrics.rs | 63 ++++++++++++++++++- .../src/pipeline/processor.rs | 34 ++++------ 3 files changed, 88 insertions(+), 50 deletions(-) diff --git a/crates/sui-indexer-alt-framework/src/ingestion/client.rs b/crates/sui-indexer-alt-framework/src/ingestion/client.rs index 82dded7f3bf05..25fac78a162ba 100644 --- a/crates/sui-indexer-alt-framework/src/ingestion/client.rs +++ b/crates/sui-indexer-alt-framework/src/ingestion/client.rs @@ -5,12 +5,12 @@ use crate::ingestion::local_client::LocalIngestionClient; use crate::ingestion::remote_client::RemoteIngestionClient; use crate::ingestion::Error as IngestionError; use crate::ingestion::Result as IngestionResult; +use crate::metrics::CheckpointLagMetricReporter; use crate::metrics::IndexerMetrics; use backoff::backoff::Constant; use backoff::Error as BE; use backoff::ExponentialBackoff; use std::path::PathBuf; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use std::time::Duration; use sui_storage::blob::Blob; @@ -49,27 +49,30 @@ pub struct IngestionClient { client: Arc, /// Wrap the metrics in an `Arc` to keep copies of the client cheap. metrics: Arc, - latest_ingested_checkpoint: Arc, + checkpoint_lag_reporter: Arc, } impl IngestionClient { pub(crate) fn new_remote(url: Url, metrics: Arc) -> IngestionResult { let client = Arc::new(RemoteIngestionClient::new(url)?); - let latest_ingested_checkpoint = Arc::new(AtomicU64::new(0)); - Ok(IngestionClient { - client, - metrics, - latest_ingested_checkpoint, - }) + Ok(Self::new_impl(client, metrics)) } pub(crate) fn new_local(path: PathBuf, metrics: Arc) -> Self { let client = Arc::new(LocalIngestionClient::new(path)); - let latest_ingested_checkpoint = Arc::new(AtomicU64::new(0)); + Self::new_impl(client, metrics) + } + + fn new_impl(client: Arc, metrics: Arc) -> Self { + let checkpoint_lag_reporter = CheckpointLagMetricReporter::new( + metrics.ingested_checkpoint_timestamp_lag.clone(), + metrics.latest_ingested_checkpoint_timestamp_lag_ms.clone(), + metrics.latest_ingested_checkpoint.clone(), + ); IngestionClient { client, metrics, - latest_ingested_checkpoint, + checkpoint_lag_reporter, } } @@ -172,22 +175,8 @@ impl IngestionClient { "Fetched checkpoint" ); - let lag = - chrono::Utc::now().timestamp_millis() - data.checkpoint_summary.timestamp_ms as i64; - self.metrics - .ingested_checkpoint_timestamp_lag - .observe((lag as f64) / 1000.0); - - let new_seq = data.checkpoint_summary.sequence_number; - let old_seq = self - .latest_ingested_checkpoint - .fetch_max(new_seq, Ordering::Relaxed); - if new_seq > old_seq { - self.metrics.latest_ingested_checkpoint.set(new_seq as i64); - self.metrics - .latest_ingested_checkpoint_timestamp_lag_ms - .set(lag); - } + self.checkpoint_lag_reporter + .report_lag(checkpoint, data.checkpoint_summary.timestamp_ms); self.metrics.total_ingested_checkpoints.inc(); diff --git a/crates/sui-indexer-alt-framework/src/metrics.rs b/crates/sui-indexer-alt-framework/src/metrics.rs index eca0d1b3842ca..dee1313b82ca7 100644 --- a/crates/sui-indexer-alt-framework/src/metrics.rs +++ b/crates/sui-indexer-alt-framework/src/metrics.rs @@ -1,7 +1,10 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::{net::SocketAddr, sync::Arc}; +use std::{ + net::SocketAddr, + sync::{atomic::AtomicU64, Arc}, +}; use anyhow::Result; use axum::{extract::Extension, http::StatusCode, routing::get, Router}; @@ -18,7 +21,7 @@ use tokio::{net::TcpListener, task::JoinHandle}; use tokio_util::sync::CancellationToken; use tracing::{info, warn}; -use crate::ingestion::error::Error; +use crate::{ingestion::error::Error, pipeline::Processor}; /// Histogram buckets for the distribution of checkpoint fetching latencies. const INGESTION_LATENCY_SEC_BUCKETS: &[f64] = &[ @@ -130,6 +133,20 @@ struct DbConnectionStatsCollector { desc: Vec<(MetricType, Desc)>, } +/// A helper struct to report metrics regarding the checkpoint lag at various points in the indexer. +pub(crate) struct CheckpointLagMetricReporter { + /// Metric to report the lag distribution of each checkpoint. + checkpoint_time_lag_histogram: Histogram, + /// Metric to report the lag of the checkpoint with the highest sequence number observed so far. + /// This is needed since concurrent pipelines observe checkpoints out of order. + latest_checkpoint_time_lag_gauge: IntGauge, + /// Metric to report the sequence number of the checkpoint with the highest sequence number observed so far. + latest_checkpoint_sequence_number_gauge: IntGauge, + + // Internal state to keep track of the highest checkpoint sequence number reported so far. + latest_reported_checkpoint: AtomicU64, +} + impl MetricsService { /// Create a new metrics service, exposing Mysten-wide metrics, and Indexer-specific metrics. /// Returns the Indexer-specific metrics and the service itself (which must be run with @@ -663,6 +680,48 @@ impl Collector for DbConnectionStatsCollector { } } +impl CheckpointLagMetricReporter { + pub fn new( + checkpoint_time_lag_histogram: Histogram, + latest_checkpoint_time_lag_gauge: IntGauge, + latest_checkpoint_sequence_number_gauge: IntGauge, + ) -> Arc { + Arc::new(Self { + checkpoint_time_lag_histogram, + latest_checkpoint_time_lag_gauge, + latest_checkpoint_sequence_number_gauge, + latest_reported_checkpoint: AtomicU64::new(0), + }) + } + + pub fn new_for_pipeline( + checkpoint_time_lag_histogram: &HistogramVec, + latest_checkpoint_time_lag_gauge: &IntGaugeVec, + latest_checkpoint_sequence_number_gauge: &IntGaugeVec, + ) -> Arc { + Self::new( + checkpoint_time_lag_histogram.with_label_values(&[P::NAME]), + latest_checkpoint_time_lag_gauge.with_label_values(&[P::NAME]), + latest_checkpoint_sequence_number_gauge.with_label_values(&[P::NAME]), + ) + } + + pub fn report_lag(&self, cp_sequence_number: u64, checkpoint_timestamp_ms: u64) { + let lag = chrono::Utc::now().timestamp_millis() - checkpoint_timestamp_ms as i64; + self.checkpoint_time_lag_histogram + .observe((lag as f64) / 1000.0); + + let prev = self + .latest_reported_checkpoint + .fetch_max(cp_sequence_number, std::sync::atomic::Ordering::Relaxed); + if cp_sequence_number > prev { + self.latest_checkpoint_sequence_number_gauge + .set(cp_sequence_number as i64); + self.latest_checkpoint_time_lag_gauge.set(lag); + } + } +} + /// Route handler for metrics service async fn metrics(Extension(registry): Extension) -> (StatusCode, String) { match TextEncoder.encode_to_string(®istry.gather()) { diff --git a/crates/sui-indexer-alt-framework/src/pipeline/processor.rs b/crates/sui-indexer-alt-framework/src/pipeline/processor.rs index 10a20969d9721..2c7c23d999b7e 100644 --- a/crates/sui-indexer-alt-framework/src/pipeline/processor.rs +++ b/crates/sui-indexer-alt-framework/src/pipeline/processor.rs @@ -1,7 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::sync::atomic::AtomicU64; use std::sync::Arc; use sui_types::full_checkpoint_content::CheckpointData; @@ -10,7 +9,11 @@ use tokio_stream::wrappers::ReceiverStream; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info}; -use crate::{metrics::IndexerMetrics, pipeline::Break, task::TrySpawnStreamExt}; +use crate::{ + metrics::{CheckpointLagMetricReporter, IndexerMetrics}, + pipeline::Break, + task::TrySpawnStreamExt, +}; use super::IndexedCheckpoint; @@ -49,7 +52,11 @@ pub(super) fn processor( ) -> JoinHandle<()> { tokio::spawn(async move { info!(pipeline = P::NAME, "Starting processor"); - let latest_processed_checkpoint = Arc::new(AtomicU64::new(0)); + let checkpoint_lag_reporter = CheckpointLagMetricReporter::new_for_pipeline::

( + &metrics.processed_checkpoint_timestamp_lag, + &metrics.latest_processed_checkpoint_timestamp_lag_ms, + &metrics.latest_processed_checkpoint, + ); let processor = Arc::new(processor); match ReceiverStream::new(rx) @@ -57,7 +64,7 @@ pub(super) fn processor( let tx = tx.clone(); let metrics = metrics.clone(); let cancel = cancel.clone(); - let latest_processed_checkpoint = latest_processed_checkpoint.clone(); + let checkpoint_lag_reporter = checkpoint_lag_reporter.clone(); let processor = processor.clone(); async move { @@ -90,24 +97,7 @@ pub(super) fn processor( "Processed checkpoint", ); - let lag = chrono::Utc::now().timestamp_millis() - timestamp_ms as i64; - metrics - .processed_checkpoint_timestamp_lag - .with_label_values(&[P::NAME]) - .observe((lag as f64) / 1000.0); - - let prev = latest_processed_checkpoint - .fetch_max(cp_sequence_number, std::sync::atomic::Ordering::Relaxed); - if cp_sequence_number > prev { - metrics - .latest_processed_checkpoint - .with_label_values(&[P::NAME]) - .set(cp_sequence_number as i64); - metrics - .latest_processed_checkpoint_timestamp_lag_ms - .with_label_values(&[P::NAME]) - .set(lag); - } + checkpoint_lag_reporter.report_lag(cp_sequence_number, timestamp_ms); metrics .total_handler_checkpoints_processed From 5577878bd585c20ec9ed03004e12362a4dbf1145 Mon Sep 17 00:00:00 2001 From: Xun Li Date: Mon, 16 Dec 2024 15:23:54 -0800 Subject: [PATCH 14/22] [indexer-alt] Add more checkpoint lag metrics --- .../sui-indexer-alt-framework/src/metrics.rs | 86 +++++++++++++++++++ .../src/pipeline/concurrent/collector.rs | 12 ++- .../pipeline/concurrent/commit_watermark.rs | 18 ++-- .../src/pipeline/concurrent/committer.rs | 19 +++- .../src/pipeline/mod.rs | 4 + 5 files changed, 131 insertions(+), 8 deletions(-) diff --git a/crates/sui-indexer-alt-framework/src/metrics.rs b/crates/sui-indexer-alt-framework/src/metrics.rs index dee1313b82ca7..3afe99d3e5925 100644 --- a/crates/sui-indexer-alt-framework/src/metrics.rs +++ b/crates/sui-indexer-alt-framework/src/metrics.rs @@ -71,6 +71,7 @@ pub(crate) struct IndexerMetrics { pub total_ingested_transient_retries: IntCounterVec, pub total_ingested_not_found_retries: IntCounter, + // Checkpoint lag metrics for the ingestion pipeline. pub latest_ingested_checkpoint: IntGauge, pub latest_ingested_checkpoint_timestamp_lag_ms: IntGauge, pub ingested_checkpoint_timestamp_lag: Histogram, @@ -102,6 +103,26 @@ pub(crate) struct IndexerMetrics { pub total_pruner_chunks_deleted: IntCounterVec, pub total_pruner_rows_deleted: IntCounterVec, + // Checkpoint lag metrics for the collector. + pub latest_collected_checkpoint: IntGaugeVec, + pub latest_collected_checkpoint_timestamp_lag_ms: IntGaugeVec, + pub collected_checkpoint_timestamp_lag: HistogramVec, + + // Checkpoint lag metrics for the committer. + // We can only report partially committed checkpoints, since the concurrent committer isn't aware of + // when a checkpoint is fully committed. So we report whenever we see a checkpoint. Since data from + // the same checkpoint is batched continuously, this is a good proxy for the last committed checkpoint. + pub latest_partially_committed_checkpoint: IntGaugeVec, + pub latest_partially_committed_checkpoint_timestamp_lag_ms: IntGaugeVec, + pub partially_committed_checkpoint_timestamp_lag: HistogramVec, + + // Checkpoint lag metrics for the watermarker. + // The latest watermarked checkpoint metric is already covered by watermark_checkpoint_in_db. + // While we already have watermark_timestamp_in_db_ms metric, reporting the lag explicitly + // for consistency. + pub latest_watermarked_checkpoint_timestamp_lag_ms: IntGaugeVec, + pub watermarked_checkpoint_timestamp_lag: HistogramVec, + pub collector_gather_latency: HistogramVec, pub collector_batch_size: HistogramVec, pub committer_commit_latency: HistogramVec, @@ -409,6 +430,71 @@ impl IndexerMetrics { registry, ) .unwrap(), + latest_collected_checkpoint: register_int_gauge_vec_with_registry!( + "indexer_latest_collected_checkpoint", + "Latest checkpoint sequence number collected by this collector", + &["pipeline"], + registry, + ) + .unwrap(), + latest_collected_checkpoint_timestamp_lag_ms: register_int_gauge_vec_with_registry!( + "indexer_latest_collected_checkpoint_timestamp_lag_ms", + "Difference between the system timestamp when the latest checkpoint was collected and the \ + timestamp in the checkpoint, in milliseconds", + &["pipeline"], + registry, + ) + .unwrap(), + collected_checkpoint_timestamp_lag: register_histogram_vec_with_registry!( + "indexer_collected_checkpoint_timestamp_lag", + "Difference between the system timestamp when a checkpoint was collected and the \ + timestamp in each checkpoint, in seconds", + &["pipeline"], + LAG_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + latest_partially_committed_checkpoint: register_int_gauge_vec_with_registry!( + "indexer_latest_partially_committed_checkpoint", + "Latest checkpoint sequence number partially committed by this collector", + &["pipeline"], + registry, + ) + .unwrap(), + latest_partially_committed_checkpoint_timestamp_lag_ms: register_int_gauge_vec_with_registry!( + "indexer_latest_partially_committed_checkpoint_timestamp_lag_ms", + "Difference between the system timestamp when the latest checkpoint was partially committed and the \ + timestamp in the checkpoint, in milliseconds", + &["pipeline"], + registry, + ) + .unwrap(), + partially_committed_checkpoint_timestamp_lag: register_histogram_vec_with_registry!( + "indexer_partially_committed_checkpoint_timestamp_lag", + "Difference between the system timestamp when a checkpoint was partially committed and the \ + timestamp in each checkpoint, in seconds", + &["pipeline"], + LAG_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), + latest_watermarked_checkpoint_timestamp_lag_ms: register_int_gauge_vec_with_registry!( + "indexer_latest_watermarked_checkpoint_timestamp_lag_ms", + "Difference between the system timestamp when the latest checkpoint was watermarked and the \ + timestamp in the checkpoint, in milliseconds", + &["pipeline"], + registry, + ) + .unwrap(), + watermarked_checkpoint_timestamp_lag: register_histogram_vec_with_registry!( + "indexer_watermarked_checkpoint_timestamp_lag", + "Difference between the system timestamp when a checkpoint was watermarked and the \ + timestamp in each checkpoint, in seconds", + &["pipeline"], + LAG_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), collector_gather_latency: register_histogram_vec_with_registry!( "indexer_collector_gather_latency", "Time taken to gather rows into a batch by this collector", diff --git a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs index f3a935efd8dfd..5b3cb84c911d8 100644 --- a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs +++ b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs @@ -12,7 +12,7 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, info}; use crate::{ - metrics::IndexerMetrics, + metrics::{CheckpointLagMetricReporter, IndexerMetrics}, pipeline::{CommitterConfig, IndexedCheckpoint, WatermarkPart}, }; @@ -99,6 +99,12 @@ pub(super) fn collector( let mut received: BTreeMap> = BTreeMap::new(); let checkpoint_lag = checkpoint_lag.unwrap_or_default(); + let checkpoint_lag_reporter = CheckpointLagMetricReporter::new_for_pipeline::( + &metrics.collected_checkpoint_timestamp_lag, + &metrics.latest_collected_checkpoint_timestamp_lag_ms, + &metrics.latest_collected_checkpoint, + ); + // Data for checkpoints that are ready to be sent but haven't been written yet. let mut pending: BTreeMap> = BTreeMap::new(); let mut pending_rows = 0; @@ -128,6 +134,10 @@ pub(super) fn collector( let indexed = entry.get_mut(); indexed.batch_into(&mut batch); if indexed.is_empty() { + checkpoint_lag_reporter.report_lag( + indexed.watermark.checkpoint(), + indexed.watermark.timestamp_ms(), + ); entry.remove(); } } diff --git a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/commit_watermark.rs b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/commit_watermark.rs index 96ade643909d7..a8cbf1fe0ee98 100644 --- a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/commit_watermark.rs +++ b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/commit_watermark.rs @@ -17,7 +17,7 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, warn}; use crate::{ - metrics::IndexerMetrics, + metrics::{CheckpointLagMetricReporter, IndexerMetrics}, pipeline::{logging::WatermarkLogger, CommitterConfig, WatermarkPart, WARN_PENDING_WATERMARKS}, watermarks::CommitterWatermark, }; @@ -79,6 +79,12 @@ pub(super) fn commit_watermark( // demonstrate that the pipeline is making progress. let mut logger = WatermarkLogger::new("concurrent_committer", &watermark); + let checkpoint_lag_reporter = CheckpointLagMetricReporter::new_for_pipeline::( + &metrics.watermarked_checkpoint_timestamp_lag, + &metrics.latest_watermarked_checkpoint_timestamp_lag_ms, + &metrics.watermark_checkpoint_in_db, + ); + info!(pipeline = H::NAME, ?watermark, "Starting commit watermark"); loop { @@ -202,16 +208,16 @@ pub(super) fn commit_watermark( logger.log::(&watermark, elapsed); + checkpoint_lag_reporter.report_lag( + watermark.checkpoint_hi_inclusive as u64, + watermark.timestamp_ms_hi_inclusive as u64, + ); + metrics .watermark_epoch_in_db .with_label_values(&[H::NAME]) .set(watermark.epoch_hi_inclusive); - metrics - .watermark_checkpoint_in_db - .with_label_values(&[H::NAME]) - .set(watermark.checkpoint_hi_inclusive); - metrics .watermark_transaction_in_db .with_label_values(&[H::NAME]) diff --git a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/committer.rs b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/committer.rs index 8a08ad225d403..5b06a82e89013 100644 --- a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/committer.rs +++ b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/committer.rs @@ -11,7 +11,7 @@ use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, warn}; use crate::{ - metrics::IndexerMetrics, + metrics::{CheckpointLagMetricReporter, IndexerMetrics}, pipeline::{Break, CommitterConfig, WatermarkPart}, task::TrySpawnStreamExt, }; @@ -45,6 +45,11 @@ pub(super) fn committer( ) -> JoinHandle<()> { tokio::spawn(async move { info!(pipeline = H::NAME, "Starting committer"); + let checkpoint_lag_reporter = CheckpointLagMetricReporter::new_for_pipeline::( + &metrics.partially_committed_checkpoint_timestamp_lag, + &metrics.latest_partially_committed_checkpoint_timestamp_lag_ms, + &metrics.latest_partially_committed_checkpoint, + ); match ReceiverStream::new(rx) .try_for_each_spawned( @@ -55,6 +60,7 @@ pub(super) fn committer( let db = db.clone(); let metrics = metrics.clone(); let cancel = cancel.clone(); + let checkpoint_lag_reporter = checkpoint_lag_reporter.clone(); // Repeatedly try to get a connection to the DB and write the batch. Use an // exponential backoff in case the failure is due to contention over the DB @@ -67,11 +73,16 @@ pub(super) fn committer( ..Default::default() }; + let highest_checkpoint = watermark.iter().map(|w| w.checkpoint()).max(); + let highest_checkpoint_timestamp = + watermark.iter().map(|w| w.timestamp_ms()).max(); + use backoff::Error as BE; let commit = move || { let values = values.clone(); let db = db.clone(); let metrics = metrics.clone(); + let checkpoint_lag_reporter = checkpoint_lag_reporter.clone(); async move { if values.is_empty() { return Ok(()); @@ -112,6 +123,12 @@ pub(super) fn committer( "Wrote batch", ); + checkpoint_lag_reporter.report_lag( + // unwrap is safe because we would have returned if values is empty. + highest_checkpoint.unwrap(), + highest_checkpoint_timestamp.unwrap(), + ); + metrics .total_committer_batches_succeeded .with_label_values(&[H::NAME]) diff --git a/crates/sui-indexer-alt-framework/src/pipeline/mod.rs b/crates/sui-indexer-alt-framework/src/pipeline/mod.rs index e5c90a2d714e5..08604023d0f66 100644 --- a/crates/sui-indexer-alt-framework/src/pipeline/mod.rs +++ b/crates/sui-indexer-alt-framework/src/pipeline/mod.rs @@ -115,6 +115,10 @@ impl WatermarkPart { self.watermark.checkpoint_hi_inclusive as u64 } + fn timestamp_ms(&self) -> u64 { + self.watermark.timestamp_ms_hi_inclusive as u64 + } + /// Check if all the rows from this watermark are represented in this part. fn is_complete(&self) -> bool { self.batch_rows == self.total_rows From 3c99eb435b339d3cc5cdfb31c6ea810b624721fb Mon Sep 17 00:00:00 2001 From: Xun Li Date: Tue, 17 Dec 2024 13:30:58 -0800 Subject: [PATCH 15/22] [indexer-alt] Do not prune in the case of object creation (#20641) ## Description We do not need to prune anything when we create or unwrap objects in a checkpoint, since we know that the object did not exist prior to this checkpoint in the table. This saves a lot of unnecessary pruning requests. ## Test plan Deployed in prod. --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- .../src/handlers/coin_balance_buckets.rs | 5 +- .../handlers/coin_balance_buckets_pruner.rs | 67 +++++++++++++++---- .../sui-indexer-alt/src/handlers/obj_info.rs | 9 --- .../src/handlers/obj_info_pruner.rs | 59 ++++++++++++---- 4 files changed, 103 insertions(+), 37 deletions(-) diff --git a/crates/sui-indexer-alt/src/handlers/coin_balance_buckets.rs b/crates/sui-indexer-alt/src/handlers/coin_balance_buckets.rs index b1bba4ec74ba8..0c4d8c96ffcb6 100644 --- a/crates/sui-indexer-alt/src/handlers/coin_balance_buckets.rs +++ b/crates/sui-indexer-alt/src/handlers/coin_balance_buckets.rs @@ -187,7 +187,7 @@ impl TryInto for &ProcessedCoinBalanceBucket { /// Get the owner kind and address of a coin, if it is owned by a single address, /// either through fast-path ownership or ConsensusV2 ownership. -fn get_coin_owner(object: &Object) -> Option<(StoredCoinOwnerKind, SuiAddress)> { +pub(crate) fn get_coin_owner(object: &Object) -> Option<(StoredCoinOwnerKind, SuiAddress)> { match object.owner() { Owner::AddressOwner(owner_id) => Some((StoredCoinOwnerKind::Fastpath, *owner_id)), Owner::ConsensusV2 { authenticator, .. } => Some(( @@ -198,8 +198,9 @@ fn get_coin_owner(object: &Object) -> Option<(StoredCoinOwnerKind, SuiAddress)> } } -fn get_coin_balance_bucket(coin: &Object) -> anyhow::Result { +pub(crate) fn get_coin_balance_bucket(coin: &Object) -> anyhow::Result { let Some(coin) = coin.as_coin_maybe() else { + // TODO: We should make this an invariant violation. bail!("Failed to deserialize Coin for {}", coin.id()); }; let balance = coin.balance.value(); diff --git a/crates/sui-indexer-alt/src/handlers/coin_balance_buckets_pruner.rs b/crates/sui-indexer-alt/src/handlers/coin_balance_buckets_pruner.rs index f6644534cf7e1..f3a9619692464 100644 --- a/crates/sui-indexer-alt/src/handlers/coin_balance_buckets_pruner.rs +++ b/crates/sui-indexer-alt/src/handlers/coin_balance_buckets_pruner.rs @@ -6,22 +6,62 @@ use std::{collections::BTreeMap, sync::Arc}; use anyhow::Result; use diesel::sql_query; use diesel_async::RunQueryDsl; +use sui_field_count::FieldCount; use sui_indexer_alt_framework::pipeline::{concurrent::Handler, Processor}; use sui_pg_db as db; -use sui_types::full_checkpoint_content::CheckpointData; +use sui_types::{base_types::ObjectID, full_checkpoint_content::CheckpointData}; -use super::coin_balance_buckets::{ - CoinBalanceBucketChangeKind, CoinBalanceBuckets, ProcessedCoinBalanceBucket, -}; +use super::coin_balance_buckets::{get_coin_balance_bucket, get_coin_owner}; pub(crate) struct CoinBalanceBucketsPruner; +pub(crate) struct CoinBalanceBucketToBePruned { + pub object_id: ObjectID, + pub cp_sequence_number_exclusive: u64, +} + impl Processor for CoinBalanceBucketsPruner { const NAME: &'static str = "coin_balance_buckets_pruner"; - type Value = ProcessedCoinBalanceBucket; + type Value = CoinBalanceBucketToBePruned; fn process(&self, checkpoint: &Arc) -> Result> { - CoinBalanceBuckets.process(checkpoint) + let cp_sequence_number = checkpoint.checkpoint_summary.sequence_number; + let checkpoint_input_objects = checkpoint.checkpoint_input_objects(); + let latest_live_output_objects: BTreeMap<_, _> = checkpoint + .latest_live_output_objects() + .into_iter() + .map(|o| (o.id(), o)) + .collect(); + let mut values = Vec::new(); + for (object_id, input_object) in checkpoint_input_objects { + // This loop processes all coins that were owned by a single address prior to the checkpoint, + // but is now deleted/wrapped, or changed owner or coin balance bucket the checkpoint. + if !input_object.is_coin() { + continue; + } + let Some(input_coin_owner) = get_coin_owner(input_object) else { + continue; + }; + let input_coin_balance_bucket = get_coin_balance_bucket(input_object)?; + if let Some(output_object) = latest_live_output_objects.get(&object_id) { + let output_coin_owner = get_coin_owner(output_object); + let output_coin_balance_bucket = get_coin_balance_bucket(output_object)?; + if (output_coin_owner, output_coin_balance_bucket) + != (Some(input_coin_owner), input_coin_balance_bucket) + { + values.push(CoinBalanceBucketToBePruned { + object_id, + cp_sequence_number_exclusive: cp_sequence_number, + }); + } + } else { + values.push(CoinBalanceBucketToBePruned { + object_id, + cp_sequence_number_exclusive: cp_sequence_number + 1, + }); + } + } + Ok(values) } } @@ -32,13 +72,8 @@ impl Handler for CoinBalanceBucketsPruner { let mut to_prune = BTreeMap::new(); for v in values { - let object_id = v.object_id; - let cp_sequence_number_exclusive = match v.change { - CoinBalanceBucketChangeKind::Insert { .. } => v.cp_sequence_number, - CoinBalanceBucketChangeKind::Delete => v.cp_sequence_number + 1, - } as i64; - let cp = to_prune.entry(object_id).or_default(); - *cp = std::cmp::max(*cp, cp_sequence_number_exclusive); + let cp = to_prune.entry(v.object_id).or_default(); + *cp = std::cmp::max(*cp, v.cp_sequence_number_exclusive); } let values = to_prune .iter() @@ -66,3 +101,9 @@ impl Handler for CoinBalanceBucketsPruner { Ok(rows_deleted) } } + +impl FieldCount for CoinBalanceBucketToBePruned { + // This does not really matter since we are not limited by postgres' bound variable limit, because + // we don't bind parameters in the deletion statement. + const FIELD_COUNT: usize = 1; +} diff --git a/crates/sui-indexer-alt/src/handlers/obj_info.rs b/crates/sui-indexer-alt/src/handlers/obj_info.rs index 1da35917fedf0..a472c275daf83 100644 --- a/crates/sui-indexer-alt/src/handlers/obj_info.rs +++ b/crates/sui-indexer-alt/src/handlers/obj_info.rs @@ -95,15 +95,6 @@ impl Handler for ObjInfo { } } -impl ProcessedObjInfo { - pub fn object_id(&self) -> ObjectID { - match &self.update { - ProcessedObjInfoUpdate::Insert(object) => object.id(), - ProcessedObjInfoUpdate::Delete(object_id) => *object_id, - } - } -} - impl FieldCount for ProcessedObjInfo { const FIELD_COUNT: usize = StoredObjInfo::FIELD_COUNT; } diff --git a/crates/sui-indexer-alt/src/handlers/obj_info_pruner.rs b/crates/sui-indexer-alt/src/handlers/obj_info_pruner.rs index fc6f25306b839..eabbaeb6944d8 100644 --- a/crates/sui-indexer-alt/src/handlers/obj_info_pruner.rs +++ b/crates/sui-indexer-alt/src/handlers/obj_info_pruner.rs @@ -6,20 +6,51 @@ use std::{collections::BTreeMap, sync::Arc}; use anyhow::Result; use diesel::sql_query; use diesel_async::RunQueryDsl; +use sui_field_count::FieldCount; use sui_indexer_alt_framework::pipeline::{concurrent::Handler, Processor}; use sui_pg_db as db; -use sui_types::full_checkpoint_content::CheckpointData; - -use super::obj_info::{ObjInfo, ProcessedObjInfo, ProcessedObjInfoUpdate}; +use sui_types::{base_types::ObjectID, full_checkpoint_content::CheckpointData}; pub(crate) struct ObjInfoPruner; +pub(crate) struct ObjInfoToBePruned { + pub object_id: ObjectID, + pub cp_sequence_number_exclusive: u64, +} + impl Processor for ObjInfoPruner { const NAME: &'static str = "obj_info_pruner"; - type Value = ProcessedObjInfo; + type Value = ObjInfoToBePruned; fn process(&self, checkpoint: &Arc) -> Result> { - ObjInfo.process(checkpoint) + let cp_sequence_number = checkpoint.checkpoint_summary.sequence_number; + let checkpoint_input_objects = checkpoint.checkpoint_input_objects(); + let latest_live_output_objects = checkpoint + .latest_live_output_objects() + .into_iter() + .map(|o| (o.id(), o)) + .collect::>(); + let mut values = Vec::with_capacity(checkpoint_input_objects.len()); + // We only need to prune if an object is removed, or its owner changed. + // We do not need to prune when an object is created or unwrapped, since there would have not + // been an entry for it in the table prior to this checkpoint. + // This makes the logic different from the one in obj_info.rs. + for (object_id, input_object) in checkpoint_input_objects { + if let Some(output_object) = latest_live_output_objects.get(&object_id) { + if output_object.owner() != input_object.owner() { + values.push(ObjInfoToBePruned { + object_id, + cp_sequence_number_exclusive: cp_sequence_number, + }); + } + } else { + values.push(ObjInfoToBePruned { + object_id, + cp_sequence_number_exclusive: cp_sequence_number + 1, + }); + } + } + Ok(values) } } @@ -30,16 +61,12 @@ impl Handler for ObjInfoPruner { // For each (object_id, cp_sequence_number_exclusive), delete all entries in obj_info with // cp_sequence_number less than cp_sequence_number_exclusive that match the object_id. - // For each object_id, we first get the highest cp_sequence_number_exclusive. + + // Minor optimization:For each object_id, we first get the highest cp_sequence_number_exclusive. let mut to_prune = BTreeMap::new(); for v in values { - let object_id = v.object_id(); - let cp_sequence_number_exclusive = match v.update { - ProcessedObjInfoUpdate::Insert(_) => v.cp_sequence_number, - ProcessedObjInfoUpdate::Delete(_) => v.cp_sequence_number + 1, - } as i64; - let cp = to_prune.entry(object_id).or_default(); - *cp = std::cmp::max(*cp, cp_sequence_number_exclusive); + let cp = to_prune.entry(v.object_id).or_default(); + *cp = std::cmp::max(*cp, v.cp_sequence_number_exclusive); } let values = to_prune .iter() @@ -67,3 +94,9 @@ impl Handler for ObjInfoPruner { Ok(rows_deleted) } } + +impl FieldCount for ObjInfoToBePruned { + // This does not really matter since we are not limited by postgres' bound variable limit, because + // we don't bind parameters in the deletion statement. + const FIELD_COUNT: usize = 1; +} From 8484b17b7977f20a6667f99e32e2710a6346ceda Mon Sep 17 00:00:00 2001 From: hayes-mysten <135670682+hayes-mysten@users.noreply.github.com> Date: Tue, 17 Dec 2024 13:36:31 -0800 Subject: [PATCH 16/22] [docs] fix typo in legacyAddress docs (#20662) ## Description Describe the changes or additions included in this PR. ## Test plan How did you test the new or updated feature? --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- pnpm-lock.yaml | 132 +++++++++++++------------- sdk/docs/pages/typescript/zklogin.mdx | 2 +- 2 files changed, 66 insertions(+), 68 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe22aeddafcc9..2462a4adbcc42 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1461,13 +1461,13 @@ importers: version: 20.14.10 next: specifier: ^14.2.10 - version: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) + version: 14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) nextra: specifier: ^2.13.4 - version: 2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.13.4(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra-theme-docs: specifier: ^2.13.4 - version: 2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(nextra@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.13.4(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(nextra@2.13.4(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -4466,59 +4466,59 @@ packages: '@ndelangen/get-tarball@3.0.9': resolution: {integrity: sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==} - '@next/env@14.2.13': - resolution: {integrity: sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==} + '@next/env@14.2.15': + resolution: {integrity: sha512-S1qaj25Wru2dUpcIZMjxeMVSwkt8BK4dmWHHiBuRstcIyOsMapqT4A4jSB6onvqeygkSSmOkyny9VVx8JIGamQ==} - '@next/swc-darwin-arm64@14.2.13': - resolution: {integrity: sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==} + '@next/swc-darwin-arm64@14.2.15': + resolution: {integrity: sha512-Rvh7KU9hOUBnZ9TJ28n2Oa7dD9cvDBKua9IKx7cfQQ0GoYUwg9ig31O2oMwH3wm+pE3IkAQ67ZobPfEgurPZIA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.13': - resolution: {integrity: sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==} + '@next/swc-darwin-x64@14.2.15': + resolution: {integrity: sha512-5TGyjFcf8ampZP3e+FyCax5zFVHi+Oe7sZyaKOngsqyaNEpOgkKB3sqmymkZfowy3ufGA/tUgDPPxpQx931lHg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.13': - resolution: {integrity: sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==} + '@next/swc-linux-arm64-gnu@14.2.15': + resolution: {integrity: sha512-3Bwv4oc08ONiQ3FiOLKT72Q+ndEMyLNsc/D3qnLMbtUYTQAmkx9E/JRu0DBpHxNddBmNT5hxz1mYBphJ3mfrrw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.13': - resolution: {integrity: sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==} + '@next/swc-linux-arm64-musl@14.2.15': + resolution: {integrity: sha512-k5xf/tg1FBv/M4CMd8S+JL3uV9BnnRmoe7F+GWC3DxkTCD9aewFRH1s5rJ1zkzDa+Do4zyN8qD0N8c84Hu96FQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.13': - resolution: {integrity: sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==} + '@next/swc-linux-x64-gnu@14.2.15': + resolution: {integrity: sha512-kE6q38hbrRbKEkkVn62reLXhThLRh6/TvgSP56GkFNhU22TbIrQDEMrO7j0IcQHcew2wfykq8lZyHFabz0oBrA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.13': - resolution: {integrity: sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==} + '@next/swc-linux-x64-musl@14.2.15': + resolution: {integrity: sha512-PZ5YE9ouy/IdO7QVJeIcyLn/Rc4ml9M2G4y3kCM9MNf1YKvFY4heg3pVa/jQbMro+tP6yc4G2o9LjAz1zxD7tQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.13': - resolution: {integrity: sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==} + '@next/swc-win32-arm64-msvc@14.2.15': + resolution: {integrity: sha512-2raR16703kBvYEQD9HNLyb0/394yfqzmIeyp2nDzcPV4yPjqNUG3ohX6jX00WryXz6s1FXpVhsCo3i+g4RUX+g==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.13': - resolution: {integrity: sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==} + '@next/swc-win32-ia32-msvc@14.2.15': + resolution: {integrity: sha512-fyTE8cklgkyR1p03kJa5zXEaZ9El+kDNM5A+66+8evQS5e/6v0Gk28LqA0Jet8gKSOyP+OTm/tJHzMlGdQerdQ==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.13': - resolution: {integrity: sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==} + '@next/swc-win32-x64-msvc@14.2.15': + resolution: {integrity: sha512-SzqGbsLsP9OwKNUG9nekShTwhj6JSB9ZLMWQ8g1gG6hdE5gQLncbnbymrwy2yVmH9nikSLYRYxYMFu78Ggp7/g==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -12100,8 +12100,8 @@ packages: react: '*' react-dom: '*' - next@14.2.13: - resolution: {integrity: sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==} + next@14.2.15: + resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -15959,7 +15959,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/parser@7.23.9': dependencies: @@ -18742,33 +18742,33 @@ snapshots: pump: 3.0.0 tar-fs: 2.1.1 - '@next/env@14.2.13': {} + '@next/env@14.2.15': {} - '@next/swc-darwin-arm64@14.2.13': + '@next/swc-darwin-arm64@14.2.15': optional: true - '@next/swc-darwin-x64@14.2.13': + '@next/swc-darwin-x64@14.2.15': optional: true - '@next/swc-linux-arm64-gnu@14.2.13': + '@next/swc-linux-arm64-gnu@14.2.15': optional: true - '@next/swc-linux-arm64-musl@14.2.13': + '@next/swc-linux-arm64-musl@14.2.15': optional: true - '@next/swc-linux-x64-gnu@14.2.13': + '@next/swc-linux-x64-gnu@14.2.15': optional: true - '@next/swc-linux-x64-musl@14.2.13': + '@next/swc-linux-x64-musl@14.2.15': optional: true - '@next/swc-win32-arm64-msvc@14.2.13': + '@next/swc-win32-arm64-msvc@14.2.15': optional: true - '@next/swc-win32-ia32-msvc@14.2.13': + '@next/swc-win32-ia32-msvc@14.2.15': optional: true - '@next/swc-win32-x64-msvc@14.2.13': + '@next/swc-win32-x64-msvc@14.2.15': optional: true '@noble/curves@1.4.2': @@ -18964,15 +18964,13 @@ snapshots: transitivePeerDependencies: - '@parcel/core' - '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)': + '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': dependencies: '@parcel/core': 2.12.0(@swc/helpers@0.5.5) '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) '@parcel/logger': 2.12.0 '@parcel/utils': 2.12.0 lmdb: 2.8.5 - transitivePeerDependencies: - - '@swc/helpers' '@parcel/codeframe@2.12.0': dependencies: @@ -19032,7 +19030,7 @@ snapshots: '@parcel/core@2.12.0(@swc/helpers@0.5.5)': dependencies: '@mischnic/json-sourcemap': 0.1.1 - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) '@parcel/diagnostic': 2.12.0 '@parcel/events': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) @@ -19447,7 +19445,7 @@ snapshots: '@parcel/types@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)': dependencies: - '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) '@parcel/diagnostic': 2.12.0 '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) '@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) @@ -28616,7 +28614,7 @@ snapshots: nanospinner@1.1.0: dependencies: - picocolors: 1.0.1 + picocolors: 1.1.0 nanostores@0.10.3: {} @@ -28644,21 +28642,21 @@ snapshots: transitivePeerDependencies: - supports-color - next-seo@6.6.0(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-seo@6.6.0(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) + next: 14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next-themes@0.2.1(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) + next: 14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6): + next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6): dependencies: - '@next/env': 14.2.13 + '@next/env': 14.2.15 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001660 @@ -28668,22 +28666,22 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.24.7)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.13 - '@next/swc-darwin-x64': 14.2.13 - '@next/swc-linux-arm64-gnu': 14.2.13 - '@next/swc-linux-arm64-musl': 14.2.13 - '@next/swc-linux-x64-gnu': 14.2.13 - '@next/swc-linux-x64-musl': 14.2.13 - '@next/swc-win32-arm64-msvc': 14.2.13 - '@next/swc-win32-ia32-msvc': 14.2.13 - '@next/swc-win32-x64-msvc': 14.2.13 + '@next/swc-darwin-arm64': 14.2.15 + '@next/swc-darwin-x64': 14.2.15 + '@next/swc-linux-arm64-gnu': 14.2.15 + '@next/swc-linux-arm64-musl': 14.2.15 + '@next/swc-linux-x64-gnu': 14.2.15 + '@next/swc-linux-x64-musl': 14.2.15 + '@next/swc-win32-arm64-msvc': 14.2.15 + '@next/swc-win32-ia32-msvc': 14.2.15 + '@next/swc-win32-x64-msvc': 14.2.15 '@playwright/test': 1.45.1 sass: 1.77.6 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(nextra@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@2.13.4(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(nextra@2.13.4(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@popperjs/core': 2.11.8 @@ -28694,16 +28692,16 @@ snapshots: git-url-parse: 13.1.1 intersection-observer: 0.12.2 match-sorter: 6.3.4 - next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) - next-seo: 6.6.0(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next-themes: 0.2.1(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) + next-seo: 6.6.0(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes: 0.2.1(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra: 2.13.4(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.23.8 - nextra@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra@2.13.4(next@14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mdx-js/mdx': 2.3.0 @@ -28717,7 +28715,7 @@ snapshots: gray-matter: 4.0.3 katex: 0.16.11 lodash.get: 4.4.2 - next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) + next: 14.2.15(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) next-mdx-remote: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) p-limit: 3.1.0 react: 18.3.1 @@ -29643,7 +29641,7 @@ snapshots: postcss@8.4.31: dependencies: nanoid: 3.3.7 - picocolors: 1.0.1 + picocolors: 1.1.0 source-map-js: 1.2.1 postcss@8.4.39: @@ -31112,7 +31110,7 @@ snapshots: css-select: 5.1.0 css-tree: 2.3.1 csso: 5.0.5 - picocolors: 1.0.1 + picocolors: 1.1.0 swap-case@2.0.2: dependencies: @@ -31979,7 +31977,7 @@ snapshots: cac: 6.7.14 debug: 4.3.7(supports-color@8.1.1) pathe: 1.1.2 - picocolors: 1.0.1 + picocolors: 1.1.0 vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - '@types/node' diff --git a/sdk/docs/pages/typescript/zklogin.mdx b/sdk/docs/pages/typescript/zklogin.mdx index e6403ef7c3433..3bd70771deaec 100644 --- a/sdk/docs/pages/typescript/zklogin.mdx +++ b/sdk/docs/pages/typescript/zklogin.mdx @@ -67,7 +67,7 @@ import { computeZkLoginAddressFromSeed, jwtToAddress, toZkLoginPublicIdentifier, - genAddressSeed(userSalt, claimName, claimValue, aud) + genAddressSeed } from '@mysten/sui/zklogin'; const address = jwtToAddress(jwtAsString, salt, true); From f18b17d94d0aa16978f3dd637bbd0b132cad680b Mon Sep 17 00:00:00 2001 From: Mark Logan <103447440+mystenmark@users.noreply.github.com> Date: Tue, 17 Dec 2024 14:18:36 -0800 Subject: [PATCH 17/22] Suppress more types of overload protection in backpressure test (#20651) Fixes rare test failure in `test_backpressure` --- crates/sui-benchmark/tests/simtest.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/sui-benchmark/tests/simtest.rs b/crates/sui-benchmark/tests/simtest.rs index 415a8e920e17c..b96d3a228bce4 100644 --- a/crates/sui-benchmark/tests/simtest.rs +++ b/crates/sui-benchmark/tests/simtest.rs @@ -909,6 +909,9 @@ mod test { // having queued certs which are missing dependencies. check_system_overload_at_execution: false, check_system_overload_at_signing: false, + max_txn_age_in_queue: Duration::from_secs(10000), + max_transaction_manager_queue_length: 10000, + max_transaction_manager_per_object_queue_length: 10000, ..Default::default() }) .with_execution_cache_config(cache_config) From ac0361801e151ff5007aef033702b10438755ba1 Mon Sep 17 00:00:00 2001 From: Adam Welc Date: Wed, 18 Dec 2024 09:09:36 +0100 Subject: [PATCH 18/22] [move-analyzer] Cache user program (#20621) ## Description This PR builds upon the compiler changes supporting partial compilation of of user code (skipping function bodies when compiling specific files designated by the user) to deliver the performance improvements described in the [PR](https://github.com/MystenLabs/sui/pull/20588) containing the compiler changes (reducing compilation time in the IDE for the Deepbook package from over 1s to less than 200ms). In this PR, in addition to caching dependencies, we also cache user code, fully compile only modified files, and merge result of this compilation with the cached data ## Test plan All tests must pass. Also tested manually to verify correctness and performance improvements --- .../move/crates/move-analyzer/src/analyzer.rs | 10 +- .../move-analyzer/src/completions/mod.rs | 11 +- .../move/crates/move-analyzer/src/symbols.rs | 456 ++++++++++++------ .../move-analyzer/tests/ide_testsuite.rs | 33 +- 4 files changed, 335 insertions(+), 175 deletions(-) diff --git a/external-crates/move/crates/move-analyzer/src/analyzer.rs b/external-crates/move/crates/move-analyzer/src/analyzer.rs index 62f406e9215c5..e0f4eb79e4ad3 100644 --- a/external-crates/move/crates/move-analyzer/src/analyzer.rs +++ b/external-crates/move/crates/move-analyzer/src/analyzer.rs @@ -45,10 +45,9 @@ pub fn run() { let (connection, io_threads) = Connection::stdio(); let symbols_map = Arc::new(Mutex::new(BTreeMap::new())); - let pkg_deps = Arc::new(Mutex::new(BTreeMap::< - PathBuf, - symbols::PrecomputedPkgDepsInfo, - >::new())); + let pkg_deps = Arc::new(Mutex::new( + BTreeMap::::new(), + )); let ide_files_root: VfsPath = MemoryFS::new().into(); let (id, client_response) = connection @@ -154,6 +153,7 @@ pub fn run() { Arc::new(Mutex::new(BTreeMap::new())), ide_files_root.clone(), p.as_path(), + None, lint, None, ) { @@ -279,7 +279,7 @@ fn on_request( context: &Context, request: &Request, ide_files_root: VfsPath, - pkg_dependencies: Arc>>, + pkg_dependencies: Arc>>, shutdown_request_received: bool, ) -> bool { if shutdown_request_received { diff --git a/external-crates/move/crates/move-analyzer/src/completions/mod.rs b/external-crates/move/crates/move-analyzer/src/completions/mod.rs index 1e9be2622ec85..576fd471e063c 100644 --- a/external-crates/move/crates/move-analyzer/src/completions/mod.rs +++ b/external-crates/move/crates/move-analyzer/src/completions/mod.rs @@ -10,7 +10,7 @@ use crate::{ utils::{completion_item, PRIMITIVE_TYPE_COMPLETIONS}, }, context::Context, - symbols::{self, CursorContext, PrecomputedPkgDepsInfo, SymbolicatorRunner, Symbols}, + symbols::{self, CursorContext, PrecomputedPkgInfo, SymbolicatorRunner, Symbols}, }; use lsp_server::Request; use lsp_types::{CompletionItem, CompletionItemKind, CompletionParams, Position}; @@ -78,7 +78,7 @@ pub fn on_completion_request( context: &Context, request: &Request, ide_files_root: VfsPath, - pkg_dependencies: Arc>>, + pkg_dependencies: Arc>>, ) { eprintln!("handling completion request"); let parameters = serde_json::from_value::(request.params.clone()) @@ -119,7 +119,7 @@ pub fn on_completion_request( fn completions( context: &Context, ide_files_root: VfsPath, - pkg_dependencies: Arc>>, + pkg_dependencies: Arc>>, path: &Path, pos: Position, ) -> Option> { @@ -143,7 +143,7 @@ fn completions( pub fn compute_completions( current_symbols: &Symbols, ide_files_root: VfsPath, - pkg_dependencies: Arc>>, + pkg_dependencies: Arc>>, path: &Path, pos: Position, ) -> Vec { @@ -156,7 +156,7 @@ pub fn compute_completions( /// view of the code (returns `None` if the symbols could not be re-computed). fn compute_completions_new_symbols( ide_files_root: VfsPath, - pkg_dependencies: Arc>>, + pkg_dependencies: Arc>>, path: &Path, cursor_position: Position, ) -> Option> { @@ -170,6 +170,7 @@ fn compute_completions_new_symbols( pkg_dependencies, ide_files_root, &pkg_path, + Some(vec![path.to_path_buf()]), LintLevel::None, cursor_info, ) diff --git a/external-crates/move/crates/move-analyzer/src/symbols.rs b/external-crates/move/crates/move-analyzer/src/symbols.rs index 2ff77fa08158c..25853fb1babb4 100644 --- a/external-crates/move/crates/move-analyzer/src/symbols.rs +++ b/external-crates/move/crates/move-analyzer/src/symbols.rs @@ -79,6 +79,7 @@ use std::{ sync::{Arc, Condvar, Mutex}, thread, time::Instant, + vec, }; use tempfile::tempdir; use url::Url; @@ -106,10 +107,7 @@ use move_compiler::{ NamedAddressMaps, }, typing::{ - ast::{ - self as T, Exp, ExpListItem, ModuleDefinition, SequenceItem, SequenceItem_, - UnannotatedExp_, - }, + ast::{Exp, ExpListItem, ModuleDefinition, SequenceItem, SequenceItem_, UnannotatedExp_}, visitor::TypingVisitorContext, }, unit_test::filter_test_members::UNIT_TEST_POISON_FUN_NAME, @@ -120,28 +118,30 @@ use move_ir_types::location::*; use move_package::{ compilation::{build_plan::BuildPlan, compiled_package::ModuleFormat}, resolution::resolution_graph::ResolvedGraph, - source_package::parsed_manifest::FileName, }; use move_symbol_pool::Symbol; const MANIFEST_FILE_NAME: &str = "Move.toml"; const STD_LIB_PKG_ADDRESS: &str = "0x1"; -type SourceFiles = BTreeMap; /// Information about compiled program (ASTs at different levels) #[derive(Clone)] -struct CompiledProgram { - parsed: P::Program, - typed: T::Program, +pub struct CompiledProgram { + pub parsed: P::Program, + pub typed_modules: UniqueMap, } -/// Information about cached dependencies used during compilation and analysis +/// Package data used during compilation and analysis #[derive(Clone)] -struct CachedDeps { +struct AnalyzedPkgInfo { /// Cached fully compiled program representing dependencies - compiled_program: Arc, + program_deps: Arc, /// Cached symbols computation data for dependencies symbols_data: Option>, + /// Compiled user program + program: Option>, + /// Mapping from file paths to file hashes + file_hashes: Arc>, } /// Information about the compiled package and data structures @@ -155,11 +155,9 @@ pub struct CompiledPkgInfo { /// A combined hash for manifest files of the dependencies deps_hash: String, /// Information about cached dependencies - cached_deps: Option, + cached_deps: Option, /// Compiled user program program: CompiledProgram, - /// Source files - source_files: SourceFiles, /// Maped files mapped_files: MappedFiles, /// Edition of the compiler @@ -200,9 +198,10 @@ impl SymbolsComputationData { } } -/// Precomputed information about package dependencies. +/// Precomputed information about the package and its dependencies +/// cached with the purpose of being re-used during the analysis. #[derive(Clone)] -pub struct PrecomputedPkgDepsInfo { +pub struct PrecomputedPkgInfo { /// Hash of the manifest file for a given package manifest_hash: Option, /// Hash of dependency source files @@ -211,6 +210,10 @@ pub struct PrecomputedPkgDepsInfo { deps: Arc, /// Symbols computation data deps_symbols_data: Arc, + /// Compiled user program + program: Arc, + /// Mapping from file paths to file hashes + file_hashes: Arc>, } /// Location of a use's identifier @@ -1365,7 +1368,7 @@ impl SymbolicatorRunner { pub fn new( ide_files_root: VfsPath, symbols_map: Arc>>, - pkg_deps: Arc>>, + packages_info: Arc>>, sender: Sender>>>, lint: LintLevel, ) -> Self { @@ -1381,63 +1384,44 @@ impl SymbolicatorRunner { // infinite loop to wait for symbolication requests eprintln!("starting symbolicator runner loop"); loop { - let all_starting_paths_opt = { + let starting_paths_opt = { // hold the lock only as long as it takes to get the data, rather than through // the whole symbolication process (hence a separate scope here) let mut symbolicate = mtx.lock().unwrap(); match symbolicate.clone() { RunnerState::Quit => break, - RunnerState::Run(root_dir) => { + RunnerState::Run(starting_paths) => { *symbolicate = RunnerState::Wait; - Some(root_dir) + Some(starting_paths) } RunnerState::Wait => { // wait for next request symbolicate = cvar.wait(symbolicate).unwrap(); match symbolicate.clone() { RunnerState::Quit => break, - RunnerState::Run(root_dir) => { + RunnerState::Run(starting_paths) => { *symbolicate = RunnerState::Wait; - Some(root_dir) + Some(starting_paths) } RunnerState::Wait => None, } } } }; - if let Some(all_starting_paths) = all_starting_paths_opt { - let mut pkgs_to_analyze = BTreeMap::new(); - for starting_path in &all_starting_paths { - let root_dir = Self::root_dir(starting_path); - if root_dir.is_none() { - if !missing_manifests.contains(starting_path) { - eprintln!("reporting missing manifest"); - - // report missing manifest file only once to avoid cluttering IDE's UI in - // cases when developer indeed intended to open a standalone file that was - // not meant to compile - missing_manifests.insert(starting_path.clone()); - if let Err(err) = sender.send(Err(anyhow!( - "Unable to find package manifest. Make sure that - the source files are located in a sub-directory of a package containing - a Move.toml file. " - ))) { - eprintln!("could not pass missing manifest error: {:?}", err); - } - } - continue; - } - pkgs_to_analyze - .entry(root_dir.unwrap()) - .or_insert_with(BTreeSet::new) - .insert(starting_path.clone()); - } - for pkg_path in pkgs_to_analyze.keys() { + if let Some(starting_paths) = starting_paths_opt { + // aggregate all starting paths by package + let pkgs_to_analyze = Self::pkgs_to_analyze( + starting_paths, + &mut missing_manifests, + sender.clone(), + ); + for (pkg_path, modified_files) in pkgs_to_analyze.into_iter() { eprintln!("symbolication started"); match get_symbols( - pkg_deps.clone(), + packages_info.clone(), ide_files_root.clone(), pkg_path.as_path(), + Some(modified_files.into_iter().collect()), lint, None, ) { @@ -1474,6 +1458,40 @@ impl SymbolicatorRunner { runner } + /// Aggregates all starting paths by package + fn pkgs_to_analyze( + starting_paths: BTreeSet, + missing_manifests: &mut BTreeSet, + sender: Sender>>>, + ) -> BTreeMap> { + let mut pkgs_to_analyze: BTreeMap> = BTreeMap::new(); + for starting_path in &starting_paths { + let Some(root_dir) = Self::root_dir(starting_path) else { + if !missing_manifests.contains(starting_path) { + eprintln!("reporting missing manifest"); + // report missing manifest file only once to avoid cluttering IDE's UI in + // cases when developer indeed intended to open a standalone file that was + // not meant to compile + missing_manifests.insert(starting_path.clone()); + if let Err(err) = sender.send(Err(anyhow!( + "Unable to find package manifest. Make sure that + the source files are located in a sub-directory of a package containing + a Move.toml file. " + ))) { + eprintln!("could not pass missing manifest error: {:?}", err); + } + } + continue; + }; + // The mutext value is only set by the `on_text_document_sync_notification` handler + // and can only contain a valid Move file path, so we simply collect a set of Move + // file paths here to pass them to the symbolicator. + let modfied_files = pkgs_to_analyze.entry(root_dir.clone()).or_default(); + modfied_files.insert(starting_path.clone()); + } + pkgs_to_analyze + } + pub fn run(&self, starting_path: PathBuf) { eprintln!("scheduling run for {:?}", starting_path); let (mtx, cvar) = &*self.mtx_cvar; @@ -1763,18 +1781,96 @@ impl Symbols { fn has_precompiled_deps( pkg_path: &Path, - pkg_dependencies: Arc>>, + pkg_dependencies: Arc>>, ) -> bool { let pkg_deps = pkg_dependencies.lock().unwrap(); pkg_deps.contains_key(pkg_path) } +fn is_parsed_pkg_modified( + pkg_def: &P::PackageDefinition, + files_to_compile: &BTreeSet, + file_hashes: Arc>, +) -> bool { + files_to_compile.iter().any(|fpath| { + file_hashes + .get(fpath) + .and_then(|fhash| match &pkg_def.def { + P::Definition::Module(mdef) => Some((mdef, fhash)), + _ => None, + }) + .map_or(false, |(mdef, fhash)| mdef.loc.file_hash() == *fhash) + }) +} + +fn is_typed_mod_modified( + mdef: &ModuleDefinition, + files_to_compile: &BTreeSet, + file_hashes: Arc>, +) -> bool { + files_to_compile.iter().any(|fpath| { + file_hashes + .get(fpath) + .map_or(false, |fhash| mdef.loc.file_hash() == *fhash) + }) +} + +/// Merges a cached compiled program with newly computed compiled program +/// In the newly computed program, only modified files are fully compiled +/// and these files are mereged with the cached compiled program. +fn merge_user_programs( + cached_info_opt: Option, + parsed_program_new: P::Program, + typed_program_modules_new: UniqueMap, + file_hashes_new: Arc>, + files_to_compile: BTreeSet, +) -> (P::Program, UniqueMap) { + // unraps are safe as this function only called when cached compiled program exists + let cached_info = cached_info_opt.unwrap(); + let compiled_program = cached_info.program.unwrap(); + let file_hashes_cached = cached_info.file_hashes; + let mut parsed_program_cached = compiled_program.parsed.clone(); + let mut typed_modules_cached = compiled_program.typed_modules.clone(); + // address maps might have changed but all would be computed in full during + // incremental compilation as only function bodies are omitted + parsed_program_cached.named_address_maps = parsed_program_new.named_address_maps; + // remove modules from user code that belong to modified files (use cached + // file hashes as we are comparing with hashes in cached modules) + parsed_program_cached.source_definitions.retain(|pkg_def| { + !is_parsed_pkg_modified(pkg_def, &files_to_compile, file_hashes_cached.clone()) + }); + let mut typed_modules_cached_filtered = UniqueMap::new(); + for (mident, mdef) in typed_modules_cached.into_iter() { + if !is_typed_mod_modified(&mdef, &files_to_compile, file_hashes_cached.clone()) { + _ = typed_modules_cached_filtered.add(mident, mdef); + } + } + typed_modules_cached = typed_modules_cached_filtered; + // add new modules from user code (use new file hashes as we are comparing with + // hashes in new modules) + for pkg_def in parsed_program_new.source_definitions { + if is_parsed_pkg_modified(&pkg_def, &files_to_compile, file_hashes_new.clone()) { + parsed_program_cached.source_definitions.push(pkg_def); + } + } + for (mident, mdef) in typed_program_modules_new.into_iter() { + if is_typed_mod_modified(&mdef, &files_to_compile, file_hashes_new.clone()) { + typed_modules_cached.remove(&mident); // in case new file has new definition of the module + _ = typed_modules_cached.add(mident, mdef); + } + } + + (parsed_program_cached, typed_modules_cached) +} + /// Builds a package at a given path and, if successful, returns parsed AST /// and typed AST as well as (regardless of success) diagnostics. +/// See `get_symbols` for explanation of what `modified_files` parameter is. pub fn get_compiled_pkg( - pkg_dependencies: Arc>>, + packages_info: Arc>>, ide_files_root: VfsPath, pkg_path: &Path, + modified_files: Option>, lint: LintLevel, ) -> Result<(Option, BTreeMap>)> { let build_config = move_package::BuildConfig { @@ -1782,7 +1878,7 @@ pub fn get_compiled_pkg( install_dir: Some(tempdir().unwrap().path().to_path_buf()), default_flavor: Some(Flavor::Sui), lint_flag: lint.into(), - skip_fetch_latest_git_deps: has_precompiled_deps(pkg_path, pkg_dependencies.clone()), + skip_fetch_latest_git_deps: has_precompiled_deps(pkg_path, packages_info.clone()), ..Default::default() }; @@ -1813,17 +1909,16 @@ pub fn get_compiled_pkg( None }; - let mut mapped_files: MappedFiles = MappedFiles::empty(); - // Hash dependencies so we can check if something has changed. - let source_files = file_sources(&resolution_graph, overlay_fs_root.clone()); - let mut hasher = Sha256::new(); - source_files - .iter() - .filter(|(_, (_, _, is_dep))| *is_dep) - .for_each(|(fhash, _)| hasher.update(fhash.0)); - let deps_hash = format!("{:X}", hasher.finalize()); - + let (mapped_files, deps_hash) = + compute_mapped_files(&resolution_graph, overlay_fs_root.clone()); + let file_hashes: Arc> = Arc::new( + mapped_files + .file_name_mapping() + .iter() + .map(|(fhash, fpath)| (fpath.clone(), *fhash)) + .collect(), + ); let compiler_flags = resolution_graph.build_options.compiler_flags().clone(); let build_plan = BuildPlan::create(resolution_graph)?.set_compiler_vfs_root(overlay_fs_root.clone()); @@ -1833,7 +1928,7 @@ pub fn get_compiled_pkg( let mut diagnostics = None; let mut dependencies = build_plan.compute_dependencies(); - let cached_deps = if let Ok(deps_package_paths) = dependencies.make_deps_for_compiler() { + let cached_info_opt = if let Ok(deps_package_paths) = dependencies.make_deps_for_compiler() { // Partition deps_package according whether src is available let src_deps = deps_package_paths .iter() @@ -1851,18 +1946,19 @@ pub fn get_compiled_pkg( .filter_map(|p| p.name.as_ref().map(|(n, _)| *n)) .collect::>(); - let pkg_deps = pkg_dependencies.lock().unwrap(); - let pkg_cached_deps = match pkg_deps.get(pkg_path) { + let pkg_info = packages_info.lock().unwrap(); + let pkg_cached_deps = match pkg_info.get(pkg_path) { Some(d) if manifest_hash.is_some() && manifest_hash == d.manifest_hash && deps_hash == d.deps_hash => { eprintln!("found cached deps for {:?}", pkg_path); - mapped_files.extend_with_duplicates(d.deps.files.clone()); - Some(CachedDeps { - compiled_program: d.deps.clone(), + Some(AnalyzedPkgInfo { + program_deps: d.deps.clone(), symbols_data: Some(d.deps_symbols_data.clone()), + program: Some(d.program.clone()), + file_hashes: d.file_hashes.clone(), }) } _ => construct_pre_compiled_lib( @@ -1875,10 +1971,11 @@ pub fn get_compiled_pkg( .and_then(|pprog_and_comments_res| pprog_and_comments_res.ok()) .map(|libs| { eprintln!("created pre-compiled libs for {:?}", pkg_path); - mapped_files.extend_with_duplicates(libs.files.clone()); - CachedDeps { - compiled_program: Arc::new(libs), + AnalyzedPkgInfo { + program_deps: Arc::new(libs), symbols_data: None, + program: None, + file_hashes: file_hashes.clone(), } }), }; @@ -1892,16 +1989,35 @@ pub fn get_compiled_pkg( None }; + let (full_compilation, files_to_compile) = if let Some(chached_info) = &cached_info_opt { + if chached_info.program.is_some() { + // we already have cached user program, consider incremental compilation + match modified_files { + Some(files) => (false, BTreeSet::from_iter(files)), + None => (true, BTreeSet::new()), + } + } else { + (true, BTreeSet::new()) + } + } else { + (true, BTreeSet::new()) + }; + let mut edition = None; let mut comments = None; - let compiled_libs = cached_deps + let compiled_libs = cached_info_opt .clone() - .map(|deps| deps.compiled_program.clone()); + .map(|deps| deps.program_deps.clone()); build_plan.compile_with_driver_and_deps(dependencies, &mut std::io::sink(), |compiler| { let compiler = compiler.set_ide_mode(); // extract expansion AST let (files, compilation_result) = compiler .set_pre_compiled_lib_opt(compiled_libs.clone()) + .set_files_to_compile(if full_compilation { + None + } else { + Some(files_to_compile.clone()) + }) .run::()?; let (comments_map, compiler) = match compilation_result { Ok(v) => v, @@ -1916,7 +2032,6 @@ pub fn get_compiled_pkg( eprintln!("compiled to parsed AST"); let (compiler, parsed_program) = compiler.into_ast(); parsed_ast = Some(parsed_program.clone()); - mapped_files.extend_with_duplicates(compiler.compilation_env().mapped_files().clone()); // extract typed AST let compilation_result = compiler.at_parser(parsed_program).run::(); @@ -1974,8 +2089,17 @@ pub fn get_compiled_pkg( // uwrap's are safe - this function returns earlier (during diagnostics processing) // when failing to produce the ASTs - let parsed_program = parsed_ast.unwrap(); - let typed_program = typed_ast.clone().unwrap(); + let (parsed_program, typed_program_modules) = if full_compilation { + (parsed_ast.unwrap(), typed_ast.unwrap().modules) + } else { + merge_user_programs( + cached_info_opt.clone(), + parsed_ast.unwrap(), + typed_ast.unwrap().modules, + file_hashes, + files_to_compile, + ) + }; let mut all_comments = comments.unwrap(); if let Some(libs) = &compiled_libs { all_comments.extend(libs.comments.clone()); @@ -1984,12 +2108,11 @@ pub fn get_compiled_pkg( path: pkg_path.into(), manifest_hash, deps_hash, - cached_deps, + cached_deps: cached_info_opt, program: CompiledProgram { parsed: parsed_program, - typed: typed_program, + typed_modules: typed_program_modules, }, - source_files, mapped_files, edition, compiler_info, @@ -2007,12 +2130,12 @@ pub fn compute_symbols_pre_process( ) -> Option { let mut fields_order_info = FieldOrderInfo::new(); let parsed_program = &compiled_pkg_info.program.parsed; - let typed_program = &compiled_pkg_info.program.typed; + let typed_program_modules = &compiled_pkg_info.program.typed_modules; pre_process_parsed_program(parsed_program, &mut fields_order_info); let mut cursor_context = compute_cursor_context(&compiled_pkg_info.mapped_files, cursor_info); pre_process_typed_modules( - &typed_program.modules, + typed_program_modules, &fields_order_info, &compiled_pkg_info.mapped_files, &mut computation_data.mod_outer_defs, @@ -2037,7 +2160,7 @@ pub fn compute_symbols_pre_process( // No cached dependency symbols data but we still have cached compilation results. // Fill out dependency symbols from compiled package info to cache them at the end of analysis pre_process_typed_modules( - &cached_deps.compiled_program.typing.modules, + &cached_deps.program_deps.typing.modules, &FieldOrderInfo::new(), &compiled_pkg_info.mapped_files, &mut computation_data_deps.mod_outer_defs, @@ -2110,7 +2233,7 @@ pub fn compute_symbols_parsed_program( computation_data_deps, compiled_pkg_info, None, - &cached_deps.compiled_program.parser, + &cached_deps.program_deps.parser, ); } } @@ -2122,7 +2245,7 @@ fn run_typing_analysis( mut computation_data: SymbolsComputationData, mapped_files: &MappedFiles, compiler_info: &mut CompilerInfo, - typed_program: &T::Program, + typed_program_modules: &UniqueMap, ) -> SymbolsComputationData { let mut typing_symbolicator = typing_analysis::TypingAnalysisContext { mod_outer_defs: &mut computation_data.mod_outer_defs, @@ -2139,7 +2262,7 @@ fn run_typing_analysis( }; process_typed_modules( - &typed_program.modules, + typed_program_modules, &computation_data.mod_to_alias_lengths, &mut typing_symbolicator, &mut computation_data.mod_use_defs, @@ -2151,7 +2274,7 @@ fn run_typing_analysis( // use-def map fn update_file_use_defs( computation_data: &SymbolsComputationData, - source_files: &SourceFiles, + mapped_files: &MappedFiles, file_use_defs: &mut FileUseDefs, ) { for (module_ident_str, use_defs) in &computation_data.mod_use_defs { @@ -2161,13 +2284,13 @@ fn update_file_use_defs( .mod_outer_defs .get(module_ident_str) .unwrap(); - let fpath = match source_files.get(&module_defs.fhash) { - Some((p, _, _)) => p, + let fpath = match mapped_files.file_name_mapping().get(&module_defs.fhash) { + Some(p) => p.as_path().to_string_lossy().to_string(), None => return, }; let fpath_buffer = - dunce::canonicalize(fpath.as_str()).unwrap_or_else(|_| PathBuf::from(fpath.as_str())); + dunce::canonicalize(fpath.clone()).unwrap_or_else(|_| PathBuf::from(fpath.as_str())); file_use_defs .entry(fpath_buffer) @@ -2176,25 +2299,31 @@ fn update_file_use_defs( } } -/// Process typed program for symbols computation. +/// Process typed program for symbols computation. Returns: +/// - computed symbols +/// - optional cacheable symbols data (obtained either from cache or recomputed) +/// - compiled user program pub fn compute_symbols_typed_program( computation_data: SymbolsComputationData, computation_data_deps: SymbolsComputationData, mut compiled_pkg_info: CompiledPkgInfo, cursor_context: Option, -) -> (Symbols, Option>) { +) -> ( + Symbols, + Option>, + CompiledProgram, +) { // run typing analysis for the main user program let compiler_info = &mut compiled_pkg_info.compiler_info.as_mut().unwrap(); let mapped_files = &compiled_pkg_info.mapped_files; - let source_files = &compiled_pkg_info.source_files; let mut computation_data = run_typing_analysis( computation_data, mapped_files, compiler_info, - &compiled_pkg_info.program.typed, + &compiled_pkg_info.program.typed_modules, ); let mut file_use_defs = BTreeMap::new(); - update_file_use_defs(&computation_data, source_files, &mut file_use_defs); + update_file_use_defs(&computation_data, mapped_files, &mut file_use_defs); let cacheable_symbols_data_opt = if let Some(cached_deps) = compiled_pkg_info.cached_deps.clone() { @@ -2209,14 +2338,14 @@ pub fn compute_symbols_typed_program( computation_data_deps, mapped_files, compiler_info, - &cached_deps.compiled_program.typing, + &cached_deps.program_deps.typing.modules, ); Arc::new(computation_data_deps) }; // create `file_use_defs` map and merge references to produce complete symbols data // (mod_outer_defs and def_info have already been merged to facilitate user program // analysis) - update_file_use_defs(&deps_symbols_data, source_files, &mut file_use_defs); + update_file_use_defs(&deps_symbols_data, mapped_files, &mut file_use_defs); for (def_loc, uses) in &deps_symbols_data.references { computation_data .references @@ -2246,13 +2375,14 @@ pub fn compute_symbols_typed_program( cursor_context, }, cacheable_symbols_data_opt, + compiled_pkg_info.program, ) } /// Compute symbols for a given package from the parsed and typed ASTs, /// as well as other auxiliary data provided in `compiled_pkg_info`. pub fn compute_symbols( - pkg_dependencies: Arc>>, + packages_info: Arc>>, mut compiled_pkg_info: CompiledPkgInfo, cursor_info: Option<(&PathBuf, Position)>, ) -> Symbols { @@ -2260,6 +2390,12 @@ pub fn compute_symbols( let manifest_hash = compiled_pkg_info.manifest_hash; let cached_dep_opt = compiled_pkg_info.cached_deps.clone(); let deps_hash = compiled_pkg_info.deps_hash.clone(); + let file_hashes = compiled_pkg_info + .mapped_files + .file_name_mapping() + .iter() + .map(|(fhash, fpath)| (fpath.clone(), *fhash)) + .collect::>(); let mut symbols_computation_data = SymbolsComputationData::new(); let mut symbols_computation_data_deps = SymbolsComputationData::new(); let cursor_context = compute_symbols_pre_process( @@ -2275,34 +2411,33 @@ pub fn compute_symbols( cursor_context, ); - let (symbols, cacheable_symbols_data_opt) = compute_symbols_typed_program( + let (symbols, cacheable_symbols_data_opt, program) = compute_symbols_typed_program( symbols_computation_data, symbols_computation_data_deps, compiled_pkg_info, cursor_context, ); - let mut pkg_deps = pkg_dependencies.lock().unwrap(); + let mut pkg_deps = packages_info.lock().unwrap(); if let Some(cached_deps) = cached_dep_opt { // we have at least compiled program available, either already cached // or created for the purpose of this analysis - if cached_deps.symbols_data.is_none() { - // if no symbols computation data was cached, it means that - // compiled program was created for the purpose of this analysis - // and we need to cache both - if let Some(deps_symbols_data) = cacheable_symbols_data_opt { - eprintln!("caching pre-compiled program and pre-computed symbols"); - pkg_deps.insert( - pkg_path, - PrecomputedPkgDepsInfo { - manifest_hash, - deps_hash, - deps: cached_deps.compiled_program.clone(), - deps_symbols_data, - }, - ); - } + if let Some(deps_symbols_data) = cacheable_symbols_data_opt { + // dependencies may have changed or not, but we still need to update the cache + // with new file hashes and user program info + eprintln!("caching pre-compiled program and pre-computed symbols"); + pkg_deps.insert( + pkg_path, + PrecomputedPkgInfo { + manifest_hash, + deps_hash, + deps: cached_deps.program_deps.clone(), + deps_symbols_data, + program: Arc::new(program), + file_hashes: Arc::new(file_hashes), + }, + ); } } symbols @@ -2312,22 +2447,34 @@ pub fn compute_symbols( /// correctly computed symbols should be a replacement for the old set - if symbols are not /// actually (re)computed and the diagnostics are returned, the old symbolic information should /// be retained even if it's getting out-of-date. +/// +/// Takes `modified_files` as an argument to indicate if we can retain (portion of) the cached +/// user code. If `modified_files` is `None`, we can't retain any cached user code (need to recompute) +/// everything. If `modified_files` is `Some`, we can retain cached user code for all Move files other than +/// the ones in `modified_files` (if `modified_paths` contains a path not representing +/// a Move file but rather a directory, then we conservatively do not re-use any cached info). pub fn get_symbols( - pkg_dependencies: Arc>>, + packages_info: Arc>>, ide_files_root: VfsPath, pkg_path: &Path, + modified_files: Option>, lint: LintLevel, cursor_info: Option<(&PathBuf, Position)>, ) -> Result<(Option, BTreeMap>)> { let compilation_start = Instant::now(); - let (compiled_pkg_info_opt, ide_diagnostics) = - get_compiled_pkg(pkg_dependencies.clone(), ide_files_root, pkg_path, lint)?; + let (compiled_pkg_info_opt, ide_diagnostics) = get_compiled_pkg( + packages_info.clone(), + ide_files_root, + pkg_path, + modified_files, + lint, + )?; eprintln!("compilation complete in: {:?}", compilation_start.elapsed()); let Some(compiled_pkg_info) = compiled_pkg_info_opt else { return Ok((None, ide_diagnostics)); }; let analysis_start = Instant::now(); - let symbols = compute_symbols(pkg_dependencies, compiled_pkg_info, cursor_info); + let symbols = compute_symbols(packages_info, compiled_pkg_info, cursor_info); eprintln!("analysis complete in {:?}", analysis_start.elapsed()); eprintln!("get_symbols load complete"); @@ -2462,38 +2609,39 @@ fn process_typed_modules<'a>( } } -fn file_sources(resolved_graph: &ResolvedGraph, overlay_fs: VfsPath) -> SourceFiles { - resolved_graph - .package_table - .iter() - .flat_map(|(_, rpkg)| { - rpkg.get_sources(&resolved_graph.build_options) - .unwrap() - .iter() - .map(|f| { - let is_dep = rpkg.package_path != resolved_graph.graph.root_path; - // dunce does a better job of canonicalization on Windows - let fname = dunce::canonicalize(f.as_str()) - .map(|p| p.to_string_lossy().to_string()) - .unwrap_or_else(|_| f.to_string()); - let mut contents = String::new(); - // there is a fair number of unwraps here but if we can't read the files - // that by all accounts should be in the file system, then there is not much - // we can do so it's better to fail so that we can investigate - let vfs_file_path = overlay_fs.join(fname.as_str()).unwrap(); - let mut vfs_file = vfs_file_path.open_file().unwrap(); - let _ = vfs_file.read_to_string(&mut contents); - let fhash = FileHash::new(&contents); - // write to top layer of the overlay file system so that the content - // is immutable for the duration of compilation and symbolication - let _ = vfs_file_path.parent().create_dir_all(); - let mut vfs_file = vfs_file_path.create_file().unwrap(); - let _ = vfs_file.write_all(contents.as_bytes()); - (fhash, (Symbol::from(fname), contents, is_dep)) - }) - .collect::>() - }) - .collect() +fn compute_mapped_files( + resolved_graph: &ResolvedGraph, + overlay_fs: VfsPath, +) -> (MappedFiles, String) { + let mut mapped_files: MappedFiles = MappedFiles::empty(); + let mut hasher = Sha256::new(); + for rpkg in resolved_graph.package_table.values() { + for f in rpkg.get_sources(&resolved_graph.build_options).unwrap() { + let is_dep = rpkg.package_path != resolved_graph.graph.root_path; + // dunce does a better job of canonicalization on Windows + let fname = dunce::canonicalize(f.as_str()) + .map(|p| p.to_string_lossy().to_string()) + .unwrap_or_else(|_| f.to_string()); + let mut contents = String::new(); + // there is a fair number of unwraps here but if we can't read the files + // that by all accounts should be in the file system, then there is not much + // we can do so it's better to fail so that we can investigate + let vfs_file_path = overlay_fs.join(fname.as_str()).unwrap(); + let mut vfs_file = vfs_file_path.open_file().unwrap(); + let _ = vfs_file.read_to_string(&mut contents); + let fhash = FileHash::new(&contents); + if is_dep { + hasher.update(fhash.0); + } + // write to top layer of the overlay file system so that the content + // is immutable for the duration of compilation and symbolication + let _ = vfs_file_path.parent().create_dir_all(); + let mut vfs_file = vfs_file_path.create_file().unwrap(); + let _ = vfs_file.write_all(contents.as_bytes()); + mapped_files.add(fhash, fname.into(), Arc::from(contents.into_boxed_str())); + } + } + (mapped_files, format!("{:X}", hasher.finalize())) } /// Produces module ident string of the form pkg::module to be used as a map key diff --git a/external-crates/move/crates/move-analyzer/tests/ide_testsuite.rs b/external-crates/move/crates/move-analyzer/tests/ide_testsuite.rs index 1111d7e20f3c6..23e6a24f3f6e0 100644 --- a/external-crates/move/crates/move-analyzer/tests/ide_testsuite.rs +++ b/external-crates/move/crates/move-analyzer/tests/ide_testsuite.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use std::{ - collections::BTreeMap, + collections::{BTreeMap, BTreeSet}, fs::{self, File}, io::{self, BufWriter}, path::{Path, PathBuf}, @@ -369,6 +369,7 @@ fn check_expected(expected_path: &Path, result: &str) -> anyhow::Result<()> { fn initial_symbols( project: String, + files: &BTreeSet<&String>, ) -> datatest_stable::Result<(PathBuf, CompiledPkgInfo, Symbols)> { let base_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut project_path = base_path.clone(); @@ -377,19 +378,27 @@ fn initial_symbols( let ide_files_root: VfsPath = MemoryFS::new().into(); let pkg_deps = Arc::new(Mutex::new(BTreeMap::new())); - let (compiled_pkg_info_opt, _) = get_compiled_pkg( + let (mut compiled_pkg_info_opt, _) = get_compiled_pkg( pkg_deps.clone(), ide_files_root.clone(), project_path.as_path(), + None, LintLevel::None, )?; + if let Some(f) = files.first() { + let mod_file = project_path.join("sources").join(f); + (compiled_pkg_info_opt, _) = get_compiled_pkg( + pkg_deps.clone(), + ide_files_root.clone(), + project_path.as_path(), + Some(vec![mod_file]), + LintLevel::None, + )?; + } + let compiled_pkg_info = compiled_pkg_info_opt.ok_or("PACKAGE COMPILATION FAILED")?; - let symbols = compute_symbols( - Arc::new(Mutex::new(BTreeMap::new())), - compiled_pkg_info.clone(), - None, - ); + let symbols = compute_symbols(pkg_deps.clone(), compiled_pkg_info.clone(), None); Ok((project_path, compiled_pkg_info, symbols)) } @@ -398,7 +407,7 @@ fn use_def_test_suite( project: String, file_tests: BTreeMap>, ) -> datatest_stable::Result { - let (project_path, _, symbols) = initial_symbols(project)?; + let (project_path, _, symbols) = initial_symbols(project, &file_tests.keys().collect())?; let mut output: BufWriter<_> = BufWriter::new(Vec::new()); let writer: &mut dyn io::Write = output.get_mut(); @@ -432,7 +441,8 @@ fn completion_test_suite( project: String, file_tests: BTreeMap>, ) -> datatest_stable::Result { - let (project_path, compiled_pkg_info, mut symbols) = initial_symbols(project)?; + let (project_path, compiled_pkg_info, mut symbols) = + initial_symbols(project, &file_tests.keys().collect())?; let mut output: BufWriter<_> = BufWriter::new(Vec::new()); let writer: &mut dyn io::Write = output.get_mut(); @@ -461,7 +471,8 @@ fn cursor_test_suite( project: String, file_tests: BTreeMap>, ) -> datatest_stable::Result { - let (project_path, compiled_pkg_info, mut symbols) = initial_symbols(project)?; + let (project_path, compiled_pkg_info, mut symbols) = + initial_symbols(project, &file_tests.keys().collect())?; let mut output: BufWriter<_> = BufWriter::new(Vec::new()); let writer: &mut dyn io::Write = output.get_mut(); @@ -489,7 +500,7 @@ fn hint_test_suite( project: String, file_tests: BTreeMap>, ) -> datatest_stable::Result { - let (project_path, _, symbols) = initial_symbols(project)?; + let (project_path, _, symbols) = initial_symbols(project, &file_tests.keys().collect())?; let mut output: BufWriter<_> = BufWriter::new(Vec::new()); let writer: &mut dyn io::Write = output.get_mut(); From 9b5ec7910bd07e347a454353d3fb86dc387c9ff3 Mon Sep 17 00:00:00 2001 From: Markus Legner Date: Mon, 16 Dec 2024 15:20:06 +0100 Subject: [PATCH 19/22] fix: add missing comma to sim_test macro --- crates/sui-proc-macros/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sui-proc-macros/src/lib.rs b/crates/sui-proc-macros/src/lib.rs index 89c1909fc5331..1e32d9bdf51d4 100644 --- a/crates/sui-proc-macros/src/lib.rs +++ b/crates/sui-proc-macros/src/lib.rs @@ -200,7 +200,7 @@ pub fn sim_test(args: TokenStream, item: TokenStream) -> TokenStream { let return_type = &sig.output; let body = &input.block; quote! { - #[::sui_simulator::sim_test(crate = "sui_simulator", #(#args)*)] + #[::sui_simulator::sim_test(crate = "sui_simulator", #(#args),*)] #[::sui_macros::init_static_initializers] #ignore #sig { From d4d8d4948cecb774b79a64ab8a42e3847343fd0a Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Wed, 18 Dec 2024 11:49:24 +0000 Subject: [PATCH 20/22] easy(indexer-alt): small clean-ups (#20564) ## Description - Row chunk size should default to `i16::MAX` if the field count is `0`. - Fold the `if empty { ... }` check into the `debug_assert` so "nothing gets left behind" when we're building for release (in reality the compiler will probably not have a problem with compiling this out, but it's good practice to fold the full condition into assert body). ## Test plan CI --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- .../src/pipeline/concurrent/collector.rs | 4 +--- .../src/pipeline/concurrent/mod.rs | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs index 5b3cb84c911d8..f513893e8588f 100644 --- a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs +++ b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/collector.rs @@ -31,9 +31,7 @@ impl PendingCheckpoint { /// Whether there are values left to commit from this indexed checkpoint. fn is_empty(&self) -> bool { let empty = self.values.is_empty(); - if empty { - debug_assert!(self.watermark.batch_rows == 0); - } + debug_assert!(!empty || self.watermark.batch_rows == 0); empty } diff --git a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/mod.rs b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/mod.rs index a5e43cca2adb4..d1aa0d0d756e2 100644 --- a/crates/sui-indexer-alt-framework/src/pipeline/concurrent/mod.rs +++ b/crates/sui-indexer-alt-framework/src/pipeline/concurrent/mod.rs @@ -257,9 +257,9 @@ pub(crate) fn pipeline( } const fn max_chunk_rows() -> usize { - // Handle division by zero if H::Value::FIELD_COUNT == 0 { - return 0; + i16::MAX as usize + } else { + i16::MAX as usize / H::Value::FIELD_COUNT } - i16::MAX as usize / H::Value::FIELD_COUNT } From 3e63476580a842d93339524ccb3d8f3ce2e49b99 Mon Sep 17 00:00:00 2001 From: Ashok Menon Date: Wed, 18 Dec 2024 14:37:41 +0000 Subject: [PATCH 21/22] indexer-alt: remove {sum,wal}_{obj_types,coin_balances} (#20659) ## Description Clean up the previous consistent pipelines (obj_types and coin_balances) and their schemas. ## Test plan CI --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- .../2024-12-17-162230_drop_sum_wal/down.sql | 50 +++++ .../2024-12-17-162230_drop_sum_wal/up.sql | 7 + crates/sui-indexer-alt-schema/src/objects.rs | 72 +------ crates/sui-indexer-alt-schema/src/schema.rs | 52 ----- crates/sui-indexer-alt/src/config.rs | 161 +++++++-------- crates/sui-indexer-alt/src/handlers/mod.rs | 4 - .../src/handlers/sum_coin_balances.rs | 185 ------------------ .../src/handlers/sum_obj_types.rs | 183 ----------------- .../src/handlers/wal_coin_balances.rs | 65 ------ .../src/handlers/wal_obj_types.rs | 68 ------- crates/sui-indexer-alt/src/lib.rs | 86 ++------ 11 files changed, 136 insertions(+), 797 deletions(-) create mode 100644 crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/down.sql create mode 100644 crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/up.sql delete mode 100644 crates/sui-indexer-alt/src/handlers/sum_coin_balances.rs delete mode 100644 crates/sui-indexer-alt/src/handlers/sum_obj_types.rs delete mode 100644 crates/sui-indexer-alt/src/handlers/wal_coin_balances.rs delete mode 100644 crates/sui-indexer-alt/src/handlers/wal_obj_types.rs diff --git a/crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/down.sql b/crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/down.sql new file mode 100644 index 0000000000000..5306d86af5e89 --- /dev/null +++ b/crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/down.sql @@ -0,0 +1,50 @@ +CREATE TABLE IF NOT EXISTS sum_obj_types +( + object_id BYTEA PRIMARY KEY, + object_version BIGINT NOT NULL, + owner_kind SMALLINT NOT NULL, + owner_id BYTEA, + package BYTEA, + module TEXT, + name TEXT, + instantiation BYTEA +); + +CREATE INDEX IF NOT EXISTS sum_obj_types_owner +ON sum_obj_types (owner_kind, owner_id, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_pkg +ON sum_obj_types (package, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_mod +ON sum_obj_types (package, module, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_name +ON sum_obj_types (package, module, name, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_inst +ON sum_obj_types (package, module, name, instantiation, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_owner_pkg +ON sum_obj_types (owner_kind, owner_id, package, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_owner_mod +ON sum_obj_types (owner_kind, owner_id, package, module, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_owner_name +ON sum_obj_types (owner_kind, owner_id, package, module, name, object_id, object_version); + +CREATE INDEX IF NOT EXISTS sum_obj_types_owner_inst +ON sum_obj_types (owner_kind, owner_id, package, module, name, instantiation, object_id, object_version); + +CREATE TABLE IF NOT EXISTS sum_coin_balances +( + object_id BYTEA PRIMARY KEY, + object_version BIGINT NOT NULL, + owner_id BYTEA NOT NULL, + coin_type BYTEA NOT NULL, + coin_balance BIGINT NOT NULL +); + +CREATE INDEX IF NOT EXISTS sum_coin_balances_owner_type +ON sum_coin_balances (owner_id, coin_type, coin_balance, object_id, object_version); diff --git a/crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/up.sql b/crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/up.sql new file mode 100644 index 0000000000000..03a4545304302 --- /dev/null +++ b/crates/sui-indexer-alt-schema/migrations/2024-12-17-162230_drop_sum_wal/up.sql @@ -0,0 +1,7 @@ +DROP TABLE IF EXISTS sum_obj_types; + +DROP TABLE IF EXISTS wal_obj_types; + +DROP TABLE IF EXISTS sum_coin_balances; + +DROP TABLE IF EXISTS wal_coin_balances; diff --git a/crates/sui-indexer-alt-schema/src/objects.rs b/crates/sui-indexer-alt-schema/src/objects.rs index cc4211d0645d8..15c37eef0bdc8 100644 --- a/crates/sui-indexer-alt-schema/src/objects.rs +++ b/crates/sui-indexer-alt-schema/src/objects.rs @@ -6,12 +6,8 @@ use diesel::{ sql_types::SmallInt, FromSqlRow, }; use sui_field_count::FieldCount; -use sui_types::base_types::ObjectID; -use crate::schema::{ - coin_balance_buckets, kv_objects, obj_info, obj_versions, sum_coin_balances, sum_obj_types, - wal_coin_balances, wal_obj_types, -}; +use crate::schema::{coin_balance_buckets, kv_objects, obj_info, obj_versions}; #[derive(Insertable, Debug, Clone, FieldCount)] #[diesel(table_name = kv_objects, primary_key(object_id, object_version))] @@ -30,17 +26,6 @@ pub struct StoredObjVersion { pub cp_sequence_number: i64, } -/// An insert/update or deletion of an object record, keyed on a particular Object ID and version. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct StoredObjectUpdate { - pub object_id: ObjectID, - pub object_version: u64, - pub cp_sequence_number: u64, - /// `None` means the object was deleted or wrapped at this version, `Some(x)` means it was - /// changed to `x`. - pub update: Option, -} - #[derive(AsExpression, FromSqlRow, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] #[diesel(sql_type = SmallInt)] #[repr(i16)] @@ -59,54 +44,6 @@ pub enum StoredCoinOwnerKind { Consensus = 1, } -#[derive(Insertable, Debug, Clone, FieldCount)] -#[diesel(table_name = sum_coin_balances, primary_key(object_id))] -pub struct StoredSumCoinBalance { - pub object_id: Vec, - pub object_version: i64, - pub owner_id: Vec, - pub coin_type: Vec, - pub coin_balance: i64, -} - -#[derive(Insertable, Debug, Clone, FieldCount)] -#[diesel(table_name = sum_obj_types, primary_key(object_id))] -pub struct StoredSumObjType { - pub object_id: Vec, - pub object_version: i64, - pub owner_kind: StoredOwnerKind, - pub owner_id: Option>, - pub package: Option>, - pub module: Option, - pub name: Option, - pub instantiation: Option>, -} - -#[derive(Insertable, Debug, Clone)] -#[diesel(table_name = wal_coin_balances, primary_key(object_id, object_version))] -pub struct StoredWalCoinBalance { - pub object_id: Vec, - pub object_version: i64, - pub owner_id: Option>, - pub coin_type: Option>, - pub coin_balance: Option, - pub cp_sequence_number: i64, -} - -#[derive(Insertable, Debug, Clone)] -#[diesel(table_name = wal_obj_types, primary_key(object_id, object_version))] -pub struct StoredWalObjType { - pub object_id: Vec, - pub object_version: i64, - pub owner_kind: Option, - pub owner_id: Option>, - pub package: Option>, - pub module: Option, - pub name: Option, - pub instantiation: Option>, - pub cp_sequence_number: i64, -} - #[derive(Insertable, Debug, Clone, FieldCount)] #[diesel(table_name = obj_info, primary_key(object_id, cp_sequence_number))] pub struct StoredObjInfo { @@ -131,13 +68,6 @@ pub struct StoredCoinBalanceBucket { pub coin_balance_bucket: Option, } -/// StoredObjectUpdate is a wrapper type, we want to count the fields of the inner type. -impl FieldCount for StoredObjectUpdate { - // Add one here for cp_sequence_number field, because StoredObjectUpdate is used for - // wal_* handlers, where the actual type to commit has an additional field besides fields of T. - const FIELD_COUNT: usize = T::FIELD_COUNT.saturating_add(1); -} - impl serialize::ToSql for StoredOwnerKind where i16: serialize::ToSql, diff --git a/crates/sui-indexer-alt-schema/src/schema.rs b/crates/sui-indexer-alt-schema/src/schema.rs index 9c59ec662d066..fe5c034144332 100644 --- a/crates/sui-indexer-alt-schema/src/schema.rs +++ b/crates/sui-indexer-alt-schema/src/schema.rs @@ -136,16 +136,6 @@ diesel::table! { } } -diesel::table! { - sum_coin_balances (object_id) { - object_id -> Bytea, - object_version -> Int8, - owner_id -> Bytea, - coin_type -> Bytea, - coin_balance -> Int8, - } -} - diesel::table! { sum_displays (object_type) { object_type -> Bytea, @@ -155,19 +145,6 @@ diesel::table! { } } -diesel::table! { - sum_obj_types (object_id) { - object_id -> Bytea, - object_version -> Int8, - owner_kind -> Int2, - owner_id -> Nullable, - package -> Nullable, - module -> Nullable, - name -> Nullable, - instantiation -> Nullable, - } -} - diesel::table! { sum_packages (package_id) { package_id -> Bytea, @@ -225,31 +202,6 @@ diesel::table! { } } -diesel::table! { - wal_coin_balances (object_id, object_version) { - object_id -> Bytea, - object_version -> Int8, - owner_id -> Nullable, - coin_type -> Nullable, - coin_balance -> Nullable, - cp_sequence_number -> Int8, - } -} - -diesel::table! { - wal_obj_types (object_id, object_version) { - object_id -> Bytea, - object_version -> Int8, - owner_kind -> Nullable, - owner_id -> Nullable, - package -> Nullable, - module -> Nullable, - name -> Nullable, - instantiation -> Nullable, - cp_sequence_number -> Int8, - } -} - diesel::allow_tables_to_appear_in_same_query!( coin_balance_buckets, ev_emit_mod, @@ -264,9 +216,7 @@ diesel::allow_tables_to_appear_in_same_query!( kv_transactions, obj_info, obj_versions, - sum_coin_balances, sum_displays, - sum_obj_types, sum_packages, tx_affected_addresses, tx_affected_objects, @@ -274,6 +224,4 @@ diesel::allow_tables_to_appear_in_same_query!( tx_calls, tx_digests, tx_kinds, - wal_coin_balances, - wal_obj_types, ); diff --git a/crates/sui-indexer-alt/src/config.rs b/crates/sui-indexer-alt/src/config.rs index ab297db368069..241e765c08351 100644 --- a/crates/sui-indexer-alt/src/config.rs +++ b/crates/sui-indexer-alt/src/config.rs @@ -46,17 +46,10 @@ pub struct IndexerConfig { pub extra: toml::Table, } -#[derive(Clone)] +#[derive(Default, Clone)] pub struct ConsistencyConfig { - /// How often to check whether write-ahead logs related to the consistent range can be - /// pruned. - pub consistent_pruning_interval_ms: u64, - - /// How long to wait before honouring reader low watermarks. - pub pruner_delay_ms: u64, - /// Number of checkpoints to delay indexing summary tables for. - pub consistent_range: Option, + pub consistent_range: u64, } // Configuration layers apply overrides over a base configuration. When reading configs from a @@ -81,8 +74,6 @@ pub struct IngestionLayer { #[DefaultConfig] #[derive(Clone, Default, Debug)] pub struct ConsistencyLayer { - consistent_pruning_interval_ms: Option, - pruner_delay_ms: Option, consistent_range: Option, #[serde(flatten)] @@ -137,13 +128,7 @@ pub struct PrunerLayer { #[derive(Clone, Default, Debug)] #[serde(rename_all = "snake_case")] pub struct PipelineLayer { - // Consistent pipelines (a sequential pipeline with a write-ahead log) - pub sum_coin_balances: Option, - pub wal_coin_balances: Option, - pub sum_obj_types: Option, - pub wal_obj_types: Option, - - // Sequential pipelines without a write-ahead log + // Sequential pipelines pub sum_displays: Option, pub sum_packages: Option, @@ -214,11 +199,7 @@ impl ConsistencyLayer { pub fn finish(self, base: ConsistencyConfig) -> ConsistencyConfig { check_extra("consistency", self.extra); ConsistencyConfig { - consistent_pruning_interval_ms: self - .consistent_pruning_interval_ms - .unwrap_or(base.consistent_pruning_interval_ms), - pruner_delay_ms: self.pruner_delay_ms.unwrap_or(base.pruner_delay_ms), - consistent_range: self.consistent_range.or(base.consistent_range), + consistent_range: self.consistent_range.unwrap_or(base.consistent_range), } } } @@ -286,10 +267,6 @@ impl PipelineLayer { /// configure. pub fn example() -> Self { PipelineLayer { - sum_coin_balances: Some(Default::default()), - wal_coin_balances: Some(Default::default()), - sum_obj_types: Some(Default::default()), - wal_obj_types: Some(Default::default()), sum_displays: Some(Default::default()), sum_packages: Some(Default::default()), obj_info: Some(Default::default()), @@ -355,10 +332,6 @@ impl Merge for ConsistencyLayer { check_extra("consistency", self.extra); check_extra("consistency", other.extra); ConsistencyLayer { - consistent_pruning_interval_ms: other - .consistent_pruning_interval_ms - .or(self.consistent_pruning_interval_ms), - pruner_delay_ms: other.pruner_delay_ms.or(self.pruner_delay_ms), consistent_range: other.consistent_range.or(self.consistent_range), extra: Default::default(), } @@ -428,10 +401,6 @@ impl Merge for PipelineLayer { check_extra("pipeline", self.extra); check_extra("pipeline", other.extra); PipelineLayer { - sum_coin_balances: self.sum_coin_balances.merge(other.sum_coin_balances), - wal_coin_balances: self.wal_coin_balances.merge(other.wal_coin_balances), - sum_obj_types: self.sum_obj_types.merge(other.sum_obj_types), - wal_obj_types: self.wal_obj_types.merge(other.wal_obj_types), sum_displays: self.sum_displays.merge(other.sum_displays), sum_packages: self.sum_packages.merge(other.sum_packages), obj_info: self.obj_info.merge(other.obj_info), @@ -473,16 +442,6 @@ impl Merge for Option { } } -impl Default for ConsistencyConfig { - fn default() -> Self { - Self { - consistent_pruning_interval_ms: 300_000, - pruner_delay_ms: 120_000, - consistent_range: None, - } - } -} - impl From for IngestionLayer { fn from(config: IngestionConfig) -> Self { Self { @@ -497,9 +456,7 @@ impl From for IngestionLayer { impl From for ConsistencyLayer { fn from(config: ConsistencyConfig) -> Self { Self { - consistent_pruning_interval_ms: Some(config.consistent_pruning_interval_ms), - pruner_delay_ms: Some(config.pruner_delay_ms), - consistent_range: config.consistent_range, + consistent_range: Some(config.consistent_range), extra: Default::default(), } } @@ -579,15 +536,11 @@ mod tests { #[test] fn merge_simple() { let this = ConsistencyLayer { - consistent_pruning_interval_ms: None, - pruner_delay_ms: Some(2000), consistent_range: Some(3000), extra: Default::default(), }; let that = ConsistencyLayer { - consistent_pruning_interval_ms: Some(1000), - pruner_delay_ms: None, consistent_range: Some(4000), extra: Default::default(), }; @@ -598,8 +551,6 @@ mod tests { assert_matches!( this_then_that, ConsistencyLayer { - consistent_pruning_interval_ms: Some(1000), - pruner_delay_ms: Some(2000), consistent_range: Some(4000), extra: _, } @@ -608,8 +559,6 @@ mod tests { assert_matches!( that_then_this, ConsistencyLayer { - consistent_pruning_interval_ms: Some(1000), - pruner_delay_ms: Some(2000), consistent_range: Some(3000), extra: _, } @@ -619,13 +568,6 @@ mod tests { #[test] fn merge_recursive() { let this = PipelineLayer { - sum_coin_balances: None, - sum_obj_types: Some(CommitterLayer { - write_concurrency: Some(5), - collect_interval_ms: Some(500), - watermark_interval_ms: None, - extra: Default::default(), - }), sum_displays: Some(SequentialLayer { committer: Some(CommitterLayer { write_concurrency: Some(10), @@ -636,17 +578,20 @@ mod tests { checkpoint_lag: Some(100), extra: Default::default(), }), + sum_packages: None, + ev_emit_mod: Some(ConcurrentLayer { + committer: Some(CommitterLayer { + write_concurrency: Some(5), + collect_interval_ms: Some(500), + watermark_interval_ms: None, + extra: Default::default(), + }), + ..Default::default() + }), ..Default::default() }; let that = PipelineLayer { - sum_coin_balances: Some(CommitterLayer { - write_concurrency: Some(10), - collect_interval_ms: None, - watermark_interval_ms: Some(1000), - extra: Default::default(), - }), - sum_obj_types: None, sum_displays: Some(SequentialLayer { committer: Some(CommitterLayer { write_concurrency: Some(5), @@ -657,6 +602,16 @@ mod tests { checkpoint_lag: Some(200), extra: Default::default(), }), + sum_packages: Some(SequentialLayer { + committer: Some(CommitterLayer { + write_concurrency: Some(10), + collect_interval_ms: None, + watermark_interval_ms: Some(1000), + extra: Default::default(), + }), + ..Default::default() + }), + ev_emit_mod: None, ..Default::default() }; @@ -666,18 +621,6 @@ mod tests { assert_matches!( this_then_that, PipelineLayer { - sum_coin_balances: Some(CommitterLayer { - write_concurrency: Some(10), - collect_interval_ms: None, - watermark_interval_ms: Some(1000), - extra: _, - }), - sum_obj_types: Some(CommitterLayer { - write_concurrency: Some(5), - collect_interval_ms: Some(500), - watermark_interval_ms: None, - extra: _, - }), sum_displays: Some(SequentialLayer { committer: Some(CommitterLayer { write_concurrency: Some(5), @@ -688,6 +631,27 @@ mod tests { checkpoint_lag: Some(200), extra: _, }), + sum_packages: Some(SequentialLayer { + committer: Some(CommitterLayer { + write_concurrency: Some(10), + collect_interval_ms: None, + watermark_interval_ms: Some(1000), + extra: _, + }), + checkpoint_lag: None, + extra: _, + }), + ev_emit_mod: Some(ConcurrentLayer { + committer: Some(CommitterLayer { + write_concurrency: Some(5), + collect_interval_ms: Some(500), + watermark_interval_ms: None, + extra: _, + }), + pruner: None, + checkpoint_lag: None, + extra: _, + }), .. }, ); @@ -695,18 +659,6 @@ mod tests { assert_matches!( that_then_this, PipelineLayer { - sum_coin_balances: Some(CommitterLayer { - write_concurrency: Some(10), - collect_interval_ms: None, - watermark_interval_ms: Some(1000), - extra: _, - }), - sum_obj_types: Some(CommitterLayer { - write_concurrency: Some(5), - collect_interval_ms: Some(500), - watermark_interval_ms: None, - extra: _, - }), sum_displays: Some(SequentialLayer { committer: Some(CommitterLayer { write_concurrency: Some(10), @@ -717,6 +669,27 @@ mod tests { checkpoint_lag: Some(100), extra: _, }), + sum_packages: Some(SequentialLayer { + committer: Some(CommitterLayer { + write_concurrency: Some(10), + collect_interval_ms: None, + watermark_interval_ms: Some(1000), + extra: _, + }), + checkpoint_lag: None, + extra: _, + }), + ev_emit_mod: Some(ConcurrentLayer { + committer: Some(CommitterLayer { + write_concurrency: Some(5), + collect_interval_ms: Some(500), + watermark_interval_ms: None, + extra: _, + }), + pruner: None, + checkpoint_lag: None, + extra: _, + }), .. }, ); diff --git a/crates/sui-indexer-alt/src/handlers/mod.rs b/crates/sui-indexer-alt/src/handlers/mod.rs index 3897d29838964..16fd82a9b56c4 100644 --- a/crates/sui-indexer-alt/src/handlers/mod.rs +++ b/crates/sui-indexer-alt/src/handlers/mod.rs @@ -15,9 +15,7 @@ pub(crate) mod kv_transactions; pub(crate) mod obj_info; pub(crate) mod obj_info_pruner; pub(crate) mod obj_versions; -pub(crate) mod sum_coin_balances; pub(crate) mod sum_displays; -pub(crate) mod sum_obj_types; pub(crate) mod sum_packages; pub(crate) mod tx_affected_addresses; pub(crate) mod tx_affected_objects; @@ -25,5 +23,3 @@ pub(crate) mod tx_balance_changes; pub(crate) mod tx_calls; pub(crate) mod tx_digests; pub(crate) mod tx_kinds; -pub(crate) mod wal_coin_balances; -pub(crate) mod wal_obj_types; diff --git a/crates/sui-indexer-alt/src/handlers/sum_coin_balances.rs b/crates/sui-indexer-alt/src/handlers/sum_coin_balances.rs deleted file mode 100644 index 0e2a067440317..0000000000000 --- a/crates/sui-indexer-alt/src/handlers/sum_coin_balances.rs +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - collections::{btree_map::Entry, BTreeMap}, - sync::Arc, -}; - -use anyhow::{anyhow, bail, ensure}; -use diesel::{upsert::excluded, ExpressionMethods}; -use diesel_async::RunQueryDsl; -use futures::future::{try_join_all, Either}; -use sui_field_count::FieldCount; -use sui_indexer_alt_framework::pipeline::{sequential::Handler, Processor}; -use sui_indexer_alt_schema::{ - objects::{StoredObjectUpdate, StoredSumCoinBalance}, - schema::sum_coin_balances, -}; -use sui_pg_db as db; -use sui_types::{ - base_types::ObjectID, effects::TransactionEffectsAPI, full_checkpoint_content::CheckpointData, - object::Owner, -}; - -const MAX_INSERT_CHUNK_ROWS: usize = i16::MAX as usize / StoredSumCoinBalance::FIELD_COUNT; -const MAX_DELETE_CHUNK_ROWS: usize = i16::MAX as usize; - -pub(crate) struct SumCoinBalances; - -impl Processor for SumCoinBalances { - const NAME: &'static str = "sum_coin_balances"; - - type Value = StoredObjectUpdate; - - fn process(&self, checkpoint: &Arc) -> anyhow::Result> { - let CheckpointData { - transactions, - checkpoint_summary, - .. - } = checkpoint.as_ref(); - - let cp_sequence_number = checkpoint_summary.sequence_number; - let mut values: BTreeMap = BTreeMap::new(); - let mut coin_types: BTreeMap> = BTreeMap::new(); - - // Iterate over transactions in reverse so we see the latest version of each object first. - for tx in transactions.iter().rev() { - // Find all coins in the transaction's inputs and outputs. - for object in tx.input_objects.iter().chain(tx.output_objects.iter()) { - if let Some(coin_type) = object.type_().and_then(|t| t.coin_type_maybe()) { - let serialized = bcs::to_bytes(&coin_type) - .map_err(|_| anyhow!("Failed to serialize type for {}", object.id()))?; - - coin_types.insert(object.id(), serialized); - } - } - - // Deleted and wrapped coins - for change in tx.effects.object_changes() { - // The object is not deleted/wrapped, or if it is it was unwrapped in the same - // transaction. - if change.output_digest.is_some() || change.input_version.is_none() { - continue; - } - - // Object is not a coin - if !coin_types.contains_key(&change.id) { - continue; - } - - let object_id = change.id; - let object_version = tx.effects.lamport_version().value(); - match values.entry(object_id) { - Entry::Occupied(entry) => { - ensure!(entry.get().object_version > object_version); - } - - Entry::Vacant(entry) => { - entry.insert(StoredObjectUpdate { - object_id, - object_version, - cp_sequence_number, - update: None, - }); - } - } - } - - // Modified and created coins. - for object in &tx.output_objects { - let object_id = object.id(); - let object_version = object.version().value(); - - let Some(coin_type) = coin_types.get(&object_id) else { - continue; - }; - - // Coin balance only tracks address-owned objects - let Owner::AddressOwner(owner_id) = object.owner() else { - continue; - }; - - let Some(coin) = object.as_coin_maybe() else { - bail!("Failed to deserialize Coin for {object_id}"); - }; - - match values.entry(object_id) { - Entry::Occupied(entry) => { - ensure!(entry.get().object_version > object_version); - } - - Entry::Vacant(entry) => { - entry.insert(StoredObjectUpdate { - object_id, - object_version, - cp_sequence_number, - update: Some(StoredSumCoinBalance { - object_id: object_id.to_vec(), - object_version: object_version as i64, - owner_id: owner_id.to_vec(), - coin_type: coin_type.clone(), - coin_balance: coin.balance.value() as i64, - }), - }); - } - } - } - } - - Ok(values.into_values().collect()) - } -} - -#[async_trait::async_trait] -impl Handler for SumCoinBalances { - type Batch = BTreeMap; - - fn batch(batch: &mut Self::Batch, updates: Vec) { - // `updates` are guaranteed to be provided in checkpoint order, so blindly inserting them - // will result in the batch containing the most up-to-date update for each object. - for update in updates { - batch.insert(update.object_id, update); - } - } - - async fn commit(batch: &Self::Batch, conn: &mut db::Connection<'_>) -> anyhow::Result { - let mut updates = vec![]; - let mut deletes = vec![]; - - for update in batch.values() { - if let Some(update) = &update.update { - updates.push(update.clone()); - } else { - deletes.push(update.object_id.to_vec()); - } - } - let update_chunks = updates.chunks(MAX_INSERT_CHUNK_ROWS).map(Either::Left); - let delete_chunks = deletes.chunks(MAX_DELETE_CHUNK_ROWS).map(Either::Right); - - let futures = update_chunks.chain(delete_chunks).map(|chunk| match chunk { - Either::Left(update) => Either::Left( - diesel::insert_into(sum_coin_balances::table) - .values(update) - .on_conflict(sum_coin_balances::object_id) - .do_update() - .set(( - sum_coin_balances::object_version - .eq(excluded(sum_coin_balances::object_version)), - sum_coin_balances::owner_id.eq(excluded(sum_coin_balances::owner_id)), - sum_coin_balances::coin_balance - .eq(excluded(sum_coin_balances::coin_balance)), - )) - .execute(conn), - ), - - Either::Right(delete) => Either::Right( - diesel::delete(sum_coin_balances::table) - .filter(sum_coin_balances::object_id.eq_any(delete.iter().cloned())) - .execute(conn), - ), - }); - - Ok(try_join_all(futures).await?.into_iter().sum()) - } -} diff --git a/crates/sui-indexer-alt/src/handlers/sum_obj_types.rs b/crates/sui-indexer-alt/src/handlers/sum_obj_types.rs deleted file mode 100644 index 84e61e2e01523..0000000000000 --- a/crates/sui-indexer-alt/src/handlers/sum_obj_types.rs +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::{ - collections::{btree_map::Entry, BTreeMap}, - sync::Arc, -}; - -use anyhow::{anyhow, ensure}; -use diesel::{upsert::excluded, ExpressionMethods}; -use diesel_async::RunQueryDsl; -use futures::future::{try_join_all, Either}; -use sui_field_count::FieldCount; -use sui_indexer_alt_framework::pipeline::{sequential::Handler, Processor}; -use sui_indexer_alt_schema::{ - objects::{StoredObjectUpdate, StoredOwnerKind, StoredSumObjType}, - schema::sum_obj_types, -}; -use sui_pg_db as db; -use sui_types::{ - base_types::ObjectID, effects::TransactionEffectsAPI, full_checkpoint_content::CheckpointData, - object::Owner, -}; - -const MAX_INSERT_CHUNK_ROWS: usize = i16::MAX as usize / StoredSumObjType::FIELD_COUNT; -const MAX_DELETE_CHUNK_ROWS: usize = i16::MAX as usize; - -pub(crate) struct SumObjTypes; - -impl Processor for SumObjTypes { - const NAME: &'static str = "sum_obj_types"; - - type Value = StoredObjectUpdate; - - fn process(&self, checkpoint: &Arc) -> anyhow::Result> { - let CheckpointData { - transactions, - checkpoint_summary, - .. - } = checkpoint.as_ref(); - - let cp_sequence_number = checkpoint_summary.sequence_number; - let mut values: BTreeMap = BTreeMap::new(); - - // Iterate over transactions in reverse so we see the latest version of each object first. - for tx in transactions.iter().rev() { - // Deleted and wrapped objects -- objects that show up without a digest in - // `object_changes` are either deleted or wrapped. Objects without an input version - // must have been unwrapped and deleted, meaning they do not need to be deleted from - // our records. - for change in tx.effects.object_changes() { - if change.output_digest.is_some() || change.input_version.is_none() { - continue; - } - - let object_id = change.id; - let object_version = tx.effects.lamport_version().value(); - match values.entry(object_id) { - Entry::Occupied(entry) => { - ensure!(entry.get().object_version > object_version); - } - - Entry::Vacant(entry) => { - entry.insert(StoredObjectUpdate { - object_id, - object_version, - cp_sequence_number, - update: None, - }); - } - } - } - - // Modified and created objects. - for object in &tx.output_objects { - let object_id = object.id(); - let object_version = object.version().value(); - match values.entry(object_id) { - Entry::Occupied(entry) => { - ensure!(entry.get().object_version > object_version); - } - - Entry::Vacant(entry) => { - let type_ = object.type_(); - entry.insert(StoredObjectUpdate { - object_id, - object_version, - cp_sequence_number, - update: Some(StoredSumObjType { - object_id: object_id.to_vec(), - object_version: object_version as i64, - - owner_kind: match object.owner() { - Owner::AddressOwner(_) => StoredOwnerKind::Address, - Owner::ObjectOwner(_) => StoredOwnerKind::Object, - Owner::Shared { .. } => StoredOwnerKind::Shared, - Owner::Immutable => StoredOwnerKind::Immutable, - // TODO: Implement support for ConsensusV2 objects. - Owner::ConsensusV2 { .. } => todo!(), - }, - - owner_id: match object.owner() { - Owner::AddressOwner(a) => Some(a.to_vec()), - Owner::ObjectOwner(o) => Some(o.to_vec()), - _ => None, - }, - - package: type_.map(|t| t.address().to_vec()), - module: type_.map(|t| t.module().to_string()), - name: type_.map(|t| t.name().to_string()), - instantiation: type_ - .map(|t| bcs::to_bytes(&t.type_params())) - .transpose() - .map_err(|e| { - anyhow!( - "Failed to serialize type parameters for {}: {e}", - object - .id() - .to_canonical_display(/* with_prefix */ true), - ) - })?, - }), - }); - } - } - } - } - - Ok(values.into_values().collect()) - } -} - -#[async_trait::async_trait] -impl Handler for SumObjTypes { - type Batch = BTreeMap; - - fn batch(batch: &mut Self::Batch, updates: Vec) { - // `updates` are guaranteed to be provided in checkpoint order, so blindly inserting them - // will result in the batch containing the most up-to-date update for each object. - for update in updates { - batch.insert(update.object_id, update); - } - } - - async fn commit(values: &Self::Batch, conn: &mut db::Connection<'_>) -> anyhow::Result { - let mut updates = vec![]; - let mut deletes = vec![]; - - for update in values.values() { - if let Some(update) = &update.update { - updates.push(update.clone()); - } else { - deletes.push(update.object_id.to_vec()); - } - } - - let update_chunks = updates.chunks(MAX_INSERT_CHUNK_ROWS).map(Either::Left); - let delete_chunks = deletes.chunks(MAX_DELETE_CHUNK_ROWS).map(Either::Right); - - let futures = update_chunks.chain(delete_chunks).map(|chunk| match chunk { - Either::Left(update) => Either::Left( - diesel::insert_into(sum_obj_types::table) - .values(update) - .on_conflict(sum_obj_types::object_id) - .do_update() - .set(( - sum_obj_types::object_version.eq(excluded(sum_obj_types::object_version)), - sum_obj_types::owner_kind.eq(excluded(sum_obj_types::owner_kind)), - sum_obj_types::owner_id.eq(excluded(sum_obj_types::owner_id)), - )) - .execute(conn), - ), - - Either::Right(delete) => Either::Right( - diesel::delete(sum_obj_types::table) - .filter(sum_obj_types::object_id.eq_any(delete.iter().cloned())) - .execute(conn), - ), - }); - - Ok(try_join_all(futures).await?.into_iter().sum()) - } -} diff --git a/crates/sui-indexer-alt/src/handlers/wal_coin_balances.rs b/crates/sui-indexer-alt/src/handlers/wal_coin_balances.rs deleted file mode 100644 index 849b2d1faed7f..0000000000000 --- a/crates/sui-indexer-alt/src/handlers/wal_coin_balances.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::sync::Arc; - -use anyhow::Result; -use diesel::{ExpressionMethods, QueryDsl}; -use diesel_async::RunQueryDsl; -use sui_indexer_alt_framework::pipeline::{concurrent::Handler, Processor}; -use sui_indexer_alt_schema::{ - objects::{StoredObjectUpdate, StoredSumCoinBalance, StoredWalCoinBalance}, - schema::wal_coin_balances, -}; -use sui_pg_db as db; -use sui_types::full_checkpoint_content::CheckpointData; - -use super::sum_coin_balances::SumCoinBalances; - -pub(crate) struct WalCoinBalances; - -impl Processor for WalCoinBalances { - const NAME: &'static str = "wal_coin_balances"; - - type Value = StoredObjectUpdate; - - fn process(&self, checkpoint: &Arc) -> Result> { - SumCoinBalances.process(checkpoint) - } -} - -#[async_trait::async_trait] -impl Handler for WalCoinBalances { - const MIN_EAGER_ROWS: usize = 100; - const MAX_PENDING_ROWS: usize = 10000; - - async fn commit(values: &[Self::Value], conn: &mut db::Connection<'_>) -> Result { - let values: Vec<_> = values - .iter() - .map(|value| StoredWalCoinBalance { - object_id: value.object_id.to_vec(), - object_version: value.object_version as i64, - - owner_id: value.update.as_ref().map(|o| o.owner_id.clone()), - - coin_type: value.update.as_ref().map(|o| o.coin_type.clone()), - coin_balance: value.update.as_ref().map(|o| o.coin_balance), - - cp_sequence_number: value.cp_sequence_number as i64, - }) - .collect(); - - Ok(diesel::insert_into(wal_coin_balances::table) - .values(&values) - .on_conflict_do_nothing() - .execute(conn) - .await?) - } - - async fn prune(from: u64, to: u64, conn: &mut db::Connection<'_>) -> Result { - let filter = wal_coin_balances::table - .filter(wal_coin_balances::cp_sequence_number.between(from as i64, to as i64 - 1)); - - Ok(diesel::delete(filter).execute(conn).await?) - } -} diff --git a/crates/sui-indexer-alt/src/handlers/wal_obj_types.rs b/crates/sui-indexer-alt/src/handlers/wal_obj_types.rs deleted file mode 100644 index c0a6feb51cd24..0000000000000 --- a/crates/sui-indexer-alt/src/handlers/wal_obj_types.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use std::sync::Arc; - -use anyhow::Result; -use diesel::{ExpressionMethods, QueryDsl}; -use diesel_async::RunQueryDsl; -use sui_indexer_alt_framework::pipeline::{concurrent::Handler, Processor}; -use sui_indexer_alt_schema::{ - objects::{StoredObjectUpdate, StoredSumObjType, StoredWalObjType}, - schema::wal_obj_types, -}; -use sui_pg_db as db; -use sui_types::full_checkpoint_content::CheckpointData; - -use super::sum_obj_types::SumObjTypes; - -pub(crate) struct WalObjTypes; - -impl Processor for WalObjTypes { - const NAME: &'static str = "wal_obj_types"; - - type Value = StoredObjectUpdate; - - fn process(&self, checkpoint: &Arc) -> Result> { - SumObjTypes.process(checkpoint) - } -} - -#[async_trait::async_trait] -impl Handler for WalObjTypes { - const MIN_EAGER_ROWS: usize = 100; - const MAX_PENDING_ROWS: usize = 10000; - - async fn commit(values: &[Self::Value], conn: &mut db::Connection<'_>) -> Result { - let values: Vec<_> = values - .iter() - .map(|value| StoredWalObjType { - object_id: value.object_id.to_vec(), - object_version: value.object_version as i64, - - owner_kind: value.update.as_ref().map(|o| o.owner_kind), - owner_id: value.update.as_ref().and_then(|o| o.owner_id.clone()), - - package: value.update.as_ref().and_then(|o| o.package.clone()), - module: value.update.as_ref().and_then(|o| o.module.clone()), - name: value.update.as_ref().and_then(|o| o.name.clone()), - instantiation: value.update.as_ref().and_then(|o| o.instantiation.clone()), - - cp_sequence_number: value.cp_sequence_number as i64, - }) - .collect(); - - Ok(diesel::insert_into(wal_obj_types::table) - .values(&values) - .on_conflict_do_nothing() - .execute(conn) - .await?) - } - - async fn prune(from: u64, to: u64, conn: &mut db::Connection<'_>) -> Result { - let filter = wal_obj_types::table - .filter(wal_obj_types::cp_sequence_number.between(from as i64, to as i64 - 1)); - - Ok(diesel::delete(filter).execute(conn).await?) - } -} diff --git a/crates/sui-indexer-alt/src/lib.rs b/crates/sui-indexer-alt/src/lib.rs index 4889d99bc83e2..15f41c9ef3b06 100644 --- a/crates/sui-indexer-alt/src/lib.rs +++ b/crates/sui-indexer-alt/src/lib.rs @@ -11,11 +11,10 @@ use handlers::{ ev_emit_mod::EvEmitMod, ev_struct_inst::EvStructInst, kv_checkpoints::KvCheckpoints, kv_epoch_ends::KvEpochEnds, kv_epoch_starts::KvEpochStarts, kv_feature_flags::KvFeatureFlags, kv_objects::KvObjects, kv_protocol_configs::KvProtocolConfigs, kv_transactions::KvTransactions, - obj_info::ObjInfo, obj_versions::ObjVersions, sum_coin_balances::SumCoinBalances, - sum_displays::SumDisplays, sum_obj_types::SumObjTypes, sum_packages::SumPackages, - tx_affected_addresses::TxAffectedAddresses, tx_affected_objects::TxAffectedObjects, - tx_balance_changes::TxBalanceChanges, tx_calls::TxCalls, tx_digests::TxDigests, - tx_kinds::TxKinds, wal_coin_balances::WalCoinBalances, wal_obj_types::WalObjTypes, + obj_info::ObjInfo, obj_versions::ObjVersions, sum_displays::SumDisplays, + sum_packages::SumPackages, tx_affected_addresses::TxAffectedAddresses, + tx_affected_objects::TxAffectedObjects, tx_balance_changes::TxBalanceChanges, + tx_calls::TxCalls, tx_digests::TxDigests, tx_kinds::TxKinds, }; use sui_indexer_alt_framework::ingestion::{ClientArgs, IngestionConfig}; use sui_indexer_alt_framework::pipeline::{ @@ -57,10 +56,6 @@ pub async fn start_indexer( } = indexer_config.finish(); let PipelineLayer { - sum_coin_balances, - wal_coin_balances, - sum_obj_types, - wal_obj_types, sum_displays, sum_packages, obj_info, @@ -87,29 +82,10 @@ pub async fn start_indexer( } = pipeline.finish(); let ingestion = ingestion.finish(IngestionConfig::default()); - - let ConsistencyConfig { - consistent_pruning_interval_ms, - pruner_delay_ms, - consistent_range, - } = consistency.finish(ConsistencyConfig::default()); - + let consistency = consistency.finish(ConsistencyConfig::default()); let committer = committer.finish(CommitterConfig::default()); let pruner = pruner.finish(PrunerConfig::default()); - // Pipelines that are split up into a summary table, and a write-ahead log prune their - // write-ahead log so it contains just enough information to overlap with the summary table. - let consistent_range = consistent_range.unwrap_or_default(); - let pruner_config = (consistent_range != 0).then(|| PrunerConfig { - interval_ms: consistent_pruning_interval_ms, - delay_ms: pruner_delay_ms, - // Retain at least twice as much data as the lag, to guarantee overlap between the - // summary table and the write-ahead log. - retention: consistent_range * 2, - // Prune roughly five minutes of data in one go. - max_chunk_size: 5 * 300, - }); - let cancel = CancellationToken::new(); let retry_interval = ingestion.retry_interval(); @@ -171,40 +147,10 @@ pub async fn start_indexer( }; } + // A consistent pipeline consists of two concurrent pipelines. The first (main) one writes + // new data, and the second one lags behind deleting data that has fallen out of the consistent + // range. macro_rules! add_consistent { - ($sum_handler:expr, $sum_config:expr; $wal_handler:expr, $wal_config:expr) => { - if let Some(sum_layer) = $sum_config { - indexer - .sequential_pipeline( - $sum_handler, - SequentialConfig { - committer: sum_layer.finish(committer.clone()), - checkpoint_lag: consistent_range, - }, - ) - .await?; - - if let Some(pruner_config) = pruner_config.clone() { - indexer - .concurrent_pipeline( - $wal_handler, - ConcurrentConfig { - committer: $wal_config - .unwrap_or_default() - .finish(committer.clone()), - pruner: Some(pruner_config), - checkpoint_lag: None, - }, - ) - .await?; - } - } - }; - } - - // Add two concurrent pipelines, one as the main pipeline, and one as a lagged pruner. - // The lagged pruner will prune the main pipeline's data based on the consistency range. - macro_rules! add_concurrent_with_lagged_pruner { ($main_handler:expr, $main_config:expr; $lagged_handler:expr, $lagged_config:expr) => { if let Some(main_layer) = $main_config { indexer @@ -224,7 +170,7 @@ pub async fn start_indexer( $lagged_config.unwrap_or_default().finish(ConcurrentConfig { committer: committer.clone(), pruner: None, - checkpoint_lag: Some(consistent_range), + checkpoint_lag: Some(consistency.consistent_range), }), ) .await?; @@ -240,26 +186,16 @@ pub async fn start_indexer( add_concurrent!(KvProtocolConfigs(genesis.clone()), kv_protocol_configs); } - add_consistent!( - SumCoinBalances, sum_coin_balances; - WalCoinBalances, wal_coin_balances - ); - - add_consistent!( - SumObjTypes, sum_obj_types; - WalObjTypes, wal_obj_types - ); - // Other summary tables (without write-ahead log) add_sequential!(SumDisplays, sum_displays); add_sequential!(SumPackages, sum_packages); - add_concurrent_with_lagged_pruner!( + add_consistent!( ObjInfo, obj_info; ObjInfoPruner, obj_info_pruner ); - add_concurrent_with_lagged_pruner!( + add_consistent!( CoinBalanceBuckets, coin_balance_buckets; CoinBalanceBucketsPruner, coin_balance_buckets_pruner ); From 84141f575c7759a296bb4cb7e0a93ef28d042d63 Mon Sep 17 00:00:00 2001 From: phoenix <51927076+phoenix-o@users.noreply.github.com> Date: Wed, 18 Dec 2024 12:14:48 -0500 Subject: [PATCH 22/22] Revert "[kv store] add watermark table to bigtable (#20390)" (#20670) This reverts commit 7eadb135535da670a73b4c1dd790cf829586dbb9. ## Description The hot row-hot cell pattern significantly slows down writes to Bigtable, impacting the ingestion speed of core pipelines --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: - [ ] REST API: --- crates/sui-data-ingestion/src/lib.rs | 2 +- crates/sui-data-ingestion/src/main.rs | 19 +------ .../sui-data-ingestion/src/progress_store.rs | 15 ++---- crates/sui-kvstore/src/bigtable/client.rs | 49 ++++--------------- crates/sui-kvstore/src/bigtable/init.sh | 2 +- crates/sui-kvstore/src/lib.rs | 6 --- 6 files changed, 17 insertions(+), 76 deletions(-) diff --git a/crates/sui-data-ingestion/src/lib.rs b/crates/sui-data-ingestion/src/lib.rs index 1853b8f0af884..3ef48d9ec9713 100644 --- a/crates/sui-data-ingestion/src/lib.rs +++ b/crates/sui-data-ingestion/src/lib.rs @@ -4,7 +4,7 @@ mod progress_store; mod workers; -pub use progress_store::IngestionWorkflowsProgressStore; +pub use progress_store::DynamoDBProgressStore; pub use workers::{ ArchivalConfig, ArchivalReducer, ArchivalWorker, BlobTaskConfig, BlobWorker, KVStoreTaskConfig, KVStoreWorker, diff --git a/crates/sui-data-ingestion/src/main.rs b/crates/sui-data-ingestion/src/main.rs index f403a01c2d447..cd2c00694a07d 100644 --- a/crates/sui-data-ingestion/src/main.rs +++ b/crates/sui-data-ingestion/src/main.rs @@ -9,7 +9,7 @@ use std::path::PathBuf; use std::time::Duration; use sui_data_ingestion::{ ArchivalConfig, ArchivalReducer, ArchivalWorker, BlobTaskConfig, BlobWorker, - IngestionWorkflowsProgressStore, KVStoreTaskConfig, KVStoreWorker, + DynamoDBProgressStore, KVStoreTaskConfig, KVStoreWorker, }; use sui_data_ingestion_core::{DataIngestionMetrics, ReaderOptions}; use sui_data_ingestion_core::{IndexerExecutor, WorkerPool}; @@ -119,27 +119,12 @@ async fn main() -> Result<()> { mysten_metrics::init_metrics(®istry); let metrics = DataIngestionMetrics::new(®istry); - let mut bigtable_client = None; - for task in &config.tasks { - if let Task::BigTableKV(kv_config) = &task.task { - bigtable_client = Some( - BigTableClient::new_remote( - kv_config.instance_id.clone(), - false, - Some(Duration::from_secs(kv_config.timeout_secs as u64)), - ) - .await?, - ); - } - } - - let progress_store = IngestionWorkflowsProgressStore::new( + let progress_store = DynamoDBProgressStore::new( &config.progress_store.aws_access_key_id, &config.progress_store.aws_secret_access_key, config.progress_store.aws_region, config.progress_store.table_name, config.is_backfill, - bigtable_client, ) .await; let mut executor = IndexerExecutor::new(progress_store, config.tasks.len(), metrics); diff --git a/crates/sui-data-ingestion/src/progress_store.rs b/crates/sui-data-ingestion/src/progress_store.rs index 550f164e4ddf7..02857becfc626 100644 --- a/crates/sui-data-ingestion/src/progress_store.rs +++ b/crates/sui-data-ingestion/src/progress_store.rs @@ -11,24 +11,21 @@ use aws_sdk_s3::config::{Credentials, Region}; use std::str::FromStr; use std::time::Duration; use sui_data_ingestion_core::ProgressStore; -use sui_kvstore::{BigTableClient, KeyValueStoreWriter}; use sui_types::messages_checkpoint::CheckpointSequenceNumber; -pub struct IngestionWorkflowsProgressStore { +pub struct DynamoDBProgressStore { client: Client, table_name: String, is_backfill: bool, - bigtable_client: Option, } -impl IngestionWorkflowsProgressStore { +impl DynamoDBProgressStore { pub async fn new( aws_access_key_id: &str, aws_secret_access_key: &str, aws_region: String, table_name: String, is_backfill: bool, - bigtable_client: Option, ) -> Self { let credentials = Credentials::new( aws_access_key_id, @@ -53,13 +50,12 @@ impl IngestionWorkflowsProgressStore { client, table_name, is_backfill, - bigtable_client, } } } #[async_trait] -impl ProgressStore for IngestionWorkflowsProgressStore { +impl ProgressStore for DynamoDBProgressStore { async fn load(&mut self, task_name: String) -> Result { let item = self .client @@ -83,11 +79,6 @@ impl ProgressStore for IngestionWorkflowsProgressStore { if self.is_backfill && checkpoint_number % 1000 != 0 { return Ok(()); } - if let Some(ref mut bigtable_client) = self.bigtable_client { - bigtable_client - .save_watermark(&task_name, checkpoint_number) - .await?; - } let backoff = backoff::ExponentialBackoff::default(); backoff::future::retry(backoff, || async { let result = self diff --git a/crates/sui-kvstore/src/bigtable/client.rs b/crates/sui-kvstore/src/bigtable/client.rs index 553a97c6e9e92..5c85447622827 100644 --- a/crates/sui-kvstore/src/bigtable/client.rs +++ b/crates/sui-kvstore/src/bigtable/client.rs @@ -35,11 +35,9 @@ const OBJECTS_TABLE: &str = "objects"; const TRANSACTIONS_TABLE: &str = "transactions"; const CHECKPOINTS_TABLE: &str = "checkpoints"; const CHECKPOINTS_BY_DIGEST_TABLE: &str = "checkpoints_by_digest"; -const WATERMARK_TABLE: &str = "watermark"; const COLUMN_FAMILY_NAME: &str = "sui"; const DEFAULT_COLUMN_QUALIFIER: &str = ""; -const AGGREGATED_WATERMARK_NAME: &str = "bigtable"; const CHECKPOINT_SUMMARY_COLUMN_QUALIFIER: &str = "s"; const CHECKPOINT_SIGNATURES_COLUMN_QUALIFIER: &str = "sg"; const CHECKPOINT_CONTENTS_COLUMN_QUALIFIER: &str = "c"; @@ -133,21 +131,6 @@ impl KeyValueStoreWriter for BigTableClient { ) .await } - - async fn save_watermark( - &mut self, - name: &str, - watermark: CheckpointSequenceNumber, - ) -> Result<()> { - let key = name.as_bytes().to_vec(); - let value = watermark.to_be_bytes().to_vec(); - self.multi_set_with_timestamp( - WATERMARK_TABLE, - [(key, vec![(DEFAULT_COLUMN_QUALIFIER, value)])], - watermark as i64, - ) - .await - } } #[async_trait] @@ -254,7 +237,15 @@ impl KeyValueStoreReader for BigTableClient { } async fn get_latest_checkpoint(&mut self) -> Result { - self.get_watermark(AGGREGATED_WATERMARK_NAME).await + let upper_limit = u64::MAX.to_be_bytes().to_vec(); + match self + .reversed_scan(CHECKPOINTS_TABLE, upper_limit) + .await? + .pop() + { + Some((key_bytes, _)) => Ok(u64::from_be_bytes(key_bytes.as_slice().try_into()?)), + None => Ok(0), + } } async fn get_latest_object(&mut self, object_id: &ObjectID) -> Result> { @@ -266,17 +257,6 @@ impl KeyValueStoreReader for BigTableClient { } Ok(None) } - - async fn get_watermark(&mut self, watermark_name: &str) -> Result { - let key = watermark_name.as_bytes().to_vec(); - let mut response = self.multi_get(WATERMARK_TABLE, vec![key]).await?; - if let Some(row) = response.pop() { - if let Some((_, value)) = row.into_iter().next() { - return Ok(u64::from_be_bytes(value.as_slice().try_into()?)); - } - } - Ok(0) - } } impl BigTableClient { @@ -402,15 +382,6 @@ impl BigTableClient { &mut self, table_name: &str, values: impl IntoIterator)> + std::marker::Send, - ) -> Result<()> { - self.multi_set_with_timestamp(table_name, values, -1).await - } - - async fn multi_set_with_timestamp( - &mut self, - table_name: &str, - values: impl IntoIterator)> + std::marker::Send, - timestamp: i64, ) -> Result<()> { let mut entries = vec![]; for (row_key, cells) in values { @@ -422,7 +393,7 @@ impl BigTableClient { column_qualifier: column_name.to_owned().into_bytes(), // The timestamp of the cell into which new data should be written. // Use -1 for current Bigtable server time. - timestamp_micros: timestamp, + timestamp_micros: -1, value, })), }) diff --git a/crates/sui-kvstore/src/bigtable/init.sh b/crates/sui-kvstore/src/bigtable/init.sh index b41ce0ec1fe09..f96ac5c1e9827 100755 --- a/crates/sui-kvstore/src/bigtable/init.sh +++ b/crates/sui-kvstore/src/bigtable/init.sh @@ -10,7 +10,7 @@ if [[ -n $BIGTABLE_EMULATOR_HOST ]]; then command+=(-project emulator) fi -for table in objects transactions checkpoints checkpoints_by_digest watermark; do +for table in objects transactions checkpoints checkpoints_by_digest; do ( set -x "${command[@]}" createtable $table diff --git a/crates/sui-kvstore/src/lib.rs b/crates/sui-kvstore/src/lib.rs index d8fa9117de8c1..5d3ab55a3f64a 100644 --- a/crates/sui-kvstore/src/lib.rs +++ b/crates/sui-kvstore/src/lib.rs @@ -34,7 +34,6 @@ pub trait KeyValueStoreReader { ) -> Result>; async fn get_latest_checkpoint(&mut self) -> Result; async fn get_latest_object(&mut self, object_id: &ObjectID) -> Result>; - async fn get_watermark(&mut self, task_name: &str) -> Result; } #[async_trait] @@ -42,11 +41,6 @@ pub trait KeyValueStoreWriter { async fn save_objects(&mut self, objects: &[&Object]) -> Result<()>; async fn save_transactions(&mut self, transactions: &[TransactionData]) -> Result<()>; async fn save_checkpoint(&mut self, checkpoint: &CheckpointData) -> Result<()>; - async fn save_watermark( - &mut self, - name: &str, - watermark: CheckpointSequenceNumber, - ) -> Result<()>; } #[derive(Clone, Debug)]