From 18a4d24dcf9c501d5d869f356ef7c07bf9888cfb Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Sat, 25 Jun 2022 20:59:22 +0200 Subject: [PATCH 1/2] Database: Add endpoints for persisting offers and their status --- src/databased/runtime.rs | 114 ++++++++++++++++++++++++++++++++++++++- src/rpc/request.rs | 62 ++++++++++++++++++++- 2 files changed, 174 insertions(+), 2 deletions(-) diff --git a/src/databased/runtime.rs b/src/databased/runtime.rs index 23b02462e..3104e2c23 100644 --- a/src/databased/runtime.rs +++ b/src/databased/runtime.rs @@ -1,3 +1,6 @@ +use crate::databased::runtime::request::OfferStatus; +use crate::databased::runtime::request::OfferStatusPair; +use crate::databased::runtime::request::OfferStatusSelector; use crate::farcaster_core::consensus::Encodable; use crate::walletd::runtime::{CheckpointWallet, Wallet}; use farcaster_core::negotiation::PublicOffer; @@ -307,6 +310,20 @@ impl Runtime { )?; } + Request::SetOfferStatus(OfferStatusPair { offer, status }) => { + self.database.set_offer_status(&offer, &status)?; + } + + Request::RetrieveOffers(selector) => { + let offer_status_pairs = self.database.get_offers(selector)?; + endpoints.send_to( + ServiceBus::Ctl, + ServiceId::Database, + source, + Request::OfferStatusList(offer_status_pairs.into()), + )?; + } + _ => { error!("Request {} is not supported by the CTL interface", request); } @@ -466,18 +483,71 @@ struct Database(lmdb::Environment); const LMDB_CHECKPOINTS: &str = "checkpoints"; const LMDB_ADDRESSES: &str = "addresses"; +const LMDB_OFFER_HISTORY: &str = "history"; impl Database { fn new(path: PathBuf) -> Result { let env = lmdb::Environment::new() .set_map_size(10485760 * 1024 * 64) - .set_max_dbs(2) + .set_max_dbs(3) .open(&path)?; env.create_db(Some(LMDB_CHECKPOINTS), lmdb::DatabaseFlags::empty())?; env.create_db(Some(LMDB_ADDRESSES), lmdb::DatabaseFlags::empty())?; + env.create_db(Some(LMDB_OFFER_HISTORY), lmdb::DatabaseFlags::empty())?; Ok(Database(env)) } + fn set_offer_status( + &mut self, + offer: &PublicOffer, + status: &OfferStatus, + ) -> Result<(), lmdb::Error> { + let db = self.0.open_db(Some(LMDB_OFFER_HISTORY))?; + let mut tx = self.0.begin_rw_txn()?; + let mut key = vec![]; + let _key_size = offer.strict_encode(&mut key); + if !tx.get(db, &key).is_err() { + tx.del(db, &key.clone(), None)?; + } + let mut val = vec![]; + let _key_size = status.strict_encode(&mut val); + tx.put(db, &key, &val, lmdb::WriteFlags::empty())?; + tx.commit()?; + Ok(()) + } + + fn get_offers( + &mut self, + selector: OfferStatusSelector, + ) -> Result, lmdb::Error> { + let db = self.0.open_db(Some(LMDB_OFFER_HISTORY))?; + let tx = self.0.begin_ro_txn()?; + let mut cursor = tx.open_ro_cursor(db)?; + let res = cursor + .iter() + .filter_map(|(key, val)| { + let status = OfferStatus::strict_decode(std::io::Cursor::new(val.to_vec())).ok()?; + let filtered_status = match status { + OfferStatus::Open if selector == OfferStatusSelector::Open => Some(status), + OfferStatus::InProgress if selector == OfferStatusSelector::InProgress => { + Some(status) + } + OfferStatus::Ended(_) if selector == OfferStatusSelector::Ended => Some(status), + _ if selector == OfferStatusSelector::All => Some(status), + _ => None, + }?; + let offer = + PublicOffer::::strict_decode(std::io::Cursor::new(key.to_vec())) + .ok()?; + Some(OfferStatusPair { + offer, + status: filtered_status, + }) + }) + .collect(); + Ok(res) + } + fn set_address( &mut self, address: &bitcoin::Address, @@ -616,4 +686,46 @@ fn test_lmdb_state() { assert_eq!(sk, val_retrieved); let addrs = database.get_all_addresses().unwrap(); assert!(addrs.contains(&addr)); + + let offer_1 = PublicOffer::::from_str("Offer:Cke4ftrP5A71LQM2fvVdFMNR4gmBqNCsR11111uMFuZTAsNgpdK8DiK11111TB9zym113GTvtvqfD1111114A4TUGURtskxM3BUGLBGAdFDhJQVMQmiPUsL5vSTKhyBKw3Lh11111111111111111111111111111111111111111AfZ113XRBuStRU5H").unwrap(); + let offer_2 = PublicOffer::::from_str("Offer:Cke4ftrP5A71LQM2fvVdFMNR4grq1wi1D11111uMFuZTAsNgpdK8DiK11111TB9zym113GTvtvqfD1111114A4TUGURtskxM3BUGLBGAdFDhJQVMQmiPUsL5vSTKhyBKw3Lh11111111111111111111111111111111111111111AfZ113W5EEpvY61v").unwrap(); + + database + .set_offer_status(&offer_1, &OfferStatus::Open) + .unwrap(); + let offers_retrieved = database.get_offers(OfferStatusSelector::All).unwrap(); + assert_eq!(offer_1, offers_retrieved[0].offer); + + let offers_retrieved = database.get_offers(OfferStatusSelector::Open).unwrap(); + assert_eq!(offer_1, offers_retrieved[0].offer); + + database + .set_offer_status(&offer_1, &OfferStatus::InProgress) + .unwrap(); + let offers_retrieved = database + .get_offers(OfferStatusSelector::InProgress) + .unwrap(); + assert_eq!(offer_1, offers_retrieved[0].offer); + + database + .set_offer_status(&offer_1, &OfferStatus::Ended(request::Outcome::Buy)) + .unwrap(); + let offers_retrieved = database.get_offers(OfferStatusSelector::Ended).unwrap(); + assert_eq!(offer_1, offers_retrieved[0].offer); + + database + .set_offer_status(&offer_2, &OfferStatus::Open) + .unwrap(); + let offers_retrieved = database.get_offers(OfferStatusSelector::All).unwrap(); + let status_1 = OfferStatusPair { + offer: offer_1, + status: OfferStatus::Ended(request::Outcome::Buy), + }; + let status_2 = OfferStatusPair { + offer: offer_2, + status: OfferStatus::Open, + }; + assert!(offers_retrieved.len() == 2); + assert!(offers_retrieved.contains(&status_1)); + assert!(offers_retrieved.contains(&status_2)); } diff --git a/src/rpc/request.rs b/src/rpc/request.rs index 505632ccd..f5b778410 100644 --- a/src/rpc/request.rs +++ b/src/rpc/request.rs @@ -704,9 +704,66 @@ pub enum Request { #[api(type = 1313)] #[display("address_list({0})")] AddressList(List), + + #[api(type = 1314)] + #[display("set_offer_history({0})")] + SetOfferStatus(OfferStatusPair), + + #[api(type = 1315)] + #[display("retrieve_offers")] + RetrieveOffers(OfferStatusSelector), + + #[api(type = 1316)] + #[display("offer_status_list({0})")] + OfferStatusList(List), } -#[derive(Clone, Debug, Display, StrictEncode, StrictDecode)] +#[derive(Clone, Debug, Eq, PartialEq, Display, StrictEncode, StrictDecode)] +#[display("{offer}, {status}")] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate") +)] +#[display(OfferStatusPair::to_yaml_string)] +pub struct OfferStatusPair { + pub offer: PublicOffer, + pub status: OfferStatus, +} + +#[derive(Clone, Debug, Eq, PartialEq, Display, StrictEncode, StrictDecode)] +pub enum OfferStatusSelector { + #[display("Open")] + Open, + #[display("In Progress")] + InProgress, + #[display("Ended")] + Ended, + #[display("All")] + All, +} + +#[derive(Clone, Debug, Eq, PartialEq, Display, StrictEncode, StrictDecode)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate") +)] +pub enum OfferStatus { + #[display("Open")] + Open, + #[display("In Progress")] + InProgress, + #[display("Ended({0})")] + Ended(Outcome), +} + +#[derive(Clone, Debug, Eq, PartialEq, Display, StrictEncode, StrictDecode)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate") +)] pub enum Outcome { #[display("Success(Swapped)")] Buy, @@ -715,6 +772,7 @@ pub enum Outcome { #[display("Failure(Punished)")] Punish, } + #[derive(Clone, Debug, Display, StrictDecode, StrictEncode)] #[display("funding_info")] pub enum FundingInfo { @@ -1113,6 +1171,8 @@ impl ToYamlString for SwapProgress {} impl ToYamlString for ProgressEvent {} #[cfg(feature = "serde")] impl ToYamlString for CheckpointEntry {} +#[cfg(feature = "serde")] +impl ToYamlString for OfferStatusPair {} #[derive(Wrapper, Clone, PartialEq, Eq, Debug, From, StrictEncode, StrictDecode)] #[wrapper(IndexRange)] From 02aee9184fa675c5d9258858e005d41cb8d4377c Mon Sep 17 00:00:00 2001 From: TheCharlatan Date: Fri, 1 Jul 2022 14:38:11 +0200 Subject: [PATCH 2/2] Database: persist offer db name as s/history/offer_history/ --- src/databased/runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/databased/runtime.rs b/src/databased/runtime.rs index 3104e2c23..7ea10ce4f 100644 --- a/src/databased/runtime.rs +++ b/src/databased/runtime.rs @@ -483,7 +483,7 @@ struct Database(lmdb::Environment); const LMDB_CHECKPOINTS: &str = "checkpoints"; const LMDB_ADDRESSES: &str = "addresses"; -const LMDB_OFFER_HISTORY: &str = "history"; +const LMDB_OFFER_HISTORY: &str = "offer_history"; impl Database { fn new(path: PathBuf) -> Result {