From bb33ca91749ba1693749d22d74a5b3abe8e93bcc Mon Sep 17 00:00:00 2001 From: James Mallon Date: Tue, 2 Jan 2024 10:57:45 +0100 Subject: [PATCH] feat(block-fetcher-factory): added a factory for generating rpc clients --- src/networking/mod.rs | 139 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 src/networking/mod.rs diff --git a/src/networking/mod.rs b/src/networking/mod.rs new file mode 100644 index 0000000..8d283c3 --- /dev/null +++ b/src/networking/mod.rs @@ -0,0 +1,139 @@ +use crate::model::solana_block::SerializedSolanaBlock; +use log::error; +use solana_client::rpc_client::RpcClient; +use std::thread; +use std::time::Duration; + +/// A factory for creating instances of `BlockFetcher`. +/// +/// This factory can create either a mock or a real Solana block fetcher based on the +/// provided configuration. It abstracts the creation process of block fetchers, +/// allowing for easy switching between mock and actual implementations. +/// +/// # Fields +/// - `use_mock`: A boolean flag indicating whether to use a mock implementation. +/// - `rpc_url`: The URL of the Solana RPC client. Used when `use_mock` is `false`. +pub struct BlockFetcherFactory { + use_mock: bool, + rpc_url: String, +} + +impl BlockFetcherFactory { + /// Constructs a new `BlockFetcherFactory`. + /// + /// This method initializes a new factory with the specified configuration. + /// + /// # Parameters + /// - `use_mock`: A boolean flag indicating whether to use a mock implementation. + /// - `rpc_url`: A string slice representing the RPC URL for the Solana client. + /// + /// # Returns + /// Returns a new instance of `BlockFetcherFactory`. + pub fn new(use_mock: bool, rpc_url: &str) -> Self { + BlockFetcherFactory { + use_mock, + rpc_url: rpc_url.to_string(), + } + } + + /// Creates a block fetcher based on the factory's configuration. + /// + /// This method generates either a mock or a real implementation of `BlockFetcher` + /// depending on the `use_mock` flag. It allows for flexible switching between + /// a testing (mock) environment and a production environment. + /// + /// # Returns + /// Returns a boxed object implementing `BlockFetcher` and `Send`. + /// This could either be a `MockSolanaClient` or a `SolanaClient` based on the configuration. + pub fn create_block_fetcher(&self) -> Box { + if self.use_mock { + Box::new(MockSolanaClient::new()) + } else { + Box::new(SolanaClient::new(&self.rpc_url)) + } + } +} + +pub trait BlockFetcher { + /// Fetches a block from the blockchain by its slot number. + /// + /// Implementations of this method should retrieve the block corresponding + /// to the provided slot number and return it in a serialized form. + /// The method varies in its actual behavior based on the implementing struct. + /// + /// # Parameters + /// - `slot`: The slot number of the block to fetch. + /// + /// # Returns + /// Returns a `Result` containing either a `SerializedSolanaBlock` on success + /// or a `GetBlockError` on failure. + fn get_block(&self, slot: u64) -> Result; +} + +pub struct SolanaClient { + rpc_client: RpcClient, +} + +impl BlockFetcher for SolanaClient { + fn get_block(&self, slot: u64) -> Result { + let block = self.rpc_client.get_block(slot).map_err(|err| { + error!("Could not retrieve block {} due to error: {}", slot, err); + GetBlockError::RpcError + })?; + + let data = serde_json::to_string(&block).map_err(|err| { + error!("An error occurred serializing a Solana block: {}, Error: {}", slot, err); + GetBlockError::SerializationError + })?; + + Ok(SerializedSolanaBlock { + slot_number: block.parent_slot as u64, + data, + }) + } +} + +impl SolanaClient { + fn new(rpc_url: &str) -> Self { + SolanaClient { + rpc_client: RpcClient::new(rpc_url), + } + } +} + +#[derive(Debug, Clone)] +pub enum GetBlockError { + RpcError, + SerializationError, +} + +// mocking +pub struct MockSolanaClient { + error: Option, + get_block_returns: SerializedSolanaBlock, +} + +impl BlockFetcher for MockSolanaClient { + fn get_block(&self, _: u64) -> Result { + // simulate the latency for a network call + thread::sleep(Duration::from_millis(250)); + if self.error.is_some() { + let clone = self.error.clone().unwrap(); + Err(clone) + } else { + Ok(self.get_block_returns.clone()) + } + } +} + +impl MockSolanaClient { + pub fn new() -> Self { + MockSolanaClient { + error: None, + get_block_returns: SerializedSolanaBlock { + slot_number: 123, + data: "{ \"name\": \"hello_world\" }".to_string(), + }, + } + } +}