diff --git a/Cargo.lock b/Cargo.lock index 815fe60..94a760b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -471,6 +471,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bincode" version = "1.3.3" @@ -1029,6 +1035,16 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "derivation-path" version = "0.2.0" @@ -1369,6 +1385,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "serde_with 3.9.0", "solana-geyser-plugin-interface", "solana-logger", "solana-metrics", @@ -1493,6 +1510,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.8.1" @@ -1676,6 +1699,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -1686,6 +1710,7 @@ checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520" dependencies = [ "equivalent", "hashbrown 0.14.1", + "serde", ] [[package]] @@ -2030,6 +2055,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.3.3" @@ -2295,6 +2326,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2837,7 +2874,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" dependencies = [ "serde", - "serde_with_macros", + "serde_with_macros 2.3.3", +] + +[[package]] +name = "serde_with" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.2", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros 3.9.0", + "time", ] [[package]] @@ -2852,6 +2907,18 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "serde_with_macros" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.48", +] + [[package]] name = "sha2" version = "0.9.9" @@ -3203,7 +3270,7 @@ dependencies = [ "serde_bytes", "serde_derive", "serde_json", - "serde_with", + "serde_with 2.3.3", "sha2 0.10.8", "sha3 0.10.8", "siphasher", @@ -3659,6 +3726,37 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tiny-bip39" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index d4da6fd..fc77308 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ rand = "0.8" serde = "1.0.160" serde_derive = "1.0.160" serde_json = "1.0.96" +serde_with = "3.9.0" solana-account-decoder = "=1.18.22" solana-geyser-plugin-interface = "=1.18.22" solana-logger = "=1.18.22" diff --git a/server/Cargo.toml b/server/Cargo.toml index 93083ce..b04b180 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,6 +20,7 @@ prost-types = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } +serde_with = { workspace = true } solana-geyser-plugin-interface = { workspace = true } solana-logger = { workspace = true } solana-metrics = { workspace = true } diff --git a/server/src/geyser_grpc_plugin.rs b/server/src/geyser_grpc_plugin.rs index 07afd55..725a044 100644 --- a/server/src/geyser_grpc_plugin.rs +++ b/server/src/geyser_grpc_plugin.rs @@ -24,6 +24,7 @@ use jito_geyser_protos::solana::{ use log::*; use serde_derive::Deserialize; use serde_json; +use serde_with::{serde_as, DefaultOnError}; use solana_geyser_plugin_interface::geyser_plugin_interface::{ GeyserPlugin, GeyserPluginError, ReplicaAccountInfoVersions, ReplicaBlockInfoVersions, ReplicaEntryInfoVersions, ReplicaTransactionInfoVersions, Result as PluginResult, SlotStatus, @@ -67,18 +68,40 @@ impl std::fmt::Debug for GeyserGrpcPlugin { } } +macro_rules! generate_default_fns { + ($($name:ident: $type:ty = $value:expr),* $(,)?) => { + $( + fn $name() -> $type { + $value + } + )* + }; +} + +#[serde_as] #[derive(Clone, Debug, Deserialize)] pub struct PluginConfig { pub geyser_service_config: GeyserServiceConfig, pub bind_address: String, pub account_update_buffer_size: usize, pub slot_update_buffer_size: usize, + #[serde_as(deserialize_as = "DefaultOnError")] + #[serde(default = "default_slot_entry_update_buffer_size")] pub slot_entry_update_buffer_size: usize, pub block_update_buffer_size: usize, pub transaction_update_buffer_size: usize, pub skip_startup_stream: Option, } +impl PluginConfig { + const DEFAULT_SLOT_ENTRY_UPDATE_BUFFER_SIZE: usize = 1000000; +} + +// Can add default values for other fields here +generate_default_fns! { + default_slot_entry_update_buffer_size: usize = PluginConfig::DEFAULT_SLOT_ENTRY_UPDATE_BUFFER_SIZE, +} + impl GeyserPlugin for GeyserGrpcPlugin { fn name(&self) -> &'static str { "geyser-grpc-plugin" @@ -566,3 +589,104 @@ impl Interceptor for AccessTokenChecker { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_plugin_config_deserialization() { + let config_json = r#" + { + "libpath": "/path/to/container-output/libgeyser_grpc_plugin_server.so", + "bind_address": "0.0.0.0:10000", + "account_update_buffer_size": 100000, + "slot_update_buffer_size": 100000, + "slot_entry_update_buffer_size": 1000000, + "block_update_buffer_size": 100000, + "transaction_update_buffer_size": 100000, + "geyser_service_config": { + "heartbeat_interval_ms": 1000, + "subscriber_buffer_size": 1000000 + } + } + "#; + + let config: PluginConfig = serde_json::from_str(config_json).unwrap(); + + assert_eq!(config.bind_address, "0.0.0.0:10000"); + assert_eq!(config.account_update_buffer_size, 100000); + assert_eq!(config.slot_update_buffer_size, 100000); + assert_eq!(config.slot_entry_update_buffer_size, 1000000); + assert_eq!(config.block_update_buffer_size, 100000); + assert_eq!(config.transaction_update_buffer_size, 100000); + } + + // Please update the test when the default values are added + #[test] + fn test_plugin_config_missing_fields_error() { + let config_json = r#" + { + "bind_address": "0.0.0.0:10000", + "account_update_buffer_size": 100000, + "geyser_service_config": { + "heartbeat_interval_ms": 1000 + } + } + "#; + + let result: Result = serde_json::from_str(config_json); + assert!(result.is_err()); + } + + #[test] + fn test_plugin_config_invalid_types() { + let config_json = r#" + { + "bind_address": "0.0.0.0:10000", + "account_update_buffer_size": "not a number", + "slot_update_buffer_size": 100000, + "block_update_buffer_size": 100000, + "transaction_update_buffer_size": 100000, + "geyser_service_config": { + "heartbeat_interval_ms": 1000, + "subscriber_buffer_size": 1000000 + } + } + "#; + + let result: Result = serde_json::from_str(config_json); + assert!(result.is_err()); + } + + // We currently have default value for slot_entry_update_buffer_size, so this test will always pass + #[test] + fn test_plugin_config_no_slot_entry_update_buffer_size() { + let config_json = r#" + { + "libpath": "/path/to/container-output/libgeyser_grpc_plugin_server.so", + "bind_address": "0.0.0.0:10000", + "account_update_buffer_size": 100000, + "slot_update_buffer_size": 100000, + "block_update_buffer_size": 100000, + "transaction_update_buffer_size": 100000, + "geyser_service_config": { + "heartbeat_interval_ms": 1000, + "subscriber_buffer_size": 1000000 + } + } + "#; + + let config: PluginConfig = serde_json::from_str(config_json).unwrap(); + + assert_eq!(config.bind_address, "0.0.0.0:10000"); + assert_eq!(config.account_update_buffer_size, 100000); + assert_eq!(config.slot_update_buffer_size, 100000); + assert_eq!( + config.slot_entry_update_buffer_size, + PluginConfig::DEFAULT_SLOT_ENTRY_UPDATE_BUFFER_SIZE + ); + assert_eq!(config.block_update_buffer_size, 100000); + assert_eq!(config.transaction_update_buffer_size, 100000); + } +}