diff --git a/Cargo.toml b/Cargo.toml index 8e1f46f97..0c5629107 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,4 +14,7 @@ cairo-felt = "0.8.0" serde_with = "3.0.0" starknet-core = "0.8.0" tokio = { version = "1.5.0", features = ["sync", "rt", "macros"] } - +rocksdb = "0.19.0" +sled = "0.34.7" +anyhow = "1.0.71" +tracing = "0.1" diff --git a/src/main.rs b/src/main.rs index d46512a11..d46d0a8b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,9 @@ use rpc::starknet_backend::StarknetBackend; use crate::rpc::StarknetRpcApiServer; mod rpc; +mod store; + +const DB_PATH: &str = "store"; #[tokio::main] async fn main() { @@ -24,7 +27,7 @@ pub async fn start_rpc_server(port: u16) -> Result Felt252 { + return Felt252::from_bytes_be(&field_element.to_bytes_be()); +} + +pub fn to_field_element(felt252: Felt252) -> FieldElement { + return FieldElement::from_bytes_be(&felt252.to_be_bytes()).expect("Could not convert Felt252 to FieldElement"); +} + + impl SerializeAs for FeltHex { fn serialize_as(value: &Felt252, serializer: S) -> Result where diff --git a/src/rpc/starknet_backend.rs b/src/rpc/starknet_backend.rs index c594568c1..a4bdcdb64 100644 --- a/src/rpc/starknet_backend.rs +++ b/src/rpc/starknet_backend.rs @@ -6,6 +6,7 @@ use crate::rpc::{ MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, StateUpdate, SyncStatusType, Transaction, }; +use crate::store::{Store, EngineType}; use cairo_felt::Felt252; use jsonrpsee:: core::{async_trait, RpcResult}; @@ -15,12 +16,16 @@ use super::StarknetRpcApiServer; pub struct StarknetBackend { // mempool_handler: Mempool, - // store: Store, + store: Store, } impl StarknetBackend { - pub fn new() -> StarknetBackend { - StarknetBackend {} + pub fn new(store: &str) -> StarknetBackend { + + let store_path = format!("db_{}", store); + StarknetBackend { + store: Store::new(&store_path, EngineType::Sled).expect("Failed to create sequencer store"), + } } } @@ -59,7 +64,7 @@ impl StarknetRpcApiServer for StarknetBackend { } fn block_number(&self) -> RpcResult { - Ok(1024u64) + Ok(self.store.get_height().expect("Heigh not found")) } fn block_hash_and_number(&self) -> RpcResult { diff --git a/src/store/in_memory.rs b/src/store/in_memory.rs new file mode 100644 index 000000000..8344a32e2 --- /dev/null +++ b/src/store/in_memory.rs @@ -0,0 +1,113 @@ +use super::{Key, StoreEngine, Value}; +use anyhow::Result; +use cairo_felt::Felt252; +use std::{collections::HashMap, fmt::Debug}; +use crate::rpc::{ + InvokeTransaction, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, Transaction, + TransactionReceipt, serializable_types::to_felt252, +}; + +#[derive(Clone, Default)] +pub struct Store { + transactions: HashMap, + blocks_by_hash: HashMap, + blocks_by_height: HashMap, + transaction_receipts: HashMap, + values: HashMap, +} + +impl Store { + pub fn new() -> Result { + Ok(Self { + transactions: HashMap::new(), + blocks_by_hash: HashMap::new(), + blocks_by_height: HashMap::new(), + transaction_receipts: HashMap::new(), + values: HashMap::new(), + }) + } +} + +impl StoreEngine for Store { + fn add_transaction(&mut self, tx: Transaction) -> Result<()> { + match &tx { + Transaction::Invoke(InvokeTransaction::V1(invoke_tx)) => { + let _ = self + .transactions + .insert(to_felt252(invoke_tx.transaction_hash.clone()), tx); + Ok(()) + } + // Currently only InvokeTransactionV1 are supported + _ => todo!(), + } + } + + fn get_transaction(&self, tx_hash: Felt252) -> Result> { + Ok(self.transactions.get(&tx_hash).cloned()) + } + + fn add_block(&mut self, block: MaybePendingBlockWithTxs) -> Result<()> { + match &block { + MaybePendingBlockWithTxs::Block(block_with_txs) => { + let _ = self + .blocks_by_hash + .insert(to_felt252(block_with_txs.block_hash.clone()), block.clone()); + let _ = self + .blocks_by_height + .insert(block_with_txs.block_number, block); + Ok(()) + } + MaybePendingBlockWithTxs::PendingBlock(_) => + // Currently only MaybePendingBlockWithTxs::Block is supported + { + todo!() + } + } + } + + fn get_block_by_hash(&self, block_hash: Felt252) -> Result> { + Ok(self.blocks_by_hash.get(&block_hash).cloned()) + } + + fn get_block_by_height(&self, block_height: u64) -> Result> { + Ok(self.blocks_by_height.get(&block_height).cloned()) + } + + fn set_value(&mut self, key: Key, value: Value) -> Result<()> { + let _ = self.values.insert(key, value); + Ok(()) + } + + fn get_value(&self, key: Key) -> Result>, anyhow::Error> { + Ok(self.values.get(&key).cloned()) + } + + fn add_transaction_receipt( + &mut self, + transaction_receipt: MaybePendingTransactionReceipt, + ) -> Result<()> { + match &transaction_receipt { + MaybePendingTransactionReceipt::Receipt(TransactionReceipt::Invoke(tx_receipt)) => { + let _ = self + .transaction_receipts + .insert(to_felt252(tx_receipt.transaction_hash.clone()), transaction_receipt); + Ok(()) + } + // Currently only InvokeTransactionReceipts are supported + _ => todo!(), + } + } + + fn get_transaction_receipt( + &self, + tx_hash: Felt252, + ) -> Result> { + Ok(self.transaction_receipts.get(&tx_hash).cloned()) + } +} + +impl Debug for Store { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("In Memory Store").finish() + } +} diff --git a/src/store/mod.rs b/src/store/mod.rs new file mode 100644 index 000000000..5a2fa5610 --- /dev/null +++ b/src/store/mod.rs @@ -0,0 +1,296 @@ +use self::in_memory::Store as InMemoryStore; +use self::rocksdb::Store as RocksDBStore; +use self::sled::Store as SledStore; +use anyhow::Result; +use cairo_felt::Felt252; +use std::fmt::Debug; +use std::sync::{Arc, Mutex}; +use crate::rpc::{MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, Transaction}; + +pub mod in_memory; +pub mod rocksdb; +pub mod sled; + +pub(crate) type Key = Vec; +pub(crate) type Value = Vec; + +const BLOCK_HEIGHT: &str = "height"; +pub trait StoreEngine: Debug + Send { + fn add_transaction(&mut self, transaction: Transaction) -> Result<()>; + fn get_transaction(&self, tx_hash: Felt252) -> Result>; + fn add_block(&mut self, block: MaybePendingBlockWithTxs) -> Result<()>; + fn get_block_by_hash(&self, block_hash: Felt252) -> Result>; + fn get_block_by_height(&self, block_height: u64) -> Result>; + fn set_value(&mut self, key: Key, value: Value) -> Result<()>; + fn get_value(&self, key: Key) -> Result>; + fn add_transaction_receipt( + &mut self, + transaction_receipt: MaybePendingTransactionReceipt, + ) -> Result<()>; + fn get_transaction_receipt( + &self, + transaction_id: Felt252, + ) -> Result>; +} + +#[derive(Debug, Clone)] +pub struct Store { + engine: Arc>, +} + +#[allow(dead_code)] +pub enum EngineType { + RocksDB, + Sled, + InMemory, +} + +impl Store { + pub fn new(path: &str, engine_type: EngineType) -> Result { + let mut store = match engine_type { + EngineType::RocksDB => Self { + engine: Arc::new(Mutex::new( + RocksDBStore::new(&format!("{path}.rocksdb")) + .expect("could not create rocksdb store"), + )), + }, + EngineType::Sled => Self { + engine: Arc::new(Mutex::new(SledStore::new(&format!("{path}.sled"))?)), + }, + EngineType::InMemory => Self { + engine: Arc::new(Mutex::new(InMemoryStore::new()?)), + }, + }; + store.init(); + Ok(store) + } + + fn init(&mut self) { + if self.get_height().is_none() { + _ = self.set_height(0); + } + } + + pub fn add_transaction(&mut self, transaction: Transaction) -> Result<()> { + self.engine + .clone() + .lock() + .unwrap() + .add_transaction(transaction) + } + + pub fn get_transaction(&self, tx_hash: Felt252) -> Result> { + self.engine.clone().lock().unwrap().get_transaction(tx_hash) + } + + pub fn add_block(&mut self, block: MaybePendingBlockWithTxs) -> Result<()> { + self.engine.clone().lock().unwrap().add_block(block) + } + + pub fn get_block_by_height( + &self, + block_height: u64, + ) -> Result> { + self.engine + .clone() + .lock() + .unwrap() + .get_block_by_height(block_height) + } + + pub fn get_block_by_hash( + &self, + block_hash: Felt252, + ) -> Result> { + self.engine + .clone() + .lock() + .unwrap() + .get_block_by_hash(block_hash) + } + + pub fn set_height(&mut self, value: u64) -> Result<()> { + self.engine + .clone() + .lock() + .unwrap() + .set_value(BLOCK_HEIGHT.into(), value.to_be_bytes().to_vec()) + } + + pub fn get_height(&self) -> Option { + self.engine + .clone() + .lock() + .unwrap() + .get_value(BLOCK_HEIGHT.into()) + .map_or(None, |result| { + result.map(|value| u64::from_be_bytes(value.as_slice()[..8].try_into().unwrap())) + }) + } + + pub fn add_transaction_receipt( + &mut self, + transaction_receipt: MaybePendingTransactionReceipt, + ) -> Result<()> { + self.engine + .clone() + .lock() + .unwrap() + .add_transaction_receipt(transaction_receipt) + } + + pub fn get_transaction_receipt( + &self, + transaction_id: Felt252, + ) -> Result> { + self.engine + .clone() + .lock() + .unwrap() + .get_transaction_receipt(transaction_id) + } +} + +#[cfg(test)] +mod tests { + use starknet_core::types::FieldElement; + use super::*; + use std::{env, fs}; + use crate::rpc::{InvokeTransaction, InvokeTransactionV1, + serializable_types::{to_felt252, to_field_element}, + }; + + #[test] + fn test_in_memory_store() { + let store = Store::new("test", EngineType::InMemory).unwrap(); + test_store_tx(store.clone()); + test_store_height(store); + } + + #[test] + fn test_sled_store() { + // Removing preexistent DBs in case of a failed previous test + remove_test_dbs("test.sled."); + let store = Store::new("test", EngineType::Sled).unwrap(); + test_store_tx(store.clone()); + test_store_height(store); + remove_test_dbs("test.sled."); + } + + #[test] + fn test_rocksdb_store() { + // Removing preexistent DBs in case of a failed previous test + remove_test_dbs("test.rocksdb."); + let store = Store::new("test", EngineType::RocksDB).unwrap(); + test_store_tx(store.clone()); + test_store_height(store.clone()); + + // FIXME patching rocksdb weird behavior + std::mem::forget(store); + remove_test_dbs("test.rocksdb."); + } + + fn test_store_height(mut store: Store) { + // Test height starts in 0 + assert_eq!(Some(0u64), store.get_height()); + + // Set height to an arbitrary number + store.set_height(25u64).unwrap(); + + // Test value has been persisted + assert_eq!(Some(25u64), store.get_height()); + } + + fn test_store_tx(mut store: Store) { + let tx_hash = Felt252::new(123123); + let tx_fee = Felt252::new(89853483); + let tx_signature = vec![Felt252::new(183728913)]; + let tx_nonce = Felt252::new(5); + let tx_sender_address = Felt252::new(91232018); + let tx_calldata = vec![Felt252::new(10), Felt252::new(0)]; + + let tx = new_transaction( + tx_hash.clone(), + tx_fee.clone(), + tx_signature.clone(), + tx_nonce.clone(), + tx_sender_address.clone(), + tx_calldata.clone(), + ); + let _ = store.add_transaction(tx); + + let stored_tx = store.get_transaction(tx_hash.clone()).unwrap().unwrap(); + let ( + stored_tx_hash, + stored_tx_fee, + stored_tx_signature, + stored_tx_nonce, + stored_tx_sender_address, + stored_tx_calldata, + ) = get_tx_data(stored_tx); + assert_eq!(tx_hash, stored_tx_hash); + assert_eq!(tx_fee, stored_tx_fee); + assert_eq!(tx_signature, stored_tx_signature); + assert_eq!(tx_nonce, stored_tx_nonce); + assert_eq!(tx_sender_address, stored_tx_sender_address); + assert_eq!(tx_calldata, stored_tx_calldata); + } + + fn new_transaction( + tx_hash: Felt252, + tx_fee: Felt252, + tx_signature: Vec, + tx_nonce: Felt252, + tx_sender_address: Felt252, + tx_calldata: Vec, + ) -> Transaction { + let invoke_tx_v1 = InvokeTransactionV1 { + transaction_hash: to_field_element(tx_hash), + max_fee: to_field_element(tx_fee), + signature: tx_signature.iter().map(|elem| to_field_element(elem.clone())).collect::>(), + nonce: FieldElement::from_bytes_be(&tx_nonce.to_be_bytes()).unwrap(), + sender_address: FieldElement::from_bytes_be(&tx_sender_address.to_be_bytes()).unwrap(), + calldata: tx_calldata.iter().map(|elem| to_field_element(elem.clone())).collect::>(), + }; + Transaction::Invoke(InvokeTransaction::V1(invoke_tx_v1)) + } + + fn get_tx_data( + tx: Transaction, + ) -> ( + Felt252, + Felt252, + Vec, + Felt252, + Felt252, + Vec, + ) { + match tx { + Transaction::Invoke(InvokeTransaction::V1(invoke_tx_v1)) => ( + to_felt252(invoke_tx_v1.transaction_hash), + to_felt252(invoke_tx_v1.max_fee), + invoke_tx_v1.signature.iter().map(|elem| to_felt252(*elem)).collect::>(), + to_felt252(invoke_tx_v1.nonce), + to_felt252(invoke_tx_v1.sender_address), + invoke_tx_v1.calldata.iter().map(|elem| to_felt252(*elem)).collect::>(), + ), + _ => todo!(), + } + } + + fn remove_test_dbs(prefix: &str) { + // Removes all test databases from filesystem + for entry in fs::read_dir(env::current_dir().unwrap()).unwrap() { + if entry + .as_ref() + .unwrap() + .file_name() + .to_str() + .unwrap() + .starts_with(prefix) + { + fs::remove_dir_all(entry.unwrap().path()).unwrap(); + } + } + } +} diff --git a/src/store/rocksdb.rs b/src/store/rocksdb.rs new file mode 100644 index 000000000..7ddd28b6f --- /dev/null +++ b/src/store/rocksdb.rs @@ -0,0 +1,266 @@ +use super::{Key, StoreEngine, Value}; +use anyhow::Result; +use cairo_felt::Felt252; +use std::fmt::Debug; +use std::sync::mpsc::{channel, sync_channel, Receiver, Sender, SyncSender}; +use std::thread; +use tracing::log::error; +use crate::rpc::{ + InvokeTransaction, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, Transaction, + TransactionReceipt, +}; + +#[derive(Debug)] +enum StoreCommand { + Put(DbSelector, Key, Value, SyncSender>), + Get(DbSelector, Key, SyncSender>>), +} + +#[derive(Debug)] +enum DbSelector { + Transactions, + BlocksByHash, + BlocksByHeight, + Values, + TransactionReceipts, +} + +#[derive(Clone)] +pub struct Store { + command_sender: Sender, +} + +impl Store { + pub fn new(path: &str) -> Result { + let transactions = rocksdb::DB::open_default(format!("{path}.transactions.db"))?; + let blocks_by_hash = rocksdb::DB::open_default(format!("{path}.blocks1.db"))?; + let blocks_by_height = rocksdb::DB::open_default(format!("{path}.blocks2.db"))?; + let values = rocksdb::DB::open_default(format!("{path}.values.db"))?; + let transaction_receipts = + rocksdb::DB::open_default(format!("{path}.transaction_receipts.db"))?; + let (command_sender, command_receiver): (Sender, Receiver) = + channel(); + thread::spawn(move || { + while let Ok(command) = command_receiver.recv() { + match command { + StoreCommand::Put(db_selector, id, value, reply_to) => { + dbg!("put key:", id.clone()); + let db = match db_selector { + DbSelector::Transactions => &transactions, + DbSelector::BlocksByHash => &blocks_by_hash, + DbSelector::BlocksByHeight => &blocks_by_height, + DbSelector::Values => &values, + DbSelector::TransactionReceipts => &transaction_receipts, + }; + let result = Ok(db + .put(id, value) + .unwrap_or_else(|e| error!("failed to write to db {}", e))); + + reply_to.send(result).unwrap_or_else(|e| error!("{}", e)); + } + StoreCommand::Get(db_selector, id, reply_to) => { + dbg!("get key:", id.clone()); + let db = match db_selector { + DbSelector::Transactions => &transactions, + DbSelector::BlocksByHash => &blocks_by_hash, + DbSelector::BlocksByHeight => &blocks_by_height, + DbSelector::Values => &values, + DbSelector::TransactionReceipts => &transaction_receipts, + }; + let result = db.get(id).unwrap_or(None); + + reply_to + .send(Ok(result)) + .unwrap_or_else(|e| error!("{}", e)); + } + }; + } + }); + Ok(Self { command_sender }) + } +} + +impl StoreEngine for Store { + fn add_transaction(&mut self, tx: Transaction) -> Result<()> { + let (reply_sender, reply_receiver) = sync_channel(0); + let tx_serialized: Vec = serde_json::to_string(&tx).unwrap().as_bytes().to_vec(); + match tx { + Transaction::Invoke(InvokeTransaction::V1(invoke_tx)) => { + self.command_sender.send(StoreCommand::Put( + DbSelector::Transactions, + invoke_tx.transaction_hash.to_bytes_be().to_vec(), + tx_serialized, + reply_sender, + ))?; + reply_receiver.recv()? + } + // Currently only InvokeTransactionV1 are supported + _ => todo!(), + } + } + + fn get_transaction(&self, tx_hash: Felt252) -> Result> { + let (reply_sender, reply_receiver) = sync_channel(0); + + self.command_sender + .send(StoreCommand::Get( + DbSelector::Transactions, + tx_hash.to_be_bytes().to_vec(), + reply_sender, + )) + .unwrap(); + + // TODO: properly handle errors + reply_receiver.recv()??.map_or(Ok(None), |value| { + Ok(Some(serde_json::from_str::( + &String::from_utf8(value.to_vec())?, + )?)) + }) + } + + fn add_block(&mut self, block: MaybePendingBlockWithTxs) -> Result<()> { + let (reply_sender_by_hash, reply_receiver_by_hash) = sync_channel(0); + let (reply_sender_by_height, reply_receiver_by_height) = sync_channel(0); + + let block_serialized: Vec = serde_json::to_string(&block).unwrap().as_bytes().to_vec(); + match block { + MaybePendingBlockWithTxs::Block(block_with_txs) => { + self.command_sender.send(StoreCommand::Put( + DbSelector::BlocksByHash, + block_with_txs.block_hash.to_bytes_be().to_vec(), + block_serialized.clone(), + reply_sender_by_hash, + ))?; + self.command_sender.send(StoreCommand::Put( + DbSelector::BlocksByHeight, + block_with_txs.block_number.to_be_bytes().to_vec(), + block_serialized, + reply_sender_by_height, + ))?; + reply_receiver_by_hash + .recv() + .and(reply_receiver_by_height.recv())? + } + MaybePendingBlockWithTxs::PendingBlock(_) => + // Currently only MaybePendingBlockWithTxs::Block is supported + { + todo!() + } + } + } + + fn get_block_by_hash(&self, block_hash: Felt252) -> Result> { + let (reply_sender, reply_receiver) = sync_channel(0); + + self.command_sender + .send(StoreCommand::Get( + DbSelector::BlocksByHash, + block_hash.to_bytes_be(), + reply_sender, + )) + .unwrap(); + + // TODO: properly handle errors + reply_receiver.recv()??.map_or(Ok(None), |value| { + Ok(Some(serde_json::from_str::( + &String::from_utf8(value.to_vec())?, + )?)) + }) + } + + fn get_block_by_height(&self, block_height: u64) -> Result> { + let (reply_sender, reply_receiver) = sync_channel(0); + + self.command_sender + .send(StoreCommand::Get( + DbSelector::BlocksByHash, + block_height.to_be_bytes().to_vec(), + reply_sender, + )) + .unwrap(); + + // TODO: properly handle errors + reply_receiver.recv()??.map_or(Ok(None), |value| { + Ok(Some(serde_json::from_str::( + &String::from_utf8(value.to_vec())?, + )?)) + }) + } + + fn set_value(&mut self, key: Key, value: Value) -> Result<()> { + let (reply_sender, reply_receiver) = sync_channel(0); + self.command_sender.send(StoreCommand::Put( + DbSelector::Values, + key, + value, + reply_sender, + ))?; + reply_receiver.recv()? + } + + fn get_value(&self, key: Key) -> Result>> { + let (reply_sender, reply_receiver) = sync_channel(0); + + self.command_sender + .send(StoreCommand::Get(DbSelector::Values, key, reply_sender)) + .unwrap(); + + reply_receiver.recv()? + } + + fn add_transaction_receipt( + &mut self, + transaction_receipt: MaybePendingTransactionReceipt, + ) -> Result<()> { + let (reply_sender, reply_receiver) = sync_channel(0); + let tx_receipt_serialized = serde_json::to_string(&transaction_receipt) + .expect("Error serializing tx receipt") + .as_bytes() + .to_vec(); + match transaction_receipt { + MaybePendingTransactionReceipt::Receipt(TransactionReceipt::Invoke(tx_receipt)) => { + self.command_sender.send(StoreCommand::Put( + DbSelector::TransactionReceipts, + tx_receipt.transaction_hash.to_bytes_be().to_vec(), + tx_receipt_serialized, + reply_sender, + ))?; + reply_receiver.recv()? + } + // Currently only InvokeTransactionReceipts are supported + _ => todo!(), + } + } + + fn get_transaction_receipt( + &self, + transaction_id: Felt252, + ) -> Result> { + let (reply_sender, reply_receiver) = sync_channel(0); + + self.command_sender + .send(StoreCommand::Get( + DbSelector::TransactionReceipts, + transaction_id.to_bytes_be(), + reply_sender, + )) + .unwrap(); + + // TODO: properly handle errors + reply_receiver.recv()??.map_or(Ok(None), |value| { + Ok(Some( + serde_json::from_str::(&String::from_utf8( + value.to_vec(), + )?)?, + )) + }) + } +} + +impl Debug for Store { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RocksDB Store").finish() + } +} + +unsafe impl Sync for Store {} diff --git a/src/store/sled.rs b/src/store/sled.rs new file mode 100644 index 000000000..33806ed89 --- /dev/null +++ b/src/store/sled.rs @@ -0,0 +1,147 @@ +use super::{Key, StoreEngine, Value}; +use anyhow::Result; +use cairo_felt::Felt252; +use sled::Db; +use std::fmt::Debug; +use crate::rpc::{ + InvokeTransaction, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, Transaction, + TransactionReceipt, +}; + +#[derive(Clone)] +pub struct Store { + transactions: Db, + blocks_by_hash: Db, + blocks_by_height: Db, + values: Db, + transaction_receipts: Db, +} + +impl Store { + pub fn new(path: &str) -> Result { + Ok(Self { + transactions: sled::open(format!("{path}.transactions.db"))?, + blocks_by_hash: sled::open(format!("{path}.blocks1.db"))?, + blocks_by_height: sled::open(format!("{path}.blocks2.db"))?, + values: sled::open(format!("{path}.values.db"))?, + transaction_receipts: sled::open(format!("{path}.transaction_receipts.db"))?, + }) + } +} + +impl StoreEngine for Store { + fn add_transaction(&mut self, tx: Transaction) -> Result<()> { + let tx_serialized: Vec = serde_json::to_string(&tx)?.as_bytes().to_vec(); + match tx { + Transaction::Invoke(InvokeTransaction::V1(invoke_tx)) => { + let _ = self + .transactions + .insert(invoke_tx.transaction_hash.to_bytes_be(), tx_serialized); + Ok(()) + } + // Currently only InvokeTransactionV1 are supported + _ => todo!(), + } + } + + fn get_transaction(&self, tx_hash: Felt252) -> Result> { + self.transactions + .get(tx_hash.to_be_bytes())? + .map_or(Ok(None), |value| { + Ok(Some(serde_json::from_str::( + &String::from_utf8(value.to_vec())?, + )?)) + }) + } + + fn add_block(&mut self, block: MaybePendingBlockWithTxs) -> Result<()> { + let block_serialized: Vec = serde_json::to_string(&block)?.as_bytes().to_vec(); + match block { + MaybePendingBlockWithTxs::Block(block_with_txs) => { + let _ = self.blocks_by_hash.insert( + block_with_txs.block_hash.to_bytes_be(), + block_serialized.clone(), + ); + let _ = self + .blocks_by_height + .insert(block_with_txs.block_number.to_be_bytes(), block_serialized); + Ok(()) + } + MaybePendingBlockWithTxs::PendingBlock(_) => + // Currently only MaybePendingBlockWithTxs::Block is supported + { + todo!() + } + } + } + + fn get_block_by_hash(&self, block_hash: Felt252) -> Result> { + self.blocks_by_hash + .get(block_hash.to_be_bytes())? + .map_or(Ok(None), |value| { + Ok(Some(serde_json::from_str::( + &String::from_utf8(value.to_vec())?, + )?)) + }) + } + + fn get_block_by_height(&self, block_height: u64) -> Result> { + self.blocks_by_height + .get(block_height.to_be_bytes())? + .map_or(Ok(None), |value| { + Ok(Some(serde_json::from_str::( + &String::from_utf8(value.to_vec())?, + )?)) + }) + } + + fn set_value(&mut self, key: Key, value: Value) -> Result<()> { + let _ = self.values.insert(key, value); + Ok(()) + } + + fn get_value(&self, key: Key) -> Result>> { + Ok(self.values.get(key)?.map(|value| value.to_vec())) + } + + fn add_transaction_receipt( + &mut self, + transaction_receipt: MaybePendingTransactionReceipt, + ) -> Result<()> { + let tx_receipt_serialized = serde_json::to_string(&transaction_receipt)? + .as_bytes() + .to_vec(); + match transaction_receipt { + MaybePendingTransactionReceipt::Receipt(TransactionReceipt::Invoke(tx_receipt)) => { + let _ = self.transaction_receipts.insert( + tx_receipt.transaction_hash.to_bytes_be(), + tx_receipt_serialized, + ); + Ok(()) + } + // Currently only InvokeTransactionReceipts are supported + _ => todo!(), + } + } + + fn get_transaction_receipt( + &self, + tx_hash: Felt252, + ) -> Result> { + self.transaction_receipts + .get(tx_hash.to_bytes_be())? + .map_or(Ok(None), |value| { + Ok(Some( + serde_json::from_str::(&String::from_utf8( + value.to_vec(), + )?)?, + )) + }) + } +} + +impl Debug for Store { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Sled Store").finish() + } +}