Skip to content

Commit

Permalink
2/3 Implement v2::api for the real DB (#292)
Browse files Browse the repository at this point in the history
  • Loading branch information
rkuris authored Sep 28, 2023
1 parent 403f7d8 commit 6f29a00
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 58 deletions.
2 changes: 1 addition & 1 deletion firewood/benches/hashops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ fn bench_db<const N: usize>(criterion: &mut Criterion) {
},
|(db, batch_ops)| {
let proposal = db.new_proposal(batch_ops).unwrap();
proposal.commit().unwrap();
proposal.commit_sync().unwrap();
},
BatchSize::SmallInput,
);
Expand Down
10 changes: 5 additions & 5 deletions firewood/examples/rev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use firewood::{
db::{BatchOp, Db, DbConfig, DbError, Proposal, Revision, WalConfig},
merkle::{Node, TrieHash},
storage::StoreRevShared,
v2::api::Proof,
v2::api::{KeyType, Proof, ValueType},
};
use shale::compact::CompactSpace;

Expand Down Expand Up @@ -118,7 +118,7 @@ impl RevisionTracker {
}
}

fn create_revisions<K, V>(
fn create_revisions<K: KeyType, V: ValueType>(
&mut self,
mut iter: impl Iterator<Item = (K, V)>,
) -> Result<(), DbError>
Expand All @@ -129,7 +129,7 @@ impl RevisionTracker {
iter.try_for_each(|(k, v)| self.create_revision(k, v))
}

fn create_revision<K, V>(&mut self, k: K, v: V) -> Result<(), DbError>
fn create_revision<K: KeyType, V: ValueType>(&mut self, k: K, v: V) -> Result<(), DbError>
where
K: AsRef<[u8]>,
V: AsRef<[u8]>,
Expand All @@ -138,15 +138,15 @@ impl RevisionTracker {
key: k,
value: v.as_ref().to_vec(),
}];
self.db.new_proposal(batch)?.commit()?;
self.db.new_proposal(batch)?.commit_sync()?;

let hash = self.db.kv_root_hash().expect("root-hash should exist");
self.hashes.push_front(hash);
Ok(())
}

fn commit_proposal(&mut self, proposal: Proposal) {
proposal.commit().unwrap();
proposal.commit_sync().unwrap();
let hash = self.db.kv_root_hash().expect("root-hash should exist");
self.hashes.push_front(hash);
}
Expand Down
48 changes: 41 additions & 7 deletions firewood/src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
CachedSpace, MemStoreR, SpaceWrite, StoreConfig, StoreDelta, StoreRevMut, StoreRevShared,
ZeroStore, PAGE_SIZE_NBIT,
},
v2::api::{self, Proof},
v2::api::{self, HashKey, KeyType, Proof, ValueType},
};
use async_trait::async_trait;
use bytemuck::{cast_slice, AnyBitPattern};
Expand Down Expand Up @@ -293,11 +293,14 @@ impl<S: ShaleStore<Node> + Send + Sync> api::DbView for DbRev<S> {
}
}

async fn single_key_proof<K: api::KeyType, N: AsRef<[u8]> + Send>(
async fn single_key_proof<K: api::KeyType>(
&self,
_key: K,
) -> Result<Option<Proof<N>>, api::Error> {
todo!()
key: K,
) -> Result<Option<Proof<Vec<u8>>>, api::Error> {
self.merkle
.prove(key, self.header.kv_root)
.map(Some)
.map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e)))
}

async fn range_proof<K: api::KeyType, V, N>(
Expand Down Expand Up @@ -380,6 +383,34 @@ impl Drop for DbInner {
}
}

#[async_trait]
impl api::Db for Db {
type Historical = DbRev<SharedStore>;

type Proposal = Proposal;

async fn revision(&self, root_hash: HashKey) -> Result<Arc<Self::Historical>, api::Error> {
if let Some(rev) = self.get_revision(&TrieHash(root_hash)) {
Ok(Arc::new(rev.rev))
} else {
Err(api::Error::HashNotFound {
provided: root_hash,
})
}
}

async fn root_hash(&self) -> Result<HashKey, api::Error> {
self.kv_root_hash().map(|hash| hash.0).map_err(Into::into)
}

async fn propose<K: KeyType, V: ValueType>(
&self,
batch: api::Batch<K, V>,
) -> Result<Self::Proposal, api::Error> {
self.new_proposal(batch).map_err(Into::into)
}
}

#[derive(Debug)]
pub struct DbRevInner<T> {
inner: VecDeque<Universe<StoreRevShared>>,
Expand Down Expand Up @@ -722,7 +753,10 @@ impl Db {
}

/// Create a proposal.
pub fn new_proposal<K: AsRef<[u8]>>(&self, data: Batch<K>) -> Result<Proposal, DbError> {
pub fn new_proposal<K: KeyType, V: ValueType>(
&self,
data: Batch<K, V>,
) -> Result<Proposal, DbError> {
let mut inner = self.inner.write();
let reset_store_headers = inner.reset_store_headers;
let (store, mut rev) = Db::new_store(
Expand All @@ -742,7 +776,7 @@ impl Db {
BatchOp::Put { key, value } => {
let (header, merkle) = rev.borrow_split();
merkle
.insert(key, value, header.kv_root)
.insert(key, value.as_ref().to_vec(), header.kv_root)
.map_err(DbError::Merkle)?;
Ok(())
}
Expand Down
82 changes: 66 additions & 16 deletions firewood/src/db/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,14 @@ use super::{
use crate::{
merkle::{TrieHash, TRIE_HASH_LEN},
storage::{buffer::BufferWrite, AshRecord, StoreRevMut},
v2::api::{self, KeyType, ValueType},
};
use async_trait::async_trait;
use parking_lot::{Mutex, RwLock};
use shale::CachedStore;
use std::sync::Arc;

/// A key/value pair operation. Only put (upsert) and delete are
/// supported
#[derive(Debug)]
pub enum BatchOp<K> {
Put { key: K, value: Vec<u8> },
Delete { key: K },
}
use std::{io::ErrorKind, sync::Arc};

/// A list of operations to consist of a batch that
/// can be proposed
pub type Batch<K> = Vec<BatchOp<K>>;
pub use crate::v2::api::{Batch, BatchOp};

/// An atomic batch of changes proposed against the latest committed revision,
/// or any existing [Proposal]. Multiple proposals can be created against the
Expand All @@ -50,10 +42,29 @@ pub enum ProposalBase {
View(Arc<DbRev<SharedStore>>),
}

#[async_trait]
impl<T: crate::v2::api::DbView> crate::v2::api::Proposal<T> for Proposal {
type Proposal = Proposal;

async fn commit(self: Arc<Self>) -> Result<Arc<T>, api::Error> {
todo!()
}

async fn propose<K: api::KeyType, V: api::ValueType>(
self: Arc<Self>,
data: api::Batch<K, V>,
) -> Result<Self::Proposal, api::Error> {
self.propose_sync(data).map_err(Into::into)
}
}

impl Proposal {
// Propose a new proposal from this proposal. The new proposal will be
// the child of it.
pub fn propose<K: AsRef<[u8]>>(self: Arc<Self>, data: Batch<K>) -> Result<Proposal, DbError> {
pub fn propose_sync<K: KeyType, V: ValueType>(
self: Arc<Self>,
data: Batch<K, V>,
) -> Result<Proposal, DbError> {
let store = self.store.new_from_other();

let m = Arc::clone(&self.m);
Expand Down Expand Up @@ -81,7 +92,7 @@ impl Proposal {
BatchOp::Put { key, value } => {
let (header, merkle) = rev.borrow_split();
merkle
.insert(key, value, header.kv_root)
.insert(key, value.as_ref().to_vec(), header.kv_root)
.map_err(DbError::Merkle)?;
Ok(())
}
Expand Down Expand Up @@ -111,14 +122,14 @@ impl Proposal {

/// Persist all changes to the DB. The atomicity of the [Proposal] guarantees all changes are
/// either retained on disk or lost together during a crash.
pub fn commit(&self) -> Result<(), DbError> {
pub fn commit_sync(&self) -> Result<(), DbError> {
let mut committed = self.committed.lock();
if *committed {
return Ok(());
}

if let ProposalBase::Proposal(p) = &self.parent {
p.commit()?;
p.commit_sync()?;
};

// Check for if it can be committed
Expand Down Expand Up @@ -257,6 +268,45 @@ impl Proposal {
}
}

#[async_trait]
impl api::DbView for Proposal {
async fn root_hash(&self) -> Result<api::HashKey, api::Error> {
self.get_revision()
.kv_root_hash()
.map(|hash| hash.0)
.map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e)))
}
async fn val<K>(&self, key: K) -> Result<Option<Vec<u8>>, api::Error>
where
K: api::KeyType,
{
// TODO: pass back errors from kv_get
Ok(self.get_revision().kv_get(key))
}

async fn single_key_proof<K>(&self, key: K) -> Result<Option<api::Proof<Vec<u8>>>, api::Error>
where
K: api::KeyType,
{
self.get_revision()
.prove(key)
.map(Some)
.map_err(|e| api::Error::IO(std::io::Error::new(ErrorKind::Other, e)))
}

async fn range_proof<K, V, N>(
&self,
_first_key: Option<K>,
_last_key: Option<K>,
_limit: usize,
) -> Result<Option<api::RangeProof<K, V, N>>, api::Error>
where
K: api::KeyType,
{
todo!()
}
}

impl Drop for Proposal {
fn drop(&mut self) {
if !*self.committed.lock() {
Expand Down
8 changes: 4 additions & 4 deletions firewood/src/v2/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ pub enum Error {

#[error("Invalid proposal")]
InvalidProposal,

#[error("Internal error")]
InternalError(Box<dyn std::error::Error>),
}

/// A range proof, consisting of a proof of the first key and the last key,
Expand Down Expand Up @@ -139,10 +142,7 @@ pub trait DbView {
async fn val<K: KeyType>(&self, key: K) -> Result<Option<Vec<u8>>, Error>;

/// Obtain a proof for a single key
async fn single_key_proof<K: KeyType, V: ValueType>(
&self,
key: K,
) -> Result<Option<Proof<V>>, Error>;
async fn single_key_proof<K: KeyType>(&self, key: K) -> Result<Option<Proof<Vec<u8>>>, Error>;

/// Obtain a range proof over a set of keys
///
Expand Down
23 changes: 20 additions & 3 deletions firewood/src/v2/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use tokio::sync::Mutex;

use async_trait::async_trait;

use crate::v2::api::{self, Batch, KeyType, ValueType};
use crate::{
db::DbError,
v2::api::{self, Batch, KeyType, ValueType},
};

use super::propose;

Expand All @@ -26,6 +29,20 @@ pub struct Db<T> {
latest_cache: Mutex<Option<Arc<T>>>,
}

impl From<DbError> for api::Error {
fn from(value: DbError) -> Self {
match value {
DbError::InvalidParams => api::Error::InternalError(value.into()),
DbError::Merkle(e) => api::Error::InternalError(e.into()),
DbError::System(e) => api::Error::IO(e.into()),
DbError::KeyNotFound | DbError::CreateError => api::Error::InternalError(value.into()),
DbError::Shale(e) => api::Error::InternalError(e.into()),
DbError::IO(e) => api::Error::IO(e),
DbError::InvalidProposal => api::Error::InvalidProposal,
}
}
}

#[async_trait]
impl<T> api::Db for Db<T>
where
Expand Down Expand Up @@ -79,10 +96,10 @@ impl api::DbView for DbView {
todo!()
}

async fn single_key_proof<K: KeyType, V: ValueType>(
async fn single_key_proof<K: KeyType>(
&self,
_key: K,
) -> Result<Option<api::Proof<V>>, api::Error> {
) -> Result<Option<api::Proof<Vec<u8>>>, api::Error> {
todo!()
}

Expand Down
5 changes: 1 addition & 4 deletions firewood/src/v2/emptydb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ impl DbView for HistoricalImpl {
Ok(None)
}

async fn single_key_proof<K: KeyType, V: ValueType>(
&self,
_key: K,
) -> Result<Option<Proof<V>>, Error> {
async fn single_key_proof<K: KeyType>(&self, _key: K) -> Result<Option<Proof<Vec<u8>>>, Error> {
Ok(None)
}

Expand Down
4 changes: 2 additions & 2 deletions firewood/src/v2/propose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,10 @@ impl<T: api::DbView + Send + Sync> api::DbView for Proposal<T> {
}
}

async fn single_key_proof<K: KeyType, V: ValueType>(
async fn single_key_proof<K: KeyType>(
&self,
_key: K,
) -> Result<Option<api::Proof<V>>, api::Error> {
) -> Result<Option<api::Proof<Vec<u8>>>, api::Error> {
todo!()
}

Expand Down
Loading

0 comments on commit 6f29a00

Please sign in to comment.