diff --git a/akd/Cargo.toml b/akd/Cargo.toml index 0388e737..2d013631 100644 --- a/akd/Cargo.toml +++ b/akd/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akd" -version = "0.3.5" +version = "0.3.6" authors = ["Harjasleen Malvai ", "Kevin Lewi ", "Sean Lawlor "] description = "An implementation of an auditable key directory" license = "MIT OR Apache-2.0" diff --git a/akd/src/history_tree_node.rs b/akd/src/history_tree_node.rs index 9c0a3349..5066ec17 100644 --- a/akd/src/history_tree_node.rs +++ b/akd/src/history_tree_node.rs @@ -55,8 +55,10 @@ pub(crate) type HistoryInsertionNode = (Direction, HistoryChildState); pub struct HistoryTreeNode { /// The binary label for this node pub label: NodeLabel, - /// The epochs this node was updated - pub epochs: Vec, + /// The last epoch this node was updated in + pub last_epoch: u64, + /// The epoch that this node was birthed in + pub birth_epoch: u64, /// The label of this node's parent pub parent: NodeLabel, // The root node is marked its own parent. /// The type of node: leaf root or interior. @@ -111,7 +113,8 @@ impl Clone for HistoryTreeNode { fn clone(&self) -> Self { Self { label: self.label, - epochs: self.epochs.clone(), + last_epoch: self.last_epoch, + birth_epoch: self.birth_epoch, parent: self.parent, node_type: self.node_type, } @@ -119,10 +122,11 @@ impl Clone for HistoryTreeNode { } impl HistoryTreeNode { - fn new(label: NodeLabel, parent: NodeLabel, node_type: NodeType) -> Self { + fn new(label: NodeLabel, parent: NodeLabel, node_type: NodeType, birth_epoch: u64) -> Self { HistoryTreeNode { label, - epochs: vec![], + birth_epoch, + last_epoch: birth_epoch, parent, // Root node is its own parent node_type, } @@ -245,8 +249,7 @@ impl HistoryTreeNode { debug!("BEGIN create new node"); let mut new_node = - HistoryTreeNode::new(lcs_label, parent.label, NodeType::Interior); - new_node.epochs.push(epoch); + HistoryTreeNode::new(lcs_label, parent.label, NodeType::Interior, epoch); new_node.write_to_storage(storage).await?; set_state_map( storage, @@ -464,11 +467,11 @@ impl HistoryTreeNode { match self.get_latest_epoch() { Ok(latest) => { if latest != epoch { - self.epochs.push(epoch); + self.last_epoch = epoch; } } Err(_) => { - self.epochs.push(epoch); + self.last_epoch = epoch; } } self.write_to_storage(storage).await?; @@ -572,7 +575,7 @@ impl HistoryTreeNode { } pub(crate) fn get_birth_epoch(&self) -> u64 { - self.epochs[0] + self.birth_epoch } // gets the direction of node, i.e. if it's a left @@ -617,14 +620,31 @@ impl HistoryTreeNode { if self.get_birth_epoch() > epoch { Err(HistoryTreeNodeError::NoChildInTreeAtEpoch(epoch, dir)) } else { - let mut chosen_ep = self.get_birth_epoch(); - for existing_ep in &self.epochs { - if *existing_ep <= epoch && *existing_ep > chosen_ep { - chosen_ep = *existing_ep; + let chosen_ep = { + if self.last_epoch <= epoch { + // the "last" updated epoch is <= epoch, so it is + // the last valid state at this epoch + Some(self.last_epoch) + } else if self.birth_epoch == epoch { + // we're looking at the state at the birth epoch + Some(self.birth_epoch) + } else { + // Indeterminate, we are somewhere above the + // birth epoch but we're less than the "last" epoch. + // db query is necessary + None } + }; + + if let Some(ep) = chosen_ep { + self.get_child_at_existing_epoch::<_, H>(storage, ep, direction) + .await + } else { + let target_ep = storage.get_epoch_lte_epoch(self.label, epoch).await?; + // DB query for the state <= this epoch value + self.get_child_at_existing_epoch::<_, H>(storage, target_ep, direction) + .await } - self.get_child_at_existing_epoch::<_, H>(storage, chosen_ep, direction) - .await } } } @@ -652,13 +672,28 @@ impl HistoryTreeNode { if self.get_birth_epoch() > epoch { Err(HistoryTreeNodeError::NodeDidNotExistAtEp(self.label, epoch)) } else { - let mut chosen_ep = self.get_birth_epoch(); - for existing_ep in &self.epochs { - if *existing_ep <= epoch { - chosen_ep = *existing_ep; + let chosen_ep = { + if self.last_epoch <= epoch { + // the "last" updated epoch is <= epoch, so it is + // the last valid state at this epoch + Some(self.last_epoch) + } else if self.birth_epoch == epoch { + // we're looking at the state at the birth epoch + Some(self.birth_epoch) + } else { + // Indeterminate, we are somewhere above the + // birth epoch but we're less than the "last" epoch. + // db query is necessary + None } + }; + if let Some(ep) = chosen_ep { + self.get_state_at_existing_epoch(storage, ep).await + } else { + let target_ep = storage.get_epoch_lte_epoch(self.label, epoch).await?; + // DB query for the state <= this epoch value + self.get_state_at_existing_epoch(storage, target_ep).await } - self.get_state_at_existing_epoch(storage, chosen_ep).await } } @@ -675,12 +710,7 @@ impl HistoryTreeNode { /* Functions for compression-related operations */ pub(crate) fn get_latest_epoch(&self) -> Result { - match self.epochs.len() { - 0 => Err(HistoryTreeNodeError::NodeCreatedWithoutEpochs( - self.label.get_val(), - )), - n => Ok(self.epochs[n - 1]), - } + Ok(self.last_epoch) } /////// Helpers ///////// @@ -739,9 +769,10 @@ pub(crate) async fn get_empty_root( storage: &S, ep: Option, ) -> Result { - let mut node = HistoryTreeNode::new(NodeLabel::root(), NodeLabel::root(), NodeType::Root); + let mut node = HistoryTreeNode::new(NodeLabel::root(), NodeLabel::root(), NodeType::Root, 0u64); if let Some(epoch) = ep { - node.epochs.push(epoch); + node.birth_epoch = epoch; + node.last_epoch = epoch; let new_state: HistoryNodeState = HistoryNodeState::new::(NodeStateKey(node.label, epoch)); set_state_map(storage, new_state).await?; @@ -759,7 +790,8 @@ pub(crate) async fn get_leaf_node( ) -> Result { let node = HistoryTreeNode { label, - epochs: vec![birth_epoch], + birth_epoch, + last_epoch: birth_epoch, parent, node_type: NodeType::Leaf, }; @@ -782,7 +814,8 @@ pub(crate) async fn get_leaf_node_without_hashing Result { let node = HistoryTreeNode { label, - epochs: vec![birth_epoch], + birth_epoch, + last_epoch: birth_epoch, parent, node_type: NodeType::Leaf, }; diff --git a/akd/src/storage/memory.rs b/akd/src/storage/memory.rs index cfa0fc6a..19a47085 100644 --- a/akd/src/storage/memory.rs +++ b/akd/src/storage/memory.rs @@ -266,6 +266,43 @@ impl Storage for AsyncInMemoryDatabase { } Ok(map) } + + async fn get_epoch_lte_epoch( + &self, + node_label: crate::node_state::NodeLabel, + epoch_in_question: u64, + ) -> Result { + let ids = (0..=epoch_in_question) + .map(|epoch| crate::node_state::NodeStateKey(node_label, epoch)) + .collect::>(); + let data = self + .batch_get::(ids) + .await?; + let mut epochs = data + .into_iter() + .map(|item| { + if let DbRecord::HistoryNodeState(state) = item { + state.key.1 + } else { + 0u64 + } + }) + .collect::>(); + // reverse sort + epochs.sort_unstable_by(|a, b| b.cmp(a)); + + // move through the epochs from largest to smallest, first one that's <= ```epoch_in_question``` + // and Bob's your uncle + for item in epochs { + if item <= epoch_in_question { + return Ok(item); + } + } + Err(StorageError::GetError(format!( + "Node (val: {}, len: {}) did not exist <= epoch {}", + node_label.val, node_label.len, epoch_in_question + ))) + } } // ===== In-Memory database w/caching (for benchmarking) ==== // @@ -606,4 +643,41 @@ impl Storage for AsyncInMemoryDbWithCache { } Ok(map) } + + async fn get_epoch_lte_epoch( + &self, + node_label: crate::node_state::NodeLabel, + epoch_in_question: u64, + ) -> Result { + let ids = (0..epoch_in_question) + .map(|epoch| crate::node_state::NodeStateKey(node_label, epoch)) + .collect::>(); + let data = self + .batch_get::(ids) + .await?; + let mut epochs = data + .into_iter() + .map(|item| { + if let DbRecord::HistoryNodeState(state) = item { + state.key.1 + } else { + 0u64 + } + }) + .collect::>(); + // reverse sort + epochs.sort_unstable_by(|a, b| b.cmp(a)); + + // move through the epochs from largest to smallest, first one that's <= ```epoch_in_question``` + // and Bob's your uncle + for item in epochs { + if item <= epoch_in_question { + return Ok(item); + } + } + Err(StorageError::GetError(format!( + "Node (val: {}, len: {}) did not exist <= epoch {}", + node_label.val, node_label.len, epoch_in_question + ))) + } } diff --git a/akd/src/storage/mod.rs b/akd/src/storage/mod.rs index ed4c9fff..a76fe432 100644 --- a/akd/src/storage/mod.rs +++ b/akd/src/storage/mod.rs @@ -82,6 +82,14 @@ pub trait Storage: Clone { /// Retrieve a stored record from the data layer async fn get(&self, id: St::Key) -> Result; + /// Retrieve the last epoch <= ```epoch_in_question``` where the node with ```node_key``` + /// was edited + async fn get_epoch_lte_epoch( + &self, + node_label: crate::node_state::NodeLabel, + epoch_in_question: u64, + ) -> Result; + /// Retrieve a batch of records by id async fn batch_get( &self, @@ -139,14 +147,16 @@ pub trait Storage: Clone { fn build_history_tree_node( label_val: u64, label_len: u32, - epochs: Vec, + birth_epoch: u64, + last_epoch: u64, parent_label_val: u64, parent_label_len: u32, node_type: u8, ) -> HistoryTreeNode { HistoryTreeNode { label: NodeLabel::new(label_val, label_len), - epochs, + birth_epoch, + last_epoch, parent: NodeLabel::new(parent_label_val, parent_label_len), node_type: NodeType::from_u8(node_type), } diff --git a/akd/src/storage/tests.rs b/akd/src/storage/tests.rs index c0cca399..cc463de6 100644 --- a/akd/src/storage/tests.rs +++ b/akd/src/storage/tests.rs @@ -77,7 +77,8 @@ async fn test_get_and_set_item(storage: &Ns) { let node = HistoryTreeNode { label: NodeLabel::new(13, 4), - epochs: vec![123u64, 234u64, 345u64], + birth_epoch: 123, + last_epoch: 234, parent: NodeLabel::new(1, 1), node_type: NodeType::Leaf, }; @@ -98,7 +99,8 @@ async fn test_get_and_set_item(storage: &Ns) { assert_eq!(got_node.label, node.label); assert_eq!(got_node.parent, node.parent); assert_eq!(got_node.node_type, node.node_type); - assert_eq!(got_node.epochs, node.epochs); + assert_eq!(got_node.birth_epoch, node.birth_epoch); + assert_eq!(got_node.last_epoch, node.last_epoch); } else { panic!("Failed to retrieve History Tree Node"); } diff --git a/akd/src/tests.rs b/akd/src/tests.rs index 421e4295..5d79ab09 100644 --- a/akd/src/tests.rs +++ b/akd/src/tests.rs @@ -53,7 +53,10 @@ async fn test_set_child_without_hash_at_root() -> Result<(), HistoryTreeNodeErro root.get_latest_epoch().unwrap_or(0) == 1, "Latest epochs don't match!" ); - assert!(root.epochs.len() == 1, "Ask yourself some pressing questions, such as: Why are there random extra epochs in the root?"); + assert!( + root.birth_epoch == root.last_epoch, + "How would the last epoch be different from the birth epoch without an update?" + ); Ok(()) } @@ -103,7 +106,10 @@ async fn test_set_children_without_hash_at_root() -> Result<(), HistoryTreeNodeE } let latest_ep = root.get_latest_epoch(); assert!(latest_ep.unwrap_or(0) == 1, "Latest epochs don't match!"); - assert!(root.epochs.len() == 1, "Ask yourself some pressing questions, such as: Why are there random extra epochs in the root?"); + assert!( + root.birth_epoch == root.last_epoch, + "How would the last epoch be different from the birth epoch without an update?" + ); Ok(()) } @@ -173,7 +179,10 @@ async fn test_set_children_without_hash_multiple_at_root() -> Result<(), History } let latest_ep = root.get_latest_epoch(); assert!(latest_ep.unwrap_or(0) == 2, "Latest epochs don't match!"); - assert!(root.epochs.len() == 2, "Ask yourself some pressing questions, such as: Why are there random extra epochs in the root?"); + assert!( + root.birth_epoch < root.last_epoch, + "How is the last epoch not higher than the birth epoch after an udpate?" + ); Ok(()) } @@ -242,7 +251,10 @@ async fn test_get_child_at_existing_epoch_multiple_at_root() -> Result<(), Histo } let latest_ep = root.get_latest_epoch(); assert!(latest_ep.unwrap_or(0) == 2, "Latest epochs don't match!"); - assert!(root.epochs.len() == 2, "Ask yourself some pressing questions, such as: Why are there random extra epochs in the root?"); + assert!( + root.birth_epoch < root.last_epoch, + "How is the last epoch not higher than the birth epoch after an udpate?" + ); Ok(()) } @@ -312,7 +324,10 @@ pub async fn test_get_child_at_epoch_at_root() -> Result<(), HistoryTreeNodeErro } let latest_ep = root.get_latest_epoch(); assert!(latest_ep.unwrap_or(0) == 4, "Latest epochs don't match!"); - assert!(root.epochs.len() == 3, "Ask yourself some pressing questions, such as: Why are there random extra epochs in the root?"); + assert!( + root.birth_epoch < root.last_epoch, + "How is the last epoch not higher than the birth epoch after an udpate?" + ); Ok(()) } diff --git a/akd_mysql/Cargo.toml b/akd_mysql/Cargo.toml index 7aa39173..c62c7bd7 100644 --- a/akd_mysql/Cargo.toml +++ b/akd_mysql/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "akd_mysql" -version = "0.3.5" +version = "0.3.6" authors = ["Harjasleen Malvai ", "Kevin Lewi ", "Sean Lawlor "] description = "A MySQL storage layer implementation for an auditable key directory (AKD)" license = "MIT OR Apache-2.0" diff --git a/akd_mysql/src/mysql.rs b/akd_mysql/src/mysql.rs index 09fab42c..0cd3c389 100644 --- a/akd_mysql/src/mysql.rs +++ b/akd_mysql/src/mysql.rs @@ -42,7 +42,7 @@ const SQL_RECONNECTION_DELAY_SECS: u64 = 5; const SELECT_AZKS_DATA: &str = "`epoch`, `num_nodes`"; const SELECT_HISTORY_TREE_NODE_DATA: &str = - "`label_len`, `label_val`, `epochs`, `parent_label_len`, `parent_label_val`, `node_type`"; + "`label_len`, `label_val`, `birth_epoch`, `last_epoch`, `parent_label_len`, `parent_label_val`, `node_type`"; const SELECT_HISTORY_NODE_STATE_DATA: &str = "`label_len`, `label_val`, `epoch`, `value`, `child_states`"; const SELECT_USER_DATA: &str = @@ -338,7 +338,8 @@ impl<'a> AsyncMySqlDatabase { let command = "CREATE TABLE IF NOT EXISTS `".to_owned() + TABLE_HISTORY_TREE_NODES + "` (`label_len` INT UNSIGNED NOT NULL, `label_val` BIGINT UNSIGNED NOT NULL," - + " `epochs` VARBINARY(2000), `parent_label_len` INT UNSIGNED NOT NULL," + + " `birth_epoch` BIGINT UNSIGNED NOT NULL," + + " `last_epoch` BIGINT UNSIGNED NOT NULL, `parent_label_len` INT UNSIGNED NOT NULL," + " `parent_label_val` BIGINT UNSIGNED NOT NULL, `node_type` SMALLINT UNSIGNED NOT NULL," + " PRIMARY KEY (`label_len`, `label_val`))"; tx.query_drop(command).await?; @@ -1434,6 +1435,52 @@ impl Storage for AsyncMySqlDatabase { Err(code) => Err(StorageError::GetError(code.to_string())), } } + + async fn get_epoch_lte_epoch( + &self, + node_label: akd::node_state::NodeLabel, + epoch_in_question: u64, + ) -> core::result::Result { + *(self.num_reads.write().await) += 1; + + let result = async { + let tic = Instant::now(); + + let mut conn = self.get_connection().await?; + + let statement = format!("SELECT `epoch` FROM {} WHERE `label_len` = :len AND `label_val` = :val AND `epoch` <= :epoch ORDER BY `epoch` DESC LIMIT 1", TABLE_HISTORY_NODE_STATES); + let out = conn + .exec_first( + statement, + params! { + "len" => node_label.len, + "val" => node_label.val, + "epoch" => epoch_in_question, + }, + ) + .await; + + let toc = Instant::now() - tic; + *(self.time_read.write().await) += toc; + + let result = self.check_for_infra_error(out)?; + + match result { + Some(r) => Ok::<_, MySqlError>(r), + None => Ok::<_, MySqlError>(u64::MAX), + } + }; + + debug!("END MySQL get epoch LTE epoch"); + match result.await { + Ok(u64::MAX) => Err(StorageError::GetError(format!( + "Node (val: {}, len: {}) did not exist <= epoch {}", + node_label.val, node_label.len, epoch_in_question + ))), + Ok(ep) => Ok(ep), + Err(code) => Err(StorageError::GetError(code.to_string())), + } + } } /* Generic data structure handling for MySQL */ @@ -1465,9 +1512,6 @@ trait MySqlStorable { fn from_row(row: &mut mysql_async::Row) -> core::result::Result where Self: std::marker::Sized; - - fn serialize_epochs(epochs: &[u64]) -> Vec; - fn deserialize_epochs(bin: &[u8]) -> Option>; } impl MySqlStorable for DbRecord { @@ -1475,7 +1519,7 @@ impl MySqlStorable for DbRecord { match &self { DbRecord::Azks(_) => format!("INSERT INTO `{}` (`key`, {}) VALUES (:key, :epoch, :num_nodes) ON DUPLICATE KEY UPDATE `epoch` = :epoch, `num_nodes` = :num_nodes", TABLE_AZKS, SELECT_AZKS_DATA), DbRecord::HistoryNodeState(_) => format!("INSERT INTO `{}` ({}) VALUES (:label_len, :label_val, :epoch, :value, :child_states) ON DUPLICATE KEY UPDATE `value` = :value, `child_states` = :child_states", TABLE_HISTORY_NODE_STATES, SELECT_HISTORY_NODE_STATE_DATA), - DbRecord::HistoryTreeNode(_) => format!("INSERT INTO `{}` ({}) VALUES (:label_len, :label_val, :epochs, :parent_label_len, :parent_label_val, :node_type) ON DUPLICATE KEY UPDATE `label_len` = :label_len, `label_val` = :label_val, `epochs` = :epochs, `parent_label_len` = :parent_label_len, `parent_label_val` = :parent_label_val, `node_type` = :node_type", TABLE_HISTORY_TREE_NODES, SELECT_HISTORY_TREE_NODE_DATA), + DbRecord::HistoryTreeNode(_) => format!("INSERT INTO `{}` ({}) VALUES (:label_len, :label_val, :birth_epoch, :last_epoch, :parent_label_len, :parent_label_val, :node_type) ON DUPLICATE KEY UPDATE `label_len` = :label_len, `label_val` = :label_val, `birth_epoch` = :birth_epoch, `last_epoch` = :last_epoch, `parent_label_len` = :parent_label_len, `parent_label_val` = :parent_label_val, `node_type` = :node_type", TABLE_HISTORY_TREE_NODES, SELECT_HISTORY_TREE_NODE_DATA), DbRecord::ValueState(_) => format!("INSERT INTO `{}` ({}) VALUES (:username, :epoch, :version, :node_label_val, :node_label_len, :data)", TABLE_USER, SELECT_USER_DATA), } } @@ -1491,8 +1535,7 @@ impl MySqlStorable for DbRecord { params! { "label_len" => id.0.len, "label_val" => id.0.val, "epoch" => id.1, "value" => state.value.clone(), "child_states" => bin_data } } DbRecord::HistoryTreeNode(node) => { - let bin_data = DbRecord::serialize_epochs(&node.epochs); - params! { "label_len" => node.label.len, "label_val" => node.label.val, "epochs" => bin_data, "parent_label_len" => node.parent.len, "parent_label_val" => node.parent.val, "node_type" => node.node_type as u8 } + params! { "label_len" => node.label.len, "label_val" => node.label.val, "birth_epoch" => node.birth_epoch, "last_epoch" => node.last_epoch, "parent_label_len" => node.parent.len, "parent_label_val" => node.parent.val, "node_type" => node.node_type as u8 } } DbRecord::ValueState(state) => { params! { "username" => state.get_id().0, "epoch" => state.epoch, "version" => state.version, "node_label_len" => state.label.len, "node_label_val" => state.label.val, "data" => state.plaintext_val.0.clone() } @@ -1512,8 +1555,8 @@ impl MySqlStorable for DbRecord { } StorageType::HistoryTreeNode => { parts = format!( - "{}(:label_len{}, :label_val{}, :epochs{}, :parent_label_len{}, :parent_label_val{}, :node_type{})", - parts, i, i, i, i, i, i + "{}(:label_len{}, :label_val{}, :birth_epoch{}, :last_epoch{}, :parent_label_len{}, :parent_label_val{}, :node_type{})", + parts, i, i, i, i, i, i, i ); } StorageType::ValueState => { @@ -1535,7 +1578,7 @@ impl MySqlStorable for DbRecord { match St::data_type() { StorageType::Azks => format!("INSERT INTO `{}` (`key`, {}) VALUES (:key, :epoch, :num_nodes) as new ON DUPLICATE KEY UPDATE `epoch` = new.epoch, `num_nodes` = new.num_nodes", TABLE_AZKS, SELECT_AZKS_DATA), StorageType::HistoryNodeState => format!("INSERT INTO `{}` ({}) VALUES {} as new ON DUPLICATE KEY UPDATE `value` = new.value, `child_states` = new.child_states", TABLE_HISTORY_NODE_STATES, SELECT_HISTORY_NODE_STATE_DATA, parts), - StorageType::HistoryTreeNode => format!("INSERT INTO `{}` ({}) VALUES {} as new ON DUPLICATE KEY UPDATE `label_len` = new.label_len, `label_val` = new.label_val, `epochs` = new.epochs, `parent_label_len` = new.parent_label_len, `parent_label_val` = new.parent_label_val, `node_type` = new.node_type", TABLE_HISTORY_TREE_NODES, SELECT_HISTORY_TREE_NODE_DATA, parts), + StorageType::HistoryTreeNode => format!("INSERT INTO `{}` ({}) VALUES {} as new ON DUPLICATE KEY UPDATE `label_len` = new.label_len, `label_val` = new.label_val, `birth_epoch` = new.birth_epoch, `last_epoch` = new.last_epoch, `parent_label_len` = new.parent_label_len, `parent_label_val` = new.parent_label_val, `node_type` = new.node_type", TABLE_HISTORY_TREE_NODES, SELECT_HISTORY_TREE_NODE_DATA, parts), StorageType::ValueState => format!("INSERT INTO `{}` ({}) VALUES {}", TABLE_USER, SELECT_USER_DATA, parts), } } @@ -1564,11 +1607,11 @@ impl MySqlStorable for DbRecord { ] } DbRecord::HistoryTreeNode(node) => { - let bin_data = DbRecord::serialize_epochs(&node.epochs); vec![ (format!("label_len{}", idx), Value::from(node.label.len)), (format!("label_val{}", idx), Value::from(node.label.val)), - (format!("epochs{}", idx), Value::from(bin_data)), + (format!("birth_epoch{}", idx), Value::from(node.birth_epoch)), + (format!("last_epoch{}", idx), Value::from(node.last_epoch)), ( format!("parent_label_len{}", idx), Value::from(node.parent.len), @@ -1722,7 +1765,7 @@ impl MySqlStorable for DbRecord { } StorageType::HistoryTreeNode => { format!( - "SELECT a.`label_len`, a.`label_val`, a.`epochs`, a.`parent_label_len`, a.`parent_label_val`, a.`node_type` FROM `{}` a INNER JOIN {} ids ON ids.`label_len` = a.`label_len` AND ids.`label_val` = a.`label_val`", + "SELECT a.`label_len`, a.`label_val`, a.`birth_epoch`, a.`last_epoch`, a.`parent_label_len`, a.`parent_label_val`, a.`node_type` FROM `{}` a INNER JOIN {} ids ON ids.`label_len` = a.`label_len` AND ids.`label_val` = a.`label_val`", TABLE_HISTORY_TREE_NODES, TEMP_IDS_TABLE ) @@ -1882,11 +1925,12 @@ impl MySqlStorable for DbRecord { } } StorageType::HistoryTreeNode => { - // `label_len`, `label_val`, `epochs`, `parent_label_len`, `parent_label_val`, `node_type` + // `label_len`, `label_val`, `birth_epoch`, `last_epoch`, `parent_label_len`, `parent_label_val`, `node_type` if let ( Some(Ok(label_len)), Some(Ok(label_val)), - Some(Ok(epochs)), + Some(Ok(birth_epoch)), + Some(Ok(last_epoch)), Some(Ok(parent_label_len)), Some(Ok(parent_label_val)), Some(Ok(node_type)), @@ -1897,21 +1941,18 @@ impl MySqlStorable for DbRecord { row.take_opt(3), row.take_opt(4), row.take_opt(5), + row.take_opt(6), ) { - let bin_vec: Vec = epochs; - if let Some(decoded_epochs) = DbRecord::deserialize_epochs(&bin_vec) { - let node = AsyncMySqlDatabase::build_history_tree_node( - label_val, - label_len, - decoded_epochs, - parent_label_val, - parent_label_len, - node_type, - ); - return Ok(DbRecord::HistoryTreeNode(node)); - } else { - return Err(MySqlError::from("Deserialization of epochs failed")); - } + let node = AsyncMySqlDatabase::build_history_tree_node( + label_val, + label_len, + birth_epoch, + last_epoch, + parent_label_val, + parent_label_len, + node_type, + ); + return Ok(DbRecord::HistoryTreeNode(node)); } } StorageType::ValueState => { @@ -1947,30 +1988,6 @@ impl MySqlStorable for DbRecord { let err = MySqlError::Driver(mysql_async::DriverError::FromRow { row: row.clone() }); Err(err) } - - fn serialize_epochs(epochs: &[u64]) -> Vec { - let mut results = vec![]; - for item in epochs { - let bytes = (*item).to_be_bytes(); - results.extend_from_slice(&bytes); - } - results - } - - fn deserialize_epochs(bin: &[u8]) -> Option> { - if bin.len() % 8 == 0 { - // modulo 8 means that we have proper length byte arrays which can be decoded into u64's - let mut results = vec![]; - for chunk in bin.chunks(8) { - let mut a: [u8; 8] = Default::default(); - a.copy_from_slice(chunk); - results.push(u64::from_be_bytes(a)); - } - Some(results) - } else { - None - } - } } trait StorageErrorWrappable {