diff --git a/Cargo.lock b/Cargo.lock index e77bee0..60a9ba2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2638,7 +2638,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 2.0.63", diff --git a/src/main.rs b/src/main.rs index f926db0..45b2b37 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ use cadence::{BufferedUdpMetricSink, QueuingMetricSink, StatsdClient}; use cadence_macros::set_global_default; use figment::{providers::Env, Figment}; use grpc_geyser::GrpcGeyserImpl; -use jsonrpsee::server::{RpcServiceBuilder, ServerBuilder}; +use jsonrpsee::server::ServerBuilder; use jsonrpsee::server::middleware::http::ProxyGetRequestLayer; use priority_fee::PriorityFeeTracker; use rpc_server::AtlasPriorityFeeEstimator; @@ -19,7 +19,6 @@ mod priority_fee; mod rpc_server; mod slot_cache; mod solana; -mod temp_validator; #[derive(Debug, Deserialize, Clone)] struct EstimatorEnv { @@ -53,7 +52,6 @@ async fn main() { let port = env.port.unwrap_or(4141); let server = ServerBuilder::default() - .set_rpc_middleware(RpcServiceBuilder::new().layer(temp_validator::RpcValidatorLayer::new())) .set_http_middleware( tower::ServiceBuilder::new() // Proxy `GET /health` requests to internal `health` method. diff --git a/src/priority_fee.rs b/src/priority_fee.rs index 0c252b5..8680422 100644 --- a/src/priority_fee.rs +++ b/src/priority_fee.rs @@ -193,7 +193,7 @@ impl GrpcConsumer for PriorityFeeTracker { impl PriorityFeeTracker { pub fn new(slot_cache_length: usize) -> Self { - let (sampling_txn, sampling_rxn) = channel::<(Vec, bool, Option)>(1_000); + let (sampling_txn, sampling_rxn) = channel::<(Vec, bool, Option)>(100); let tracker = Self { priority_fees: Arc::new(DashMap::new()), @@ -502,7 +502,7 @@ impl PriorityFeeTracker { slots_vec.sort(); slots_vec.reverse(); - let lookback = calculate_lookback_size(&lookback_period, self.slot_cache.len()); + let lookback = calculate_lookback_size(&lookback_period, slots_vec.len()); let mut fees = vec![]; let mut micro_lamport_priority_fee_estimates = MicroLamportPriorityFeeEstimates::default(); diff --git a/src/rpc_server.rs b/src/rpc_server.rs index 6a7a83b..98ef272 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -45,7 +45,60 @@ impl fmt::Debug for AtlasPriorityFeeEstimator { #[serde( rename_all(serialize = "camelCase", deserialize = "camelCase"), )] -// TODO: DKH - add deny_unknown_fields +// TODO: DKH - delete after all the users were notified +pub struct GetPriorityFeeEstimateRequestLight { + pub transaction: Option, // estimate fee for a txn + pub account_keys: Option>, // estimate fee for a list of accounts + pub options: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[serde( + rename_all(serialize = "camelCase", deserialize = "camelCase"), +)] +// TODO: DKH - Delete after all the users were notified +pub struct GetPriorityFeeEstimateOptionsLight { + // controls input txn encoding + pub transaction_encoding: Option, + // controls custom priority fee level response + pub priority_level: Option, // Default to MEDIUM + pub include_all_priority_fee_levels: Option, // Include all priority level estimates in the response + #[serde()] + pub lookback_slots: Option, // how many slots to look back on, default 50, min 1, max 300 + pub include_vote: Option, // include vote txns in the estimate + // returns recommended fee, incompatible with custom controls. Currently the recommended fee is the median fee excluding vote txns + pub recommended: Option, // return the recommended fee (median fee excluding vote txns) +} + +impl Into for GetPriorityFeeEstimateRequestLight { + fn into(self) -> GetPriorityFeeEstimateRequest { + let transaction = self.transaction; + let account_keys = self.account_keys; + let options = self.options.map(|o| { + GetPriorityFeeEstimateOptions { + transaction_encoding: o.transaction_encoding, + priority_level: o.priority_level, + include_all_priority_fee_levels: o.include_all_priority_fee_levels, + lookback_slots: o.lookback_slots, + include_vote: o.include_vote, + recommended: o.recommended, + } + }); + + GetPriorityFeeEstimateRequest + { + transaction, + account_keys, + options, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, Default)] +#[serde( + rename_all(serialize = "camelCase", deserialize = "camelCase"), + deny_unknown_fields +)] pub struct GetPriorityFeeEstimateRequest { pub transaction: Option, // estimate fee for a txn pub account_keys: Option>, // estimate fee for a list of accounts @@ -55,8 +108,8 @@ pub struct GetPriorityFeeEstimateRequest { #[derive(Serialize, Deserialize, Clone, Debug, Default)] #[serde( rename_all(serialize = "camelCase", deserialize = "camelCase"), + deny_unknown_fields )] -// TODO: DKH - add deny_unknown_fields pub struct GetPriorityFeeEstimateOptions { // controls input txn encoding pub transaction_encoding: Option, @@ -83,13 +136,22 @@ pub struct GetPriorityFeeEstimateResponse { pub trait AtlasPriorityFeeEstimatorRpc { #[method(name = "health")] fn health(&self) -> String; + + // TODO: DKH - delete after all the users were notified about moving to strict parsing #[method(name = "getPriorityFeeEstimate")] + fn get_priority_fee_estimate_light( + &self, + get_priority_fee_estimate_request: GetPriorityFeeEstimateRequestLight, + ) -> RpcResult; + + // TODO: DKH - rename annotation method name to "getPriorityFeeEstimateStrict" to "getPriorityFeeEstimate" + #[method(name = "getPriorityFeeEstimateStrict")] fn get_priority_fee_estimate( &self, get_priority_fee_estimate_request: GetPriorityFeeEstimateRequest, ) -> RpcResult; - #[method(name = "getTestPriorityFeeEstimate")] + #[method(name = "getPriorityFeeEstimateTest")] fn get_test_priority_fee_estimate( &self, get_priority_fee_estimate_request: GetPriorityFeeEstimateRequest, @@ -249,9 +311,27 @@ impl AtlasPriorityFeeEstimatorRpcServer for AtlasPriorityFeeEstimator { fn health(&self) -> String { "ok".to_string() } + fn get_priority_fee_estimate_light( + &self, + get_priority_fee_estimate_request: GetPriorityFeeEstimateRequestLight, + ) -> RpcResult { + let algo_run_fn = |accounts: Vec, + include_vote: bool, + lookback_period: Option| + -> MicroLamportPriorityFeeEstimates { + self.priority_fee_tracker.get_priority_fee_estimates( + accounts, + include_vote, + lookback_period, + true, + ) + }; + self.execute_priority_fee_estimate_coordinator(get_priority_fee_estimate_request.into(), algo_run_fn) + } + fn get_priority_fee_estimate( &self, - get_priority_fee_estimate_request: GetPriorityFeeEstimateRequest, + get_priority_fee_estimate_request: GetPriorityFeeEstimateRequest ) -> RpcResult { let algo_run_fn = |accounts: Vec, include_vote: bool, @@ -265,6 +345,7 @@ impl AtlasPriorityFeeEstimatorRpcServer for AtlasPriorityFeeEstimator { ) }; self.execute_priority_fee_estimate_coordinator(get_priority_fee_estimate_request, algo_run_fn) + } fn get_test_priority_fee_estimate( @@ -516,8 +597,7 @@ mod tests { assert_eq!(resp.priority_fee_estimate, Some(10500.0)); } - // #[test] - // TODO: DKH - add the test back after we readd the validation + #[test] fn test_parsing_wrong_fields() { for (param, error) in bad_params() { let json_val = format!("{{\"jsonrpc\": \"2.0\",\"id\": \"1\", \"method\": \"getPriorityFeeEstimate\", \"params\": [{param}] }}"); @@ -549,16 +629,16 @@ mod tests { fn bad_params<'a>() -> Vec<(&'a str, &'a str)> { vec![ - (r#"{"transactions": null}"#,"unknown field `transactions`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 15"), - (r#"{"account_keys": null}"#,"unknown field `account_keys`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 15"), - (r#"{"accountkeys": null}"#,"unknown field `accountkeys`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 14"), - (r#"{"accountKeys": [1, 2]}"#, "invalid type: integer `1`, expected a string at line 1 column 18"), - (r#"{"option": null}"#, "unknown field `option`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 9"), - (r#"{"options": {"transaction_encoding":null}}"#, "unknown field `transaction_encoding`, expected one of `transactionEncoding`, `priorityLevel`, `includeAllPriorityFeeLevels`, `lookbackSlots`, `includeVote`, `recommended` at line 1 column 35"), - (r#"{"options": {"priorityLevel":"HIGH"}}"#, "unknown variant `HIGH`, expected one of `Min`, `Low`, `Medium`, `High`, `VeryHigh`, `UnsafeMax`, `Default` at line 1 column 35"), - (r#"{"options": {"includeAllPriorityFeeLevels":"no"}}"#, "invalid type: string \"no\", expected a boolean at line 1 column 47"), - (r#"{"options": {"lookbackSlots":"no"}}"#, "invalid type: string \"no\", expected u32 at line 1 column 33"), - (r#"{"options": {"lookbackSlots":"-1"}}"#, "invalid type: string \"-1\", expected u32 at line 1 column 33"), + (r#"{"transactions": null}"#,"unknown field `transactions`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 16"), + (r#"{"account_keys": null}"#,"unknown field `account_keys`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 16"), + (r#"{"accountkeys": null}"#,"unknown field `accountkeys`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 15"), + (r#"{"accountKeys": [1, 2]}"#, "invalid type: integer `1`, expected a string at line 1 column 19"), + (r#"{"option": null}"#, "unknown field `option`, expected one of `transaction`, `accountKeys`, `options` at line 1 column 10"), + (r#"{"options": {"transaction_encoding":null}}"#, "unknown field `transaction_encoding`, expected one of `transactionEncoding`, `priorityLevel`, `includeAllPriorityFeeLevels`, `lookbackSlots`, `includeVote`, `recommended` at line 1 column 36"), + (r#"{"options": {"priorityLevel":"HIGH"}}"#, "unknown variant `HIGH`, expected one of `Min`, `Low`, `Medium`, `High`, `VeryHigh`, `UnsafeMax`, `Default` at line 1 column 36"), + (r#"{"options": {"includeAllPriorityFeeLevels":"no"}}"#, "invalid type: string \"no\", expected a boolean at line 1 column 48"), + (r#"{"options": {"lookbackSlots":"no"}}"#, "invalid type: string \"no\", expected u32 at line 1 column 34"), + (r#"{"options": {"lookbackSlots":"-1"}}"#, "invalid type: string \"-1\", expected u32 at line 1 column 34"), ] } } diff --git a/src/slot_cache.rs b/src/slot_cache.rs index 2c97d5e..95ad6ea 100644 --- a/src/slot_cache.rs +++ b/src/slot_cache.rs @@ -30,6 +30,11 @@ impl SlotCache { } match self.slot_queue.write() { Ok(mut slot_queue) => { + + if self.slot_set.contains(&slot) { + return None; + } + match slot_queue.add(slot) { Ok(maybe_oldest_slot) => { if let Some(oldest_slot) = maybe_oldest_slot diff --git a/src/temp_validator.rs b/src/temp_validator.rs deleted file mode 100644 index f76a47d..0000000 --- a/src/temp_validator.rs +++ /dev/null @@ -1,80 +0,0 @@ -use cadence_macros::statsd_count; -use crate::priority_fee::PriorityLevel; -use jsonrpsee::core::__reexports::serde_json; -use jsonrpsee::server::middleware::rpc::RpcServiceT; -use jsonrpsee::types::Request; -use serde::{Deserialize, Serialize}; -use solana_transaction_status::UiTransactionEncoding; -use tracing::info; - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -#[serde( - rename_all(serialize = "camelCase", deserialize = "camelCase"), - deny_unknown_fields -)] -struct GetPriorityFeeEstimateOptionsFake { - // controls input txn encoding - pub transaction_encoding: Option, - // controls custom priority fee level response - pub priority_level: Option, // Default to MEDIUM - pub include_all_priority_fee_levels: Option, // Include all priority level estimates in the response - #[serde()] - pub lookback_slots: Option, // how many slots to look back on, default 50, min 1, max 300 - pub include_vote: Option, // include vote txns in the estimate - // returns recommended fee, incompatible with custom controls. Currently the recommended fee is the median fee excluding vote txns - pub recommended: Option, // return the recommended fee (median fee excluding vote txns) -} - -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -#[serde( - rename_all(serialize = "camelCase", deserialize = "camelCase"), - deny_unknown_fields -)] -struct GetPriorityFeeEstimateRequestFake { - transaction: Option, // estimate fee for a txn - account_keys: Option>, // estimate fee for a list of accounts - options: Option, -} - -/// RPC logger layer. -#[derive(Copy, Clone, Debug)] -pub struct RpcValidatorLayer; - -impl RpcValidatorLayer { - /// Create a new logging layer. - pub fn new() -> Self { - Self - } -} - -impl tower::Layer for RpcValidatorLayer { - type Service = RpcValidator; - - fn layer(&self, service: S) -> Self::Service { - RpcValidator { service } - } -} - -/// A middleware that logs each RPC call and response. -#[derive(Debug)] -pub struct RpcValidator { - service: S, -} - -impl<'a, S> RpcServiceT<'a> for RpcValidator -where - S: RpcServiceT<'a> + Send + Sync, -{ - type Future = S::Future; - - fn call(&self, req: Request<'a>) -> Self::Future { - if let Some(params) = &req.params { - if let Err(err_val) = serde_json::from_str::>(params.get()) { - statsd_count!("rpc_payload_parse_failed", 1); - info!("RPC parse error: {}, {}", err_val, params); - } - } - - self.service.call(req) - } -}