diff --git a/agent1.keystore b/agent1.keystore new file mode 100644 index 0000000..c3506df --- /dev/null +++ b/agent1.keystore @@ -0,0 +1 @@ +{"passphrase_check":"eyJzYWx0IjpbNzUsODIsMTk5LDE0NCwxODQsMzcsMTUxLDQ3LDEyNiwyMzAsMjQ0LDE3OCwxMTQsMTI4LDE3NSwxMjhdLCJub25jZSI6Wzg2LDQ2LDIwNywyNTIsODEsMTM0LDIyLDE1MywyMjEsNzUsMjAyLDExOSwyNywxNjAsMjQzLDMxLDkyLDczLDIyOCwxMjgsMzYsMTkxLDIxNCwxNl0sImNpcGhlciI6WzEwOCw4NCwxNiw0OCwxNjcsMTUyLDcxLDE4NiwxNDUsMTk3LDEyNywyMDksMTIwLDI0OCwxNTEsMTkxLDYwLDExNywzNCwyMDcsOTUsMTcwLDE4MCwxMCw2OSwxNDYsNTksMTg1LDUsMjEyLDIxLDI0LDI0MywxMTUsMjE1LDE3MiwxNTMsMTUwLDE4Nyw5OSwxMDksMjIzLDExNCwxNzAsMTA5LDE0NywxODgsMTg2LDIxNCwyMTIsMjM0LDIxOCwyMCwyMDcsNjgsMTY1XX0=","secrets":{"primary_keybundle:enc_key":{"blob_type":"EncryptingKey","seed_type":"Mock","hint":"","data":"eyJzYWx0IjpbMjQ5LDIyOCw1MCwyNDcsNDQsMTI4LDkyLDEyNiwxNSw1MSwyMjIsMTA0LDIyNCwzNCwyMzQsMTNdLCJub25jZSI6WzMwLDIwMywxMzYsMTMsNjAsMTEsMTAsNTMsMTI2LDUyLDE5OCwxOTcsNDYsNDksMSwyMzUsMTksMTcsMTUyLDEzLDEzMiwzNCw5MSwxOTRdLCJjaXBoZXIiOlsyMzYsMTMsMTMzLDE2NSw4OCw1LDc3LDU3LDE5NSw5NywxMjUsNCw0OSwxNTgsMzIsMiwxNDAsMjI1LDIyNywxNTQsMTA0LDE3NSwyMCwxNTcsNjEsMzAsMSw3MCwyLDE5MiwxMTIsMzcsMTI2LDE5MCw5NSwzNiwxMjksNTIsMzEsMjQ5LDE2NywyMzYsNDUsNTQsOTYsMzIsMjIwLDIwLDE5OSwyMiwyMDAsMTc0LDI0MCwyMjAsMjI3LDYyLDIxMiwyNTMsNjUsMTkwLDE1MSwxODUsNTQsMTQwLDc3LDc0LDE4MCwyMyw2NywxMjEsMTQ2LDc1LDEzOSwxNTQsMTgyLDE4NSwyNTQsODcsMTQzLDI0OSwxMzMsNSwxODIsODYsMTUsMTc3LDc5LDE3MF19"},"primary_keybundle:sign_key":{"blob_type":"SigningKey","seed_type":"Mock","hint":"","data":"eyJzYWx0IjpbNTEsODMsMTU3LDI0OSw2MSwxMzYsMjUwLDQ5LDE0MiwxNCw2MywxNzgsMjI1LDIxMSwxNTAsNjJdLCJub25jZSI6WzE4MCwxMzQsMjQ0LDIxOCwxMzQsMSwyNTEsMTkyLDIyOSwzNywxNzksMjQzLDEwMiwyMTcsMTQsMTY4LDEyOCwxNTcsMTIsMjEyLDgyLDEzNSwxNjUsMjI2XSwiY2lwaGVyIjpbMTMyLDE4Nyw0NiwyMDgsODksMTI5LDIzNywxNDQsNTEsMTg5LDE3OCw3LDkyLDYxLDE2NSwzNSwxNTEsMjI1LDExMiwxNzcsMjM0LDE4OSwxNjQsMjAxLDEyMywxMTYsMTEsMjEsMTQ0LDIwNSwyMzksMTc0LDIxNSwxNDIsMTE3LDg2LDIxNCwyNyw5NiwyMDAsMTQyLDE5MywyOSwyMzIsMjEzLDY3LDYwLDg3LDEzNiwyNTQsNDAsNjcsMTYwLDIzLDkyLDIyOCw3MywxOTksMTUzLDE4OCwyNDgsMjI5LDkwLDExLDUyLDkyLDE2NCwyMjMsMTkxLDEyNSwxMTMsNzgsMjE5LDYxLDc1LDIyNCwxNTgsNTgsMzgsMzMsMjE5LDI0LDE2NSwxNzIsMTkxLDUsMTk0LDcsMjEzLDMxLDI1NSwxMTEsMTY0LDMzLDMxLDIxMywzMywxNDAsOSwxMTAsMTA4LDYxLDIxOSwxNDIsMTgwLDEwOCwyNTIsNjIsMTU0LDE3NywxMDIsNCwyMDYsMTc2LDIyNCwxMywxMTYsMTU3LDk3LDk5XX0="},"root_seed":{"blob_type":"Seed","seed_type":"OneShot","hint":"","data":"eyJzYWx0IjpbMTE2LDI3LDE1MSw4MywxMzQsNzIsMTYyLDE2NSwxNTQsMjA0LDE1NCwxNTksMTkyLDEzNCwxMzYsMjQyXSwibm9uY2UiOlsyMzIsNDAsMjQyLDIwNCwyMjksMTk5LDMwLDIxMiwxNTUsOTQsMTY1LDYsMTczLDIzNSw5Niw3MCwxODEsNjQsMzYsNiwxMTQsMTI3LDE3NSwxMzhdLCJjaXBoZXIiOlsxNTIsMTI3LDM0LDE4OSw1NCwxNjQsNTAsMjA4LDYzLDkxLDY4LDIzNSwzNiwxODcsNzMsMzksMTQ4LDE2LDEyOSwxMzIsMTkxLDExNiwyMTIsMjExLDE0NywxNzEsMjM2LDE2MCw3NCwxNjQsMTksMjQwLDI1NSwzOSwxOTUsNDcsMjIsMTQwLDc5LDIzNCwyMTUsMTMxLDIxNywyMDQsMTg2LDU1LDI0OSwxNjldfQ=="}}} \ No newline at end of file diff --git a/agent2.keystore b/agent2.keystore new file mode 100644 index 0000000..23ee2e0 --- /dev/null +++ b/agent2.keystore @@ -0,0 +1 @@ +{"passphrase_check":"eyJzYWx0IjpbMjA2LDE2Nyw3OSw1OSw1NiwxNzgsMTQ2LDM0LDI0MywyMjYsMTAzLDI3LDQyLDE1OSwxNjMsMjI3XSwibm9uY2UiOlsxNzAsNzAsMTY1LDE3Niw5MiwxNjEsMTE0LDM2LDEzMSw0Miw3NSwzNSwxNTgsMjUxLDIzNiwxNTYsMjExLDE1OCwxNDQsMTAxLDYsMjM4LDE1MywxMjVdLCJjaXBoZXIiOlsyNTEsMTA4LDczLDEyNCwxODUsNzgsMjA0LDkwLDExMyw3NywyMDksMTc4LDEzLDQ0LDE1OSw0OSwxLDE5NiwxMTAsNTQsMTU1LDExOSw5Nyw1MSwxMDcsMTUwLDE2LDI4LDc4LDE3MiwyMTEsMTM5LDk1LDg3LDEzNywxNDUsNjgsNTUsNjEsNzcsMTcsMTUyLDE1NywyNiwxNTcsMjMzLDI0OCwyMjEsODksMTIxLDkyLDkwLDEzNCwyNiwyMzksNzFdfQ==","secrets":{"primary_keybundle:enc_key":{"blob_type":"EncryptingKey","seed_type":"Mock","hint":"","data":"eyJzYWx0IjpbOTgsMTE1LDIzNSwxMzksMTM5LDIyNywxOTYsMTY4LDIxNSwxOTIsMTg5LDI1MywxMDksMjAsNDgsMTQwXSwibm9uY2UiOlsyMzEsMjMsMzAsMTg4LDI1MCwxMCwyMjgsMTMxLDcsMTgsMjQ0LDEwLDgxLDEyNCw4OCwxMzQsMTM1LDE3OCw0OSwyLDE0MywzNSwyNDAsMTUzXSwiY2lwaGVyIjpbMzYsMjQ1LDI0MCwxNzUsMjA4LDgwLDY4LDE3MiwxNzgsMjEsOTksNjcsMjAwLDI0NCw5NywyMTQsMTA1LDMxLDcsMTIxLDE2NSwyMTksMjQ5LDE5OSwxNTYsNDksNDksMTkzLDE1LDk4LDE4LDIwNiwyMDMsMzEsMTE1LDEyMSwxMzEsMTcxLDM2LDIzMiw0NCwxMjcsMTExLDIwMiwyMDIsMjUzLDIyNiwyMywyMzAsMTc0LDQxLDE0LDE3NSw2NywyNCwyMzAsMjIwLDY0LDIxNiw1MSwxNjMsOTksMjI2LDM2LDI0NywzMSwxODMsMTkxLDIzNywzOCwyMDEsMjIyLDE0MywyMDYsMTQ5LDEwMCwxNjQsMTQ3LDExMiwyMzYsMTI5LDIzLDI1NSwzLDEzMCw5MiwyMzEsOTNdfQ=="},"primary_keybundle:sign_key":{"blob_type":"SigningKey","seed_type":"Mock","hint":"","data":"eyJzYWx0IjpbMTAwLDcsMzgsOTQsMjUsMjI3LDE3Myw3MCwxMzMsOTMsMjE3LDksMjUwLDE4NSwxNjEsMjM2XSwibm9uY2UiOlsxNDcsMTU0LDE4OCwxNSwyNDMsMTAxLDQwLDExMCwxOSwxMDQsMjUzLDMsMjE1LDEyOSw0NiwxMDQsMTcsMTQ4LDIxNiwzOSw3LDYwLDIwNiwyMDRdLCJjaXBoZXIiOlsyNDYsNDMsMTgsOTcsMTI4LDMwLDE3OCw4MCwxMzUsMTQ4LDIzNCwxMiwzMSwxNDIsMTc2LDU5LDk5LDI0NSwyMDUsMTQwLDE1NCwyMTksMjA5LDQ5LDE0OCwxMiwxOTUsOTEsMTEyLDIwOCwxMDAsMTc1LDIyMiw2NywxNzAsMzEsMjA0LDUwLDIxMSw0NSwxNyw4NSw0NSw1NSwyNiwzMCwxMzAsNDQsNCw1NiwxMyw5MywzOSwyMzksMTM5LDIyNywyNTAsNywyNTQsNzMsMTQ5LDE1NiwyMDEsMTA3LDc5LDEyNCwyMzIsMCwxNzMsMjEwLDM0LDEsMTIzLDE0MywyNTIsMzgsMTEzLDEyOSwyMTAsMTU2LDIzNSw2Nyw2LDc0LDQ5LDI1MywxNSwyMzMsMTA2LDE0MiwyMjEsNjksMTU3LDI0Nyw5NiwxNzEsMTk5LDE4LDE3LDI0OCwxMzEsMjMyLDkwLDg5LDY4LDEwMiwxODMsMzksMjEwLDI1MCw0NywyMDcsMjMyLDE4NSwzNiwxOTgsMjMsMyw0LDEwOV19"},"root_seed":{"blob_type":"Seed","seed_type":"OneShot","hint":"","data":"eyJzYWx0IjpbOTAsMywxNTgsMTg4LDE2OCwyMTUsMTEwLDQ5LDEyMiwyMjAsNzYsMjE1LDE4OCwxMDcsMTAyLDc5XSwibm9uY2UiOls1NywxMSwxOTEsMTIsOTYsMTg2LDYxLDI0OSwxMDEsMTM0LDEyNywyNDAsMjQ1LDI1NSwxMDIsMTk1LDE1OSwyNTIsNjEsMzAsNTksMTA5LDE1OCwxMTddLCJjaXBoZXIiOlsyNDAsMjQ2LDE0LDE2NywxMTMsMjA5LDY0LDYsMjI5LDYzLDE4Niw5NiwxOTcsNDIsMTMzLDE1MCw0NSw0MSw0OCwyMjcsMjU1LDE3MywyNDIsNzgsOTgsMTc2LDE1OCwyMCwxMjEsOTYsMTc0LDE5NywyMjUsNTQsMTA4LDI0LDcsNDAsMSwxNiwxMDUsOCwyMywxMDcsMjQsMjI4LDExNSwxODddfQ=="}}} \ No newline at end of file diff --git a/conductor-config.toml b/conductor-config.toml new file mode 100644 index 0000000..1b57345 --- /dev/null +++ b/conductor-config.toml @@ -0,0 +1,51 @@ +[[dnas]] +id = "marketplace-dna" +file = "./dist/holo-barcelona-hackathon.dna.json" +hash = "QmDontCheck" + + + + +[[agents]] +id = "test_agent1" +name = "HoloTester1" +public_address = "HcScjcgKqXC5pmfvka9DmtEJwVr548yd86UPtJGGoue9ynuikuRTN7oE5zcjgbi" +keystore_file = "./agent1.keystore" + +[[agents]] +id = "test_agent2" +name = "HoloTester2" +public_address = "HcScidPSdAT43q9qirJwt5rHJYjjsvougV3jgSBwdJujszw3bBu5Mktr74Rgnea" +keystore_file = "./agent2.keystore" + + + + +[[instances]] +id = "instance1" +dna = "marketplace-dna" +agent = "test_agent1" +[instances.storage] +type = "memory" +path = "tmp-storage" + +[[instances]] +id = "instance2" +dna = "marketplace-dna" +agent = "test_agent2" +[instances.storage] +type = "memory" +path = "tmp-storage" + + + + +[[interfaces]] +id = "http-interface1" +[interfaces.driver] +type = "http" +port = 3000 +[[interfaces.instances]] +id = "instance1" +[[interfaces.instances]] +id = "instance2" diff --git a/zomes/main/code/src/lib.rs b/zomes/main/code/src/lib.rs index 403b479..f470d41 100644 --- a/zomes/main/code/src/lib.rs +++ b/zomes/main/code/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(vec_remove_item)] #![feature(try_from, proc_macro_hygiene)] #[macro_use] extern crate hdk; @@ -9,13 +10,20 @@ extern crate serde_json; #[macro_use] extern crate holochain_json_derive; +mod marketplace; +use marketplace::{TradeState, ActionType}; + +mod matchmaking; +mod trade; +mod trade_action; + +use matchmaking::{TradeProposal}; +use trade::{Trade}; + + use hdk::{ - AGENT_ADDRESS, entry_definition::ValidatingEntryType, error::ZomeApiResult, - holochain_json_api::{ - error::JsonError, json::JsonString, - }, holochain_persistence_api::{ cas::content::{AddressableContent, Address}, }, @@ -27,28 +35,12 @@ use hdk::{ }, }; - use hdk_proc_macros::zome; // see https://developer.holochain.org/api/0.0.18-alpha1/hdk/ for info on using the hdk library // This is a sample zome that defines an entry type "MyEntry" that can be committed to the // agent's chain via the exposed function create_my_entry - -#[derive(Serialize, Deserialize, Debug, DefaultJson,Clone)] -pub struct TradeProposal { - seller: Address, - name_of_item: String, - description: String, -} - -#[derive(Serialize, Deserialize, Debug, DefaultJson,Clone)] -pub struct Trade { - seller: Address, - buyer: Address, - created_at: u32, -} - #[zome] mod main { @@ -87,64 +79,17 @@ mod main { #[zome_fn("hc_public")] fn create_trade_proposal(name_of_item: String, description: String) -> ZomeApiResult
{ - let trade_proposal_data = TradeProposal { - seller: AGENT_ADDRESS.to_string().into(), - name_of_item, - description, - }; - - let entry = Entry::App("trade_proposal".into(), trade_proposal_data.into()); - let trade_proposal_address = hdk::commit_entry(&entry)?; - - let anchor_entry = Entry::App( - "anchor".into(), - // NOTE: check naming - // NOTE: into? - "trade_proposals".into(), - ); - let anchor_address = hdk::commit_entry(&anchor_entry)?; - - hdk::link_entries( - &anchor_address, - &trade_proposal_address, - "has_trade_proposal", - "", - )?; - - Ok(trade_proposal_address) + matchmaking::handle_create_trade_proposal(name_of_item, description) } #[zome_fn("hc_public")] fn accept_trade_proposal(trade_proposal_address: Address, created_at: u32) -> ZomeApiResult
{ - let trade_proposal: TradeProposal = hdk::utils::get_as_type(trade_proposal_address.clone())?; - - let trade_data = Trade { - seller: trade_proposal.seller, - buyer: AGENT_ADDRESS.to_string().into(), - created_at, - }; - - let trade_entry = Entry::App( - "trade".into(), - trade_data.into() - ); - - let trade_address = hdk::commit_entry(&trade_entry)?; - - hdk::link_entries( - &trade_proposal_address, - &trade_address, - "from_trade_proposal", - "" - )?; - - Ok(trade_address) + matchmaking::handle_accept_trade_proposal(trade_proposal_address, created_at) } #[zome_fn("hc_public")] - // NOTE: WTF? pub fn check_responses(trade_proposal_address: Address) -> ZomeApiResult> { - hdk::utils::get_links_and_load_type(&trade_proposal_address, LinkMatch::Exactly("from_trade_proposal".into()), LinkMatch::Any) + matchmaking::handle_check_responses(trade_proposal_address) } #[zome_fn("hc_public")] diff --git a/zomes/main/code/src/marketplace/actions.rs b/zomes/main/code/src/marketplace/actions.rs new file mode 100644 index 0000000..1f21741 --- /dev/null +++ b/zomes/main/code/src/marketplace/actions.rs @@ -0,0 +1,17 @@ +use hdk::holochain_json_api::{ + error::JsonError, json::JsonString, +}; + +#[derive(Clone, Debug, Serialize, Deserialize, DefaultJson, PartialEq)] +pub enum ActionType { + Buy +} + +impl ActionType { + pub fn describe() -> Vec { + vec![ + ActionType::Buy, + ] + } +} + diff --git a/zomes/main/code/src/marketplace/mod.rs b/zomes/main/code/src/marketplace/mod.rs new file mode 100644 index 0000000..83e5b2d --- /dev/null +++ b/zomes/main/code/src/marketplace/mod.rs @@ -0,0 +1,12 @@ +pub mod state; +pub mod validation; +pub mod actions; + +pub use self::{ + state::{ + TradeState, + }, + actions::{ + ActionType, + }, +}; diff --git a/zomes/main/code/src/marketplace/state.rs b/zomes/main/code/src/marketplace/state.rs new file mode 100644 index 0000000..3d0f384 --- /dev/null +++ b/zomes/main/code/src/marketplace/state.rs @@ -0,0 +1,49 @@ +use hdk::holochain_json_api::{ + error::JsonError, json::JsonString, +}; + +use crate::trade_action::Action; +use crate::trade::Trade; +use super::{ + // Actions::Piece, + ActionType, + validation::{get_current_role}, +}; + +#[derive(Clone, Debug, Serialize, Deserialize, DefaultJson)] +pub struct TradeState { + pub actions: Vec, + // pub buyer: Address, + // pub seller: Address, + pub sold: bool, +} + +impl TradeState { + pub fn initial() -> Self { + TradeState { + actions: Vec::new(), + sold: false, + } + } + + pub fn render(&self) -> String { + "".to_string() + } + + pub fn evolve(&self, trade: Trade, next_action: &Action) -> Self { + let _current_role = get_current_role(&trade, &next_action.author).unwrap(); + let mut actions = self.clone().actions.clone(); + actions.push(next_action.to_owned()); + + match &next_action.action_type { + ActionType::Buy => { + TradeState { + actions, + sold: true, + ..self.clone() + } + } + } + + } +} diff --git a/zomes/main/code/src/marketplace/validation.rs b/zomes/main/code/src/marketplace/validation.rs new file mode 100644 index 0000000..9db2604 --- /dev/null +++ b/zomes/main/code/src/marketplace/validation.rs @@ -0,0 +1,54 @@ +use hdk::holochain_persistence_api::{ + cas::content::Address, +}; + +use crate::Trade; +use crate::trade_action::Action; +use super::{ + TradeState, + ActionType, +}; + +pub enum Role { + Buyer, + Seller, +} + +pub fn get_current_role(trade: &Trade, user_address: &Address) -> Result { + // FIXME + match (user_address == &trade.buyer, user_address == &trade.seller) { + (true, true) => return Err("Buyer cannot be seller".into()), + (true, false) => Ok(Role::Buyer), + (false, true) => Ok(Role::Seller), + (false, false) => return Err("User is not participant of the trade!".into()), + } +} + +impl Action { + pub fn is_valid(&self, trade: Trade, trade_state: TradeState) -> Result<(), String> { + hdk::debug(format!("{:?}", trade_state)).unwrap(); + let _current_role = get_current_role(&trade, &self.author)?; + + // move type specific validation + match &self.action_type { + ActionType::Buy => { + // TODO: Check is not bought + is_sold(&trade_state)?; + // from.is_in_bounds()?; + // to.is_in_bounds()?; + // from.is_piece_beloning_to_player(¤t_role, &trade_state)?; + // to.is_empty(&trade_state)?; + // from.can_move_to(to, ¤t_role, &trade_state)?; + hdk::debug("Validation Success!").unwrap(); + Ok(()) + } + } + } +} + +fn is_sold(trade_state: &TradeState) -> Result { + match trade_state.sold { + false => Ok(true), + true => Err("Item is sold".to_string()), + } +} diff --git a/zomes/main/code/src/matchmaking.rs b/zomes/main/code/src/matchmaking.rs new file mode 100644 index 0000000..f1d3fbc --- /dev/null +++ b/zomes/main/code/src/matchmaking.rs @@ -0,0 +1,94 @@ +use hdk::{ + AGENT_ADDRESS, + error::ZomeApiResult, + holochain_persistence_api::{ + cas::content::{Address}, + }, + holochain_json_api::{ + error::JsonError, json::{JsonString, default_to_json}, + }, + holochain_core_types::{ + entry::Entry, + link::LinkMatch, + } +}; + +use serde::Serialize; +use std::fmt::Debug; + +use crate::trade::Trade; + +#[derive(Serialize, Deserialize, Debug, DefaultJson,Clone)] +pub struct TradeProposal { + pub seller: Address, + pub name_of_item: String, + pub description: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetResponse { + pub entry: T, + pub address: Address +} + +impl + Debug + Serialize> From> for JsonString { + fn from(u: GetResponse) -> JsonString { + default_to_json(u) + } +} + +pub fn handle_create_trade_proposal(name_of_item: String, description: String) -> ZomeApiResult
{ + let trade_proposal_data = TradeProposal { + seller: AGENT_ADDRESS.to_string().into(), + name_of_item, + description, + }; + + let entry = Entry::App("trade_proposal".into(), trade_proposal_data.into()); + let trade_proposal_address = hdk::commit_entry(&entry)?; + + let anchor_entry = Entry::App( + "anchor".into(), + "trade_proposals".into(), + ); + let anchor_address = hdk::commit_entry(&anchor_entry)?; + + hdk::link_entries( + &anchor_address, + &trade_proposal_address, + "has_trade_proposal", + "", + )?; + + Ok(trade_proposal_address) +} + +pub fn handle_accept_trade_proposal(trade_proposal_address: Address, created_at: u32) -> ZomeApiResult
{ + let trade_proposal: TradeProposal = hdk::utils::get_as_type(trade_proposal_address.clone())?; + + let trade_data = Trade { + seller: trade_proposal.seller, + buyer: AGENT_ADDRESS.to_string().into(), + created_at, + }; + + let trade_entry = Entry::App( + "trade".into(), + trade_data.into() + ); + + let trade_address = hdk::commit_entry(&trade_entry)?; + + hdk::link_entries( + &trade_proposal_address, + &trade_address, + "from_trade_proposal", + "" + )?; + + Ok(trade_address) +} + +pub fn handle_check_responses(trade_proposal_address: Address) -> ZomeApiResult> { + hdk::utils::get_links_and_load_type(&trade_proposal_address, LinkMatch::Exactly("from_trade_proposal".into()), LinkMatch::Any) +} diff --git a/zomes/main/code/src/trade.rs b/zomes/main/code/src/trade.rs new file mode 100644 index 0000000..36704bd --- /dev/null +++ b/zomes/main/code/src/trade.rs @@ -0,0 +1,108 @@ +use std::convert::TryFrom; +use hdk::{ + error::{ZomeApiResult, ZomeApiError}, + holochain_persistence_api::{ + cas::content::{AddressableContent, Address}, + }, + holochain_json_api::{ + error::JsonError, json::JsonString, + }, + holochain_core_types::{ + entry::Entry, + link::LinkMatch, + } +}; + +use crate::trade_action::Action; +use crate::TradeState; + +#[derive(Serialize, Deserialize, Debug, DefaultJson,Clone)] +pub struct Trade { + pub seller: Address, + pub buyer: Address, + pub created_at: u32, +} + + +/*===================================== += DHT Functions = +=====================================*/ + +/// Traverse the linked list rooted at a trade to find all the actions +pub fn get_actions(trade_address: &Address) -> ZomeApiResult> { + match hdk::get_links(trade_address, LinkMatch::Any, LinkMatch::Any)?.addresses().into_iter().next() { + Some(first_action) => { + let mut action_addresses = vec![first_action]; + let mut more = true; + while more { + more = match hdk::get_links(action_addresses.last().unwrap(), LinkMatch::Any, LinkMatch::Any)?.addresses().into_iter().next() { + Some(address) => { + action_addresses.push(address.clone()); + true + }, + None => { + false + }, + } + } + let actions: Vec = action_addresses.iter().map(|address| { + let action_entry = hdk::get_entry(address).unwrap().unwrap(); + if let Entry::App(_, action_struct) = action_entry { + Action::try_from(action_struct).expect("Entry at address is type other than Action") + } else { + panic!("Not an app entry!") + } + }).collect(); + Ok(actions) + }, + None => { + Ok(Vec::new()) + } + } +} + +pub fn get_state_local_chain(local_chain: Vec, trade_address: &Address) -> ZomeApiResult { + let actions = get_actions_local_chain(local_chain.clone(), trade_address)?; + let trade = get_trade_local_chain(local_chain, trade_address)?; + let new_state = actions.iter().fold(TradeState::initial(), move |state, new_action| state.evolve(trade.clone(), new_action)); + Ok(new_state) +} + +pub fn get_actions_local_chain(local_chain: Vec, trade_address: &Address) -> ZomeApiResult> { + Ok(local_chain + .iter() + .filter_map(|entry| { + if let Entry::App(entry_type, entry_data) = entry { + if entry_type.to_string() == "action" { + Some(Action::try_from(entry_data.clone()).unwrap()) + } else { + None + } + } else { + None + } + }) + .filter(|trade_action| { + trade_action.trade == trade_address.to_owned() + }) + .rev() + .collect()) +} + + +pub fn get_trade_local_chain(local_chain: Vec, trade_address: &Address) -> ZomeApiResult { + local_chain + .iter() + .filter(|entry| { + entry.address() == trade_address.to_owned() + }) + .filter_map(|entry| { + if let Entry::App(_, entry_data) = entry { + Some(Trade::try_from(entry_data.clone()).unwrap()) + } else { + None + } + }) + .next() + .ok_or(ZomeApiError::HashNotFound) +} diff --git a/zomes/main/code/src/trade_action.rs b/zomes/main/code/src/trade_action.rs new file mode 100644 index 0000000..780ecfa --- /dev/null +++ b/zomes/main/code/src/trade_action.rs @@ -0,0 +1,87 @@ +use hdk::{ + entry_definition::ValidatingEntryType, + holochain_persistence_api::{ + cas::content::{Address}, + }, + holochain_json_api::{ + error::JsonError, json::JsonString, + }, + holochain_core_types::{ + dna::entry_types::Sharing, + validation::EntryValidationData, + entry::Entry, + } +}; + +use crate::trade::{get_state_local_chain, get_trade_local_chain}; + +use crate::ActionType; + +#[derive(Clone, Debug, Serialize, Deserialize, DefaultJson, PartialEq)] +pub struct Action { + pub trade: Address, + pub author: Address, + pub action_type: ActionType, + pub previous_action: Address, + pub timestamp: u32, +} + +pub fn definition() -> ValidatingEntryType { + entry!( + name: "action", + description: "An action by an agent in a trade", + sharing: Sharing::Public, + validation_package: || { + hdk::ValidationPackageDefinition::ChainFull + }, + + validation: | validation_data: hdk::EntryValidationData| { + match validation_data { + EntryValidationData::Create{entry, validation_data} => { + let mut local_chain = validation_data.package.source_chain_entries + .ok_or("Could not retrieve source chain")?; + hdk::debug(format!("{:?}", local_chain))?; + + let new_action = Action::from(entry); + + // Sometimes the validating entry is already in the chain when validation runs, + // To make our state reduction work correctly this must be removed + local_chain.remove_item(&Entry::App("action".into() , new_action.clone().into())); + + let state = get_state_local_chain(local_chain.clone(), &new_action.trade) + .map_err(|_| "Could not load state during validation")?; + let trade = get_trade_local_chain(local_chain, &new_action.trade) + .map_err(|_| "Could not load trade during validation")?; + + new_action.is_valid(trade, state) + }, + _ => { + Err("Cannot modify or delete a action".into()) + } + } + }, + + links: [ + from!( + "trade", + link_type: "", + validation_package: || { + hdk::ValidationPackageDefinition::Entry + }, + validation: | _validation_data: hdk::LinkValidationData| { + Ok(()) + } + ), + from!( + "action", + link_type: "", + validation_package: || { + hdk::ValidationPackageDefinition::Entry + }, + validation: | _validation_data: hdk::LinkValidationData| { + Ok(()) + } + ) + ] + ) +}