Skip to content

Commit

Permalink
Merge branch 'next' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
julian-demox authored Oct 15, 2024
2 parents 4425f35 + 9eba51c commit 33cbba2
Show file tree
Hide file tree
Showing 23 changed files with 507 additions and 195 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

## 0.6.0 (TBD)

* Fixed WASM + added additional WASM models
* Fixed WASM + added additional WASM models (#548)
* Added dedicated separate table for tracked tags (#535).
* [BREAKING] Added support for committed and discarded transactions (#531).
* [BREAKING] Refactored Client struct to use trait objects for inner struct fields (#539).
* Fixed panic on export command without type (#537).
Expand Down
12 changes: 10 additions & 2 deletions crates/rust-client/src/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ use miden_objects::{
accounts::AuthSecretKey,
assets::TokenSymbol,
crypto::{dsa::rpo_falcon512::SecretKey, rand::FeltRng},
notes::{NoteExecutionMode, NoteTag},
Felt, Word,
};
use winter_maybe_async::{maybe_async, maybe_await};

use super::Client;
use crate::ClientError;
use crate::{sync::NoteTagRecord, ClientError};

/// Defines templates for creating different types of Miden accounts.
pub enum AccountTemplate {
Expand Down Expand Up @@ -50,7 +51,8 @@ impl<R: FeltRng> Client<R> {
// ACCOUNT CREATION
// --------------------------------------------------------------------------------------------

/// Creates a new [Account] based on an [AccountTemplate] and saves it in the client's store.
/// Creates a new [Account] based on an [AccountTemplate] and saves it in the client's store. A
/// new tag derived from the account will start being tracked by the client.
#[maybe_async]
pub fn new_account(
&mut self,
Expand All @@ -73,6 +75,12 @@ impl<R: FeltRng> Client<R> {
)),
}?;

let id = account_and_seed.0.id();
maybe_await!(self.store.add_note_tag(NoteTagRecord::with_account_source(
NoteTag::from_account_id(id, NoteExecutionMode::Local)?,
id
)))?;

Ok(account_and_seed)
}

Expand Down
25 changes: 23 additions & 2 deletions crates/rust-client/src/notes/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use miden_objects::{
use winter_maybe_async::maybe_await;

use crate::{
store::{ExpectedNoteState, InputNoteRecord},
store::{ExpectedNoteState, InputNoteRecord, NoteState},
sync::NoteTagRecord,
Client, ClientError,
};

Expand All @@ -17,7 +18,7 @@ impl<R: FeltRng> Client<R> {

/// Imports a new input note into the client's store. The information stored depends on the
/// type of note file provided. If the note existed previously, it will be updated with the
/// new information.
/// new information. The tag specified by the `NoteFile` will start being tracked.
///
/// - If the note file is a [NoteFile::NoteId], the note is fetched from the node and stored in
/// the client's store. If the note is private or does not exist, an error is returned.
Expand Down Expand Up @@ -47,6 +48,11 @@ impl<R: FeltRng> Client<R> {
};

if let Some(note) = note {
if let NoteState::Expected(ExpectedNoteState { tag: Some(tag), .. }) = note.state() {
maybe_await!(self
.store
.add_note_tag(NoteTagRecord::with_note_source(*tag, note.id())))?;
}
maybe_await!(self.store.upsert_input_note(note))?;
}

Expand Down Expand Up @@ -90,6 +96,11 @@ impl<R: FeltRng> Client<R> {
if previous_note
.inclusion_proof_received(inclusion_proof, *note_details.metadata())?
{
maybe_await!(self.store.remove_note_tag(NoteTagRecord::with_note_source(
note_details.metadata().tag(),
note_details.id()
)))?;

Ok(Some(previous_note))
} else {
Ok(None)
Expand Down Expand Up @@ -162,6 +173,11 @@ impl<R: FeltRng> Client<R> {
}

if note_changed {
maybe_await!(self.store.remove_note_tag(NoteTagRecord::with_note_source(
metadata.tag(),
note_record.id()
)))?;

Ok(Some(note_record))
} else {
Ok(None)
Expand Down Expand Up @@ -206,6 +222,11 @@ impl<R: FeltRng> Client<R> {
note_record.inclusion_proof_received(inclusion_proof, metadata)?;

if note_record.block_header_received(block_header)? | note_changed {
maybe_await!(self.store.remove_note_tag(NoteTagRecord::with_note_source(
metadata.tag(),
note_record.id()
)))?;

Ok(Some(note_record))
} else {
Ok(None)
Expand Down
21 changes: 15 additions & 6 deletions crates/rust-client/src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
//! and retrieving data, such as account states, transaction history, and block headers.
#[cfg(feature = "async")]
use alloc::boxed::Box;
use alloc::{collections::BTreeMap, vec::Vec};
use alloc::{
collections::{BTreeMap, BTreeSet},
vec::Vec,
};
use core::fmt::Debug;

use miden_objects::{
Expand All @@ -14,7 +17,7 @@ use miden_objects::{
use winter_maybe_async::*;

use crate::{
sync::StateSyncUpdate,
sync::{NoteTagRecord, StateSyncUpdate},
transactions::{TransactionRecord, TransactionResult},
};

Expand Down Expand Up @@ -273,23 +276,29 @@ pub trait Store {
// SYNC
// --------------------------------------------------------------------------------------------

/// Returns the note tags that the client is interested in.
/// Returns the note tag records that the client is interested in.
#[maybe_async]
fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, StoreError>;

/// Returns the unique note tags (without source) that the client is interested in.
#[maybe_async]
fn get_note_tags(&self) -> Result<Vec<NoteTag>, StoreError>;
fn get_unique_note_tags(&self) -> Result<BTreeSet<NoteTag>, StoreError> {
Ok(maybe_await!(self.get_note_tags())?.into_iter().map(|r| r.tag).collect())
}

/// Adds a note tag to the list of tags that the client is interested in.
///
/// If the tag was already being tracked, returns false since no new tags were actually added.
/// Otherwise true.
#[maybe_async]
fn add_note_tag(&self, tag: NoteTag) -> Result<bool, StoreError>;
fn add_note_tag(&self, tag: NoteTagRecord) -> Result<bool, StoreError>;

/// Removes a note tag from the list of tags that the client is interested in.
///
/// If the tag was not present in the store returns false since no tag was actually removed.
/// Otherwise returns true.
#[maybe_async]
fn remove_note_tag(&self, tag: NoteTag) -> Result<bool, StoreError>;
fn remove_note_tag(&self, tag: NoteTagRecord) -> Result<usize, StoreError>;

/// Returns the block number of the last state sync block.
#[maybe_async]
Expand Down
18 changes: 13 additions & 5 deletions crates/rust-client/src/store/sqlite_store/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use alloc::{collections::BTreeMap, vec::Vec};
use alloc::{
collections::{BTreeMap, BTreeSet},
vec::Vec,
};
use core::cell::{RefCell, RefMut};
use std::path::Path;

Expand All @@ -17,7 +20,7 @@ use super::{
};
use crate::{
store::StoreError,
sync::StateSyncUpdate,
sync::{NoteTagRecord, StateSyncUpdate},
transactions::{TransactionRecord, TransactionResult},
};

Expand Down Expand Up @@ -74,17 +77,22 @@ use alloc::boxed::Box;
#[maybe_async_trait]
impl Store for SqliteStore {
#[maybe_async]
fn get_note_tags(&self) -> Result<Vec<NoteTag>, StoreError> {
fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, StoreError> {
self.get_note_tags()
}

#[maybe_async]
fn add_note_tag(&self, tag: NoteTag) -> Result<bool, StoreError> {
fn get_unique_note_tags(&self) -> Result<BTreeSet<NoteTag>, StoreError> {
self.get_unique_note_tags()
}

#[maybe_async]
fn add_note_tag(&self, tag: NoteTagRecord) -> Result<bool, StoreError> {
self.add_note_tag(tag)
}

#[maybe_async]
fn remove_note_tag(&self, tag: NoteTag) -> Result<bool, StoreError> {
fn remove_note_tag(&self, tag: NoteTagRecord) -> Result<usize, StoreError> {
self.remove_note_tag(tag)
}

Expand Down
11 changes: 8 additions & 3 deletions crates/rust-client/src/store/sqlite_store/store.sql
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,18 @@ CREATE TABLE notes_scripts (
-- Create state sync table
CREATE TABLE state_sync (
block_num UNSIGNED BIG INT NOT NULL, -- the block number of the most recent state sync
tags BLOB NULL, -- the serialized list of tags, a NULL means an empty list
PRIMARY KEY (block_num)
);

-- Create tags table
CREATE TABLE tags (
tag BLOB NOT NULL, -- the serialized tag
source BLOB NOT NULL -- the serialized tag source
);

-- insert initial row into state_sync table
INSERT OR IGNORE INTO state_sync (block_num, tags)
SELECT 0, NULL
INSERT OR IGNORE INTO state_sync (block_num)
SELECT 0
WHERE (
SELECT COUNT(*) FROM state_sync
) = 0;
Expand Down
99 changes: 67 additions & 32 deletions crates/rust-client/src/store/sqlite_store/sync.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use alloc::{string::ToString, vec::Vec};
use alloc::{collections::BTreeSet, string::ToString, vec::Vec};

use miden_objects::notes::{NoteInclusionProof, NoteTag};
use miden_tx::utils::{Deserializable, Serializable};
use rusqlite::{named_params, params};
use rusqlite::{named_params, params, Transaction};

use super::SqliteStore;
use crate::{
Expand All @@ -11,57 +11,67 @@ use crate::{
sqlite_store::{accounts::update_account, notes::upsert_input_note_tx},
CommittedNoteState, InputNoteRecord, NoteFilter, StoreError,
},
sync::StateSyncUpdate,
sync::{NoteTagRecord, NoteTagSource, StateSyncUpdate},
};

impl SqliteStore {
pub(crate) fn get_note_tags(&self) -> Result<Vec<NoteTag>, StoreError> {
const QUERY: &str = "SELECT tags FROM state_sync";
pub(crate) fn get_note_tags(&self) -> Result<Vec<NoteTagRecord>, StoreError> {
const QUERY: &str = "SELECT tag, source FROM tags";

self.db()
.prepare(QUERY)?
.query_map([], |row| Ok((row.get(0)?, row.get(1)?)))
.expect("no binding parameters used in query")
.map(|result| {
Ok(result?).and_then(|(tag, source): (Vec<u8>, Vec<u8>)| {
Ok(NoteTagRecord {
tag: NoteTag::read_from_bytes(&tag)
.map_err(StoreError::DataDeserializationError)?,
source: NoteTagSource::read_from_bytes(&source)
.map_err(StoreError::DataDeserializationError)?,
})
})
})
.collect::<Result<Vec<NoteTagRecord>, _>>()
}

pub(crate) fn get_unique_note_tags(&self) -> Result<BTreeSet<NoteTag>, StoreError> {
const QUERY: &str = "SELECT DISTINCT tag FROM tags";

self.db()
.prepare(QUERY)?
.query_map([], |row| row.get(0))
.expect("no binding parameters used in query")
.map(|result| {
result.map_err(|err| StoreError::ParsingError(err.to_string())).and_then(
|v: Option<Vec<u8>>| match v {
Some(tags) => Vec::<NoteTag>::read_from_bytes(&tags)
.map_err(StoreError::DataDeserializationError),
None => Ok(Vec::<NoteTag>::new()),
},
)
Ok(result?).and_then(|tag: Vec<u8>| {
NoteTag::read_from_bytes(&tag).map_err(StoreError::DataDeserializationError)
})
})
.next()
.expect("state sync tags exist")
.collect::<Result<BTreeSet<NoteTag>, _>>()
}

pub(super) fn add_note_tag(&self, tag: NoteTag) -> Result<bool, StoreError> {
let mut tags = self.get_note_tags()?;
if tags.contains(&tag) {
pub(super) fn add_note_tag(&self, tag: NoteTagRecord) -> Result<bool, StoreError> {
if self.get_note_tags()?.contains(&tag) {
return Ok(false);
}
tags.push(tag);
let tags = tags.to_bytes();

const QUERY: &str = "UPDATE state_sync SET tags = :tags";
self.db().execute(QUERY, named_params! {":tags": tags})?;
let mut db = self.db();
let tx = db.transaction()?;
add_note_tag_tx(&tx, tag)?;

tx.commit()?;

Ok(true)
}

pub(super) fn remove_note_tag(&self, tag: NoteTag) -> Result<bool, StoreError> {
let mut tags = self.get_note_tags()?;
if let Some(index_of_tag) = tags.iter().position(|&tag_candidate| tag_candidate == tag) {
tags.remove(index_of_tag);

let tags = tags.to_bytes();
pub(super) fn remove_note_tag(&self, tag: NoteTagRecord) -> Result<usize, StoreError> {
let mut db = self.db();
let tx = db.transaction()?;
let removed_tags = remove_note_tag_tx(&tx, tag)?;

const QUERY: &str = "UPDATE state_sync SET tags = ?";
self.db().execute(QUERY, params![tags])?;
return Ok(true);
}
tx.commit()?;

Ok(false)
Ok(removed_tags)
}

pub(super) fn get_sync_height(&self) -> Result<u32, StoreError> {
Expand Down Expand Up @@ -144,6 +154,14 @@ impl SqliteStore {
if inclusion_proof_received || block_header_received {
upsert_input_note_tx(&tx, input_note_record)?;
}

remove_note_tag_tx(
&tx,
NoteTagRecord::with_note_source(
input_note.note().metadata().tag(),
input_note_record.id(),
),
)?;
}
}

Expand Down Expand Up @@ -262,3 +280,20 @@ impl SqliteStore {
Ok(relevant_notes)
}
}

pub(super) fn add_note_tag_tx(tx: &Transaction<'_>, tag: NoteTagRecord) -> Result<(), StoreError> {
const QUERY: &str = "INSERT INTO tags (tag, source) VALUES (?, ?)";
tx.execute(QUERY, params![tag.tag.to_bytes(), tag.source.to_bytes()])?;

Ok(())
}

pub(super) fn remove_note_tag_tx(
tx: &Transaction<'_>,
tag: NoteTagRecord,
) -> Result<usize, StoreError> {
const QUERY: &str = "DELETE FROM tags WHERE tag = ? AND source = ?";
let removed_tags = tx.execute(QUERY, params![tag.tag.to_bytes(), tag.source.to_bytes()])?;

Ok(removed_tags)
}
Loading

0 comments on commit 33cbba2

Please sign in to comment.