// Copyright 2021 The Grin Developers // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Implements "linked list" storage primitive for lmdb index supporting multiple entries. use crate::core::ser::{self, Readable, Reader, Writeable, Writer}; use crate::store::Batch; use crate::types::CommitPos; use crate::util::secp::pedersen::Commitment; use byteorder::{BigEndian, WriteBytesExt}; use enum_primitive::FromPrimitive; use grin_store as store; use std::marker::PhantomData; use store::Error; enum_from_primitive! { #[derive(Copy, Clone, Debug, PartialEq)] enum ListWrapperVariant { Single = 0, Multi = 1, } } impl Writeable for ListWrapperVariant { fn write(&self, writer: &mut W) -> Result<(), ser::Error> { writer.write_u8(*self as u8) } } impl Readable for ListWrapperVariant { fn read(reader: &mut R) -> Result { ListWrapperVariant::from_u8(reader.read_u8()?).ok_or(ser::Error::CorruptedData) } } enum_from_primitive! { #[derive(Copy, Clone, Debug, PartialEq)] enum ListEntryVariant { // Start at 2 here to differentiate from ListWrapperVariant above. Head = 2, Tail = 3, Middle = 4, } } impl Writeable for ListEntryVariant { fn write(&self, writer: &mut W) -> Result<(), ser::Error> { writer.write_u8(*self as u8) } } impl Readable for ListEntryVariant { fn read(reader: &mut R) -> Result { ListEntryVariant::from_u8(reader.read_u8()?).ok_or(ser::Error::CorruptedData) } } /// Index supporting a list of (duplicate) entries per commitment. /// Each entry will be at a unique MMR pos. pub trait ListIndex { /// List type type List: Readable + Writeable; /// List entry type type Entry: ListIndexEntry; /// Construct a key for an individual entry in the list. fn entry_key(&self, commit: Commitment, pos: u64) -> (Option, Vec); /// Returns either a "Single" with embedded "pos" or a "list" with "head" and "tail". /// Key is "commit". /// Note the key for an individual entry in the list is "commit|pos". fn get_list(&self, batch: &Batch<'_>, commit: Commitment) -> Result, Error>; /// Returns one of "head", "tail" or "middle" entry variants. /// Key is "commit|pos". fn get_entry( &self, batch: &Batch<'_>, commit: Commitment, pos: u64, ) -> Result, Error> { let (db_key, key) = self.entry_key(commit, pos); batch.db.get_ser(db_key, &key, None) } /// Peek the head of the list for the specified commitment. fn peek_pos( &self, batch: &Batch<'_>, commit: Commitment, ) -> Result::Pos>, Error>; /// Push a pos onto the list for the specified commitment. fn push_pos( &self, batch: &mut Batch<'_>, commit: Commitment, new_pos: ::Pos, ) -> Result<(), Error>; /// Pop a pos off the list for the specified commitment. fn pop_pos( &self, batch: &mut Batch<'_>, commit: Commitment, ) -> Result::Pos>, Error>; } /// Supports "rewind" given the provided commit and a pos to rewind back to. pub trait RewindableListIndex { /// Rewind the index for the given commitment to the specified position. fn rewind( &self, batch: &mut Batch<'_>, commit: Commitment, rewind_pos: u64, ) -> Result<(), Error>; } /// A pruneable list index supports pruning of old data from the index lists. /// This allows us to efficiently maintain an index of "recent" kernel data. /// We can maintain a window of 2 weeks of recent data, discarding anything older than this. pub trait PruneableListIndex: ListIndex { /// Clear all data from the index. /// Used when rebuilding the index. fn clear(&self, batch: &mut Batch<'_>) -> Result<(), Error>; /// Prune old data. fn prune( &self, batch: &mut Batch<'_>, commit: Commitment, cutoff_pos: u64, ) -> Result<(), Error>; /// Pop a pos off the back of the list (used for pruning old data). fn pop_pos_back( &self, batch: &mut Batch<'_>, commit: Commitment, ) -> Result::Pos>, Error>; } /// Wrapper for the list to handle either `Single` or `Multi` entries. /// Optimized for the common case where we have a single entry in the list. #[derive(Copy, Clone, Debug, PartialEq)] pub enum ListWrapper { /// List with a single entry. /// Allows direct access to the pos. Single { /// The MMR pos where this single entry is located. pos: T, }, /// List with multiple entries. /// Maintains head and tail of the underlying linked list. Multi { /// Head of the linked list. head: u64, /// Tail of the linked list. tail: u64, }, } impl Writeable for ListWrapper where T: Writeable, { /// Write first byte representing the variant, followed by variant specific data. /// "Single" is optimized with embedded "pos". /// "Multi" has references to "head" and "tail". fn write(&self, writer: &mut W) -> Result<(), ser::Error> { match self { ListWrapper::Single { pos } => { ListWrapperVariant::Single.write(writer)?; pos.write(writer)?; } ListWrapper::Multi { head, tail } => { ListWrapperVariant::Multi.write(writer)?; writer.write_u64(*head)?; writer.write_u64(*tail)?; } } Ok(()) } } impl Readable for ListWrapper where T: Readable, { /// Read the first byte to determine what needs to be read beyond that. fn read(reader: &mut R) -> Result, ser::Error> { let entry = match ListWrapperVariant::read(reader)? { ListWrapperVariant::Single => ListWrapper::Single { pos: T::read(reader)?, }, ListWrapperVariant::Multi => ListWrapper::Multi { head: reader.read_u64()?, tail: reader.read_u64()?, }, }; Ok(entry) } } /// Index supporting multiple duplicate entries. pub struct MultiIndex { phantom: PhantomData<*const T>, list_prefix: u8, entry_prefix: u8, } impl MultiIndex { /// Initialize a new multi index with the specified list and entry prefixes. pub fn init(list_prefix: u8, entry_prefix: u8) -> MultiIndex { MultiIndex { phantom: PhantomData, list_prefix, entry_prefix, } } } impl ListIndex for MultiIndex where T: PosEntry, { type List = ListWrapper; type Entry = ListEntry; fn entry_key(&self, commit: Commitment, pos: u64) -> (Option, Vec) { let mut key = commit.as_ref().to_vec(); key.write_u64::(pos).unwrap(); (Some(self.entry_prefix), key) } fn get_list(&self, batch: &Batch<'_>, commit: Commitment) -> Result, Error> { let list_key = (Some(self.list_prefix), commit.as_ref()); batch .db .get_ser::>(list_key.0, list_key.1, None) } fn peek_pos(&self, batch: &Batch<'_>, commit: Commitment) -> Result, Error> { match self.get_list(batch, commit)? { None => Ok(None), Some(ListWrapper::Single { pos }) => Ok(Some(pos)), Some(ListWrapper::Multi { head, .. }) => { if let Some(ListEntry::Head { pos, .. }) = self.get_entry(batch, commit, head)? { Ok(Some(pos)) } else { Err(Error::OtherErr("expected head to be head variant".into())) } } } } fn push_pos(&self, batch: &mut Batch<'_>, commit: Commitment, new_pos: T) -> Result<(), Error> { let list_key = (Some(self.list_prefix), commit.as_ref()); match self.get_list(batch, commit)? { None => { let list = ListWrapper::Single { pos: new_pos }; batch.db.put_ser(list_key.0, list_key.1, &list)?; } Some(ListWrapper::Single { pos: current_pos }) => { if new_pos.pos() <= current_pos.pos() { return Err(Error::OtherErr("pos must be increasing".into())); } let head = ListEntry::Head { pos: new_pos, next: current_pos.pos(), }; let tail = ListEntry::Tail { pos: current_pos, prev: new_pos.pos(), }; let list: ListWrapper = ListWrapper::Multi { head: new_pos.pos(), tail: current_pos.pos(), }; let (new_pos_db_key, new_pos_key) = self.entry_key(commit, new_pos.pos()); batch.db.put_ser(new_pos_db_key, &new_pos_key, &head)?; let (cur_pos_db_key, cur_pos_key) = self.entry_key(commit, current_pos.pos()); batch.db.put_ser(cur_pos_db_key, &cur_pos_key, &tail)?; batch.db.put_ser(list_key.0, list_key.1, &list)?; } Some(ListWrapper::Multi { head, tail }) => { if new_pos.pos() <= head { return Err(Error::OtherErr("pos must be increasing".into())); } if let Some(ListEntry::Head { pos: current_pos, next: current_next, }) = self.get_entry(batch, commit, head)? { let head = ListEntry::Head { pos: new_pos, next: current_pos.pos(), }; let middle = ListEntry::Middle { pos: current_pos, next: current_next, prev: new_pos.pos(), }; let list: ListWrapper = ListWrapper::Multi { head: new_pos.pos(), tail, }; let (new_pos_db_key, new_pos_key) = self.entry_key(commit, new_pos.pos()); batch.db.put_ser(new_pos_db_key, &new_pos_key, &head)?; let (cur_pos_db_key, cur_pos_key) = self.entry_key(commit, current_pos.pos()); batch.db.put_ser(cur_pos_db_key, &cur_pos_key, &middle)?; batch.db.put_ser(list_key.0, list_key.1, &list)?; } else { return Err(Error::OtherErr("expected head to be head variant".into())); } } } Ok(()) } /// Pop the head of the list. /// Returns the output_pos. /// Returns None if list was empty. fn pop_pos(&self, batch: &mut Batch<'_>, commit: Commitment) -> Result, Error> { let list_key = (Some(self.list_prefix), commit.as_ref()); match self.get_list(batch, commit)? { None => Ok(None), Some(ListWrapper::Single { pos }) => { batch.delete(list_key.0, list_key.1)?; Ok(Some(pos)) } Some(ListWrapper::Multi { head, tail }) => { if let Some(ListEntry::Head { pos: current_pos, next: current_next, }) = self.get_entry(batch, commit, head)? { match self.get_entry(batch, commit, current_next)? { Some(ListEntry::Middle { pos, next, .. }) => { let head = ListEntry::Head { pos, next }; let list: ListWrapper = ListWrapper::Multi { head: pos.pos(), tail, }; let (cur_pos_db_key, cur_pos_key) = self.entry_key(commit, current_pos.pos()); batch.delete(cur_pos_db_key, &cur_pos_key)?; let (pos_db_key, pos_key) = self.entry_key(commit, pos.pos()); batch.db.put_ser(pos_db_key, &pos_key, &head)?; batch.db.put_ser(list_key.0, list_key.1, &list)?; Ok(Some(current_pos)) } Some(ListEntry::Tail { pos, .. }) => { let list = ListWrapper::Single { pos }; let (cur_pos_db_key, cur_pos_key) = self.entry_key(commit, current_pos.pos()); batch.delete(cur_pos_db_key, &cur_pos_key)?; batch.db.put_ser(list_key.0, list_key.1, &list)?; Ok(Some(current_pos)) } Some(_) => Err(Error::OtherErr("next was unexpected".into())), None => Err(Error::OtherErr("next missing".into())), } } else { Err(Error::OtherErr("expected head to be head variant".into())) } } } } } /// List index that supports rewind. impl RewindableListIndex for MultiIndex { fn rewind( &self, batch: &mut Batch<'_>, commit: Commitment, rewind_pos: u64, ) -> Result<(), Error> { while self .peek_pos(batch, commit)? .map(|x| x.pos() > rewind_pos) .unwrap_or(false) { self.pop_pos(batch, commit)?; } Ok(()) } } impl PruneableListIndex for MultiIndex { fn clear(&self, batch: &mut Batch<'_>) -> Result<(), Error> { let mut lists_to_delete = vec![]; let list_db_key = Some(self.list_prefix); for key in batch.db.iter(list_db_key, |k, _| Ok(k.to_vec()))? { if let Ok(key) = key { lists_to_delete.push(key); } } let mut list_count = 0; for l in lists_to_delete { match batch.delete(list_db_key, &l) { Ok(_) => list_count += 1, Err(_) => {} } } let mut entries_to_delete = vec![]; let entry_db_key = Some(self.entry_prefix); for key in batch.db.iter(entry_db_key, |k, _| Ok(k.to_vec()))? { if let Ok(key) = key { entries_to_delete.push(key); } } let mut entry_count = 0; for e in entries_to_delete { match batch.delete(entry_db_key, &e) { Ok(_) => entry_count += 1, Err(_) => {} } } debug!( "clear: lists deleted: {}, entries deleted: {}", list_count, entry_count ); Ok(()) } /// Pruning will be more performant than full rebuild but not yet necessary. fn prune( &self, _batch: &mut Batch<'_>, _commit: Commitment, _cutoff_pos: u64, ) -> Result<(), Error> { unimplemented!( "we currently rebuild index on startup/compaction, pruning not yet implemented" ); } /// Pop off the back/tail of the linked list. /// Used when pruning old data. fn pop_pos_back(&self, batch: &mut Batch<'_>, commit: Commitment) -> Result, Error> { let list_key = (Some(self.list_prefix), commit.as_ref()); match self.get_list(batch, commit)? { None => Ok(None), Some(ListWrapper::Single { pos }) => { batch.delete(list_key.0, list_key.1)?; Ok(Some(pos)) } Some(ListWrapper::Multi { head, tail }) => { if let Some(ListEntry::Tail { pos: current_pos, prev: current_prev, }) = self.get_entry(batch, commit, tail)? { match self.get_entry(batch, commit, current_prev)? { Some(ListEntry::Middle { pos, prev, .. }) => { let tail = ListEntry::Tail { pos, prev }; let list: ListWrapper = ListWrapper::Multi { head, tail: pos.pos(), }; let (cur_pos_db_key, cur_pos_key) = self.entry_key(commit, current_pos.pos()); batch.delete(cur_pos_db_key, &cur_pos_key)?; let (pos_db_key, pos_key) = self.entry_key(commit, pos.pos()); batch.db.put_ser(pos_db_key, &pos_key, &tail)?; batch.db.put_ser(list_key.0, list_key.1, &list)?; Ok(Some(current_pos)) } Some(ListEntry::Head { pos, .. }) => { let list = ListWrapper::Single { pos }; let (pos_db_key, pos_key) = self.entry_key(commit, current_pos.pos()); batch.delete(pos_db_key, &pos_key)?; batch.db.put_ser(list_key.0, list_key.1, &list)?; Ok(Some(current_pos)) } Some(_) => Err(Error::OtherErr("prev was unexpected".into())), None => Err(Error::OtherErr("prev missing".into())), } } else { Err(Error::OtherErr("expected tail to be tail variant".into())) } } } } } /// Something that tracks pos (in an MMR). pub trait PosEntry: Readable + Writeable + Copy { /// Accessor for the underlying (MMR) pos. fn pos(&self) -> u64; } impl PosEntry for CommitPos { fn pos(&self) -> u64 { self.pos } } /// Entry maintained in the list index. pub trait ListIndexEntry: Readable + Writeable { /// Type of the underlying pos indexed in the list. type Pos: PosEntry; /// Accessor for the underlying pos. fn get_pos(&self) -> Self::Pos; } impl ListIndexEntry for ListEntry where T: PosEntry, { type Pos = T; /// Read the common pos from the various enum variants. fn get_pos(&self) -> Self::Pos { match self { Self::Head { pos, .. } => *pos, Self::Tail { pos, .. } => *pos, Self::Middle { pos, .. } => *pos, } } } /// Head|Middle|Tail variants for the linked list entries. pub enum ListEntry { /// Head of ther list. Head { /// The thing in the list. pos: T, /// The next entry in the list. next: u64, }, /// Tail of the list. Tail { /// The thing in the list. pos: T, /// The previous entry in the list. prev: u64, }, /// An entry in the middle of the list. Middle { /// The thing in the list. pos: T, /// The next entry in the list. next: u64, /// The previous entry in the list. prev: u64, }, } impl Writeable for ListEntry where T: Writeable, { /// Write first byte representing the variant, followed by variant specific data. fn write(&self, writer: &mut W) -> Result<(), ser::Error> { match self { ListEntry::Head { pos, next } => { ListEntryVariant::Head.write(writer)?; pos.write(writer)?; writer.write_u64(*next)?; } ListEntry::Tail { pos, prev } => { ListEntryVariant::Tail.write(writer)?; pos.write(writer)?; writer.write_u64(*prev)?; } ListEntry::Middle { pos, next, prev } => { ListEntryVariant::Middle.write(writer)?; pos.write(writer)?; writer.write_u64(*next)?; writer.write_u64(*prev)?; } } Ok(()) } } impl Readable for ListEntry where T: Readable, { /// Read the first byte to determine what needs to be read beyond that. fn read(reader: &mut R) -> Result, ser::Error> { let entry = match ListEntryVariant::read(reader)? { ListEntryVariant::Head => ListEntry::Head { pos: T::read(reader)?, next: reader.read_u64()?, }, ListEntryVariant::Tail => ListEntry::Tail { pos: T::read(reader)?, prev: reader.read_u64()?, }, ListEntryVariant::Middle => ListEntry::Middle { pos: T::read(reader)?, next: reader.read_u64()?, prev: reader.read_u64()?, }, }; Ok(entry) } }