Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add database models #289

Merged
merged 28 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
db813ba
Add key_store table and generate
richardhuaaa Oct 24, 2023
f9c6ec8
Implement store/fetch/delete traits
richardhuaaa Oct 24, 2023
257d5b6
Add SQL key store
richardhuaaa Oct 24, 2023
6af1384
Use SQL key store for provider, remove in-memory key store
richardhuaaa Oct 25, 2023
a404c20
Use reference to encrypted store to allow multiple consumers
richardhuaaa Oct 25, 2023
acd42b8
Tidy identity
richardhuaaa Oct 25, 2023
0a53a11
Add identity table, queries, and unit tests
richardhuaaa Oct 26, 2023
4fb1059
Persist and retrieve identity from storage inside builder
richardhuaaa Oct 26, 2023
598b5ea
Tidy up names and types
richardhuaaa Oct 26, 2023
c8780f0
Merge branch 'main' into rich/sqlite-store
neekolas Oct 27, 2023
091afdb
Add tests and fix bugs
richardhuaaa Oct 27, 2023
18c6aa4
Fix lints
richardhuaaa Oct 27, 2023
dae1c74
Update xmtp_mls/src/storage/encrypted_store/mod.rs
richardhuaaa Oct 27, 2023
77aa94b
Dont use the word Error in error names
richardhuaaa Oct 27, 2023
5278d13
Move identity initialization to identity strategy
richardhuaaa Oct 28, 2023
a6c5095
Merge remote-tracking branch 'origin/main' into rich/sqlite-store
richardhuaaa Oct 28, 2023
7000fb4
Merge branch 'rich/sqlite-store' of github.com:xmtp/libxmtp into rich…
neekolas Oct 28, 2023
afea4b3
Refactor DB stuff into multiple files
neekolas Oct 30, 2023
671754d
Delete models.rs
neekolas Oct 30, 2023
0582dda
Merge branch 'main' into nmolnar/add-db-models
neekolas Oct 30, 2023
787e392
Cleanup
neekolas Oct 30, 2023
b570521
Add kind to group_messages
neekolas Oct 30, 2023
ad25444
Update schema
neekolas Oct 31, 2023
24fd63a
Update schema
neekolas Nov 1, 2023
b1797ae
Update schema
neekolas Nov 1, 2023
78f274e
Merge branch 'main' into nmolnar/add-db-models
neekolas Nov 1, 2023
390f60f
Add ciphersuite back in as import
neekolas Nov 1, 2023
caab44a
Merge branch 'main' into nmolnar/add-db-models
neekolas Nov 1, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
CREATE TABLE users (
user_address TEXT PRIMARY KEY NOT NULL,
created_at BIGINT NOT NULL,
last_refreshed BIGINT NOT NULL
user_address TEXT PRIMARY KEY NOT NULL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we re-using some of the tables from XMTP?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This schema below is all we have a need for at the moment. Because OpenMLS has its own database structure with the KeyValue store, some of the tables from the old xmtp schema simply aren't necessary.

created_at BIGINT NOT NULL,
last_refreshed BIGINT NOT NULL
);

CREATE TABLE conversations (
convo_id TEXT PRIMARY KEY NOT NULL,
peer_address TEXT NOT NULL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
DROP TABLE IF EXISTS groups;

DROP TABLE IF EXISTS group_messages;

DROP TABLE IF EXISTS topic_refresh_state;

DROP TABLE IF EXISTS group_intents;

DROP TABLE IF EXISTS outbound_welcome_messages;
72 changes: 72 additions & 0 deletions xmtp_mls/migrations/2023-10-29-205333_state_machine_init/up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
CREATE TABLE groups (
-- Random ID generated by group creator
"id" BLOB PRIMARY KEY NOT NULL,
-- Based on the timestamp of the welcome message
"created_at_ns" BIGINT NOT NULL,
-- Enum of GROUP_MEMBERSHIP_STATE
"membership_state" INT NOT NULL
neekolas marked this conversation as resolved.
Show resolved Hide resolved
);

-- Allow for efficient sorting of groups
CREATE INDEX groups_created_at_idx ON groups(created_at_ns);

CREATE INDEX groups_membership_state ON groups(membership_state);

-- Successfully processed messages meant to be returned to the user
CREATE TABLE group_messages (
"id" BLOB PRIMARY KEY NOT NULL,
-- Derived via SHA256(CONCAT(decrypted_message_bytes, conversation_id, timestamp))
richardhuaaa marked this conversation as resolved.
Show resolved Hide resolved
"group_id" BLOB NOT NULL,
-- Message contents after decryption
"decrypted_message_bytes" BLOB NOT NULL,
-- Based on the timestamp of the message
"sent_at_ns" BIGINT NOT NULL,
-- Enum GROUP_MESSAGE_KIND
"kind" INT NOT NULL,
-- Could remove this if we added a table mapping installation_ids to wallet addresses
richardhuaaa marked this conversation as resolved.
Show resolved Hide resolved
"sender_installation_id" BLOB NOT NULL,
"sender_wallet_address" TEXT NOT NULL,
FOREIGN KEY (group_id) REFERENCES groups(id)
);

CREATE INDEX group_messages_group_id_sort_idx ON group_messages(group_id, sent_at_ns);

-- Used to keep track of the last seen message timestamp in a topic
CREATE TABLE topic_refresh_state (
"topic" TEXT PRIMARY KEY NOT NULL,
"last_message_timestamp_ns" BIGINT NOT NULL
);

-- This table is required to retry messages that do not send successfully due to epoch conflicts
CREATE TABLE group_intents (
-- Serial ID auto-generated by the DB
"id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-- Enum INTENT_KIND
"kind" INT NOT NULL,
"group_id" BLOB NOT NULL,
-- Some sort of serializable blob that can be used to re-try the message if the first attempt failed due to conflict
"data" BLOB NOT NULL,
-- INTENT_STATE,
"state" INT NOT NULL,
-- The hash of the encrypted, concrete, form of the message if it was published.
neekolas marked this conversation as resolved.
Show resolved Hide resolved
"message_hash" BLOB,
FOREIGN KEY (group_id) REFERENCES groups(id)
);

CREATE INDEX group_intents_group_id_id ON group_intents(group_id, id);

CREATE TABLE outbound_welcome_messages (
neekolas marked this conversation as resolved.
Show resolved Hide resolved
-- Derived via SHA256(CONCAT(group_id, welcome_message, installation_id))
"id" BLOB PRIMARY KEY NOT NULL,
-- OUTBOUND_WELCOME_STATE
"state" INT NOT NULL,
"installation_id" BLOB NOT NULL,
-- The hash of the commit message which created this welcome
"commit_hash" BLOB NOT NULL,
-- The group this welcome belongs to
"group_id" BLOB NOT NULL,
"welcome_message" BLOB NOT NULL,
FOREIGN KEY (group_id) REFERENCES groups(id)
);

CREATE INDEX outbound_welcome_messages_commit_hash ON outbound_welcome_messages(commit_hash, state);
2 changes: 1 addition & 1 deletion xmtp_mls/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::configuration::CIPHERSUITE;
use crate::storage::StoredIdentity;
use crate::storage::identity::StoredIdentity;
use crate::xmtp_openmls_provider::XmtpOpenMlsProvider;
use crate::{
client::{Client, Network},
Expand Down
2 changes: 1 addition & 1 deletion xmtp_mls/src/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use xmtp_cryptography::signature::SignatureError;

use crate::{
association::{AssociationError, AssociationText, Eip191Association},
storage::{EncryptedMessageStore, StorageError, StoredIdentity},
storage::{identity::StoredIdentity, EncryptedMessageStore, StorageError},
types::Address,
xmtp_openmls_provider::XmtpOpenMlsProvider,
InboxOwner, Store,
Expand Down
11 changes: 11 additions & 0 deletions xmtp_mls/src/storage/encrypted_store/group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use super::schema::groups;
use diesel::prelude::*;

#[derive(Insertable, Identifiable, Queryable, Debug, Clone)]
#[diesel(table_name = groups)]
#[diesel(primary_key(id))]
pub struct StoredGroup {
pub id: Vec<u8>,
pub created_at_ns: i64,
pub membership_state: i32,
}
23 changes: 23 additions & 0 deletions xmtp_mls/src/storage/encrypted_store/group_intent.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::schema::group_intents;
use diesel::prelude::*;

#[derive(Queryable, Identifiable, Debug, Clone)]
#[diesel(table_name = group_intents)]
#[diesel(primary_key(id))]
pub struct StoredGroupIntent {
pub id: i32,
pub kind: i32,
pub state: i32,
pub group_id: Vec<u8>,
pub data: Vec<u8>,
pub message_hash: Option<Vec<u8>>,
}

#[derive(Insertable, Debug, Clone)]
#[diesel(table_name = group_intents)]
pub struct NewGroupIntent {
pub kind: i32,
pub state: i32,
pub group_id: Vec<u8>,
pub data: Vec<u8>,
}
14 changes: 14 additions & 0 deletions xmtp_mls/src/storage/encrypted_store/group_message.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use super::schema::group_messages;
use diesel::prelude::*;

#[derive(Insertable, Identifiable, Queryable, Debug, Clone)]
#[diesel(table_name = group_messages)]
#[diesel(primary_key(id))]
pub struct StoredGroupMessage {
pub id: Vec<u8>,
pub group_id: Vec<u8>,
pub decrypted_message_bytes: Vec<u8>,
pub sent_at_ns: i64,
pub sender_installation_id: Vec<u8>,
pub sender_wallet_address: String,
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
use diesel::prelude::*;

use super::{schema::identity, DbConnection, StorageError};
use crate::{
identity::Identity,
storage::serialization::{db_deserialize, db_serialize},
Fetch, Store,
};

use super::schema::*;

#[derive(Insertable, Queryable, Debug, Clone)]
#[diesel(table_name = openmls_key_store)]
#[diesel(primary_key(key_bytes))]
pub struct StoredKeyStoreEntry {
pub key_bytes: Vec<u8>,
pub value_bytes: Vec<u8>,
}
use diesel::prelude::*;

#[derive(Insertable, Queryable, Debug, Clone)]
#[diesel(table_name = identity)]
Expand Down Expand Up @@ -59,3 +50,47 @@ impl From<StoredIdentity> for Identity {
}
}
}

impl Store<DbConnection> for StoredIdentity {
fn store(&self, into: &mut DbConnection) -> Result<(), StorageError> {
diesel::insert_into(identity::table)
.values(self)
.execute(into)?;
Ok(())
}
}

impl Fetch<StoredIdentity> for DbConnection {
type Key = ();
fn fetch(&mut self, _key: ()) -> Result<Option<StoredIdentity>, StorageError> where {
use super::schema::identity::dsl::*;
Ok(identity.first(self).optional()?)
}
}

#[cfg(test)]
mod tests {
use super::{
super::{tests::rand_vec, EncryptedMessageStore, StorageOption},
StoredIdentity,
};
use crate::Store;

#[test]
fn can_only_store_one_identity() {
let store = EncryptedMessageStore::new(
StorageOption::Ephemeral,
EncryptedMessageStore::generate_enc_key(),
)
.unwrap();
let conn = &mut store.conn().unwrap();

StoredIdentity::new("".to_string(), rand_vec(), rand_vec())
.store(conn)
.unwrap();

let duplicate_insertion =
StoredIdentity::new("".to_string(), rand_vec(), rand_vec()).store(conn);
assert!(duplicate_insertion.is_err());
}
}
38 changes: 38 additions & 0 deletions xmtp_mls/src/storage/encrypted_store/key_store.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use super::DbConnection;
neekolas marked this conversation as resolved.
Show resolved Hide resolved
use super::{schema::openmls_key_store, StorageError};
use crate::{Delete, Fetch, Store};
use diesel::prelude::*;

#[derive(Insertable, Queryable, Debug, Clone)]
#[diesel(table_name = openmls_key_store)]
#[diesel(primary_key(key_bytes))]
pub struct StoredKeyStoreEntry {
pub key_bytes: Vec<u8>,
pub value_bytes: Vec<u8>,
}

impl Store<DbConnection> for StoredKeyStoreEntry {
fn store(&self, into: &mut DbConnection) -> Result<(), StorageError> {
diesel::insert_into(openmls_key_store::table)
.values(self)
.execute(into)?;

Ok(())
}
}

impl Fetch<StoredKeyStoreEntry> for DbConnection {
type Key = Vec<u8>;
fn fetch(&mut self, key: Vec<u8>) -> Result<Option<StoredKeyStoreEntry>, StorageError> where {
use super::schema::openmls_key_store::dsl::*;
Ok(openmls_key_store.find(key).first(self).optional()?)
}
}

impl Delete<StoredKeyStoreEntry> for DbConnection {
type Key = Vec<u8>;
fn delete(&mut self, key: Vec<u8>) -> Result<usize, StorageError> where {
use super::schema::openmls_key_store::dsl::*;
Ok(diesel::delete(openmls_key_store.filter(key_bytes.eq(key))).execute(self)?)
}
}
79 changes: 10 additions & 69 deletions xmtp_mls/src/storage/encrypted_store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
//! `diesel print-schema` or use `cargo run update-schema` which will update the files for you.
//!

pub mod models;
pub mod group;
pub mod group_intent;
pub mod group_message;
pub mod identity;
pub mod key_store;
pub mod outbound_welcome_message;
pub mod schema;

use crate::{Delete, Fetch, Store};

use self::{models::*, schema::*};
pub mod topic_refresh_state;

use super::StorageError;
use diesel::{
Expand Down Expand Up @@ -165,52 +167,9 @@ fn warn_length<T>(list: &Vec<T>, str_id: &str, max_length: usize) {
}
}

impl Store<DbConnection> for StoredKeyStoreEntry {
fn store(&self, into: &mut DbConnection) -> Result<(), StorageError> {
diesel::insert_into(openmls_key_store::table)
.values(self)
.execute(into)?;

Ok(())
}
}

impl Fetch<StoredKeyStoreEntry> for DbConnection {
type Key = Vec<u8>;
fn fetch(&mut self, key: Vec<u8>) -> Result<Option<StoredKeyStoreEntry>, StorageError> where {
use self::schema::openmls_key_store::dsl::*;
Ok(openmls_key_store.find(key).first(self).optional()?)
}
}

impl Delete<StoredKeyStoreEntry> for DbConnection {
type Key = Vec<u8>;
fn delete(&mut self, key: Vec<u8>) -> Result<usize, StorageError> where {
use self::schema::openmls_key_store::dsl::*;
Ok(diesel::delete(openmls_key_store.filter(key_bytes.eq(key))).execute(self)?)
}
}

impl Store<DbConnection> for StoredIdentity {
fn store(&self, into: &mut DbConnection) -> Result<(), StorageError> {
diesel::insert_into(identity::table)
.values(self)
.execute(into)?;
Ok(())
}
}

impl Fetch<StoredIdentity> for DbConnection {
type Key = ();
fn fetch(&mut self, _key: ()) -> Result<Option<StoredIdentity>, StorageError> where {
use self::schema::identity::dsl::*;
Ok(identity.first(self).optional()?)
}
}

#[cfg(test)]
mod tests {
use super::{models::*, EncryptedMessageStore, StorageError, StorageOption};
use super::{identity::StoredIdentity, EncryptedMessageStore, StorageError, StorageOption};
use crate::{Fetch, Store};
use rand::{
distributions::{Alphanumeric, DistString},
Expand All @@ -219,11 +178,11 @@ mod tests {
use std::boxed::Box;
use std::fs;

fn rand_string() -> String {
pub(crate) fn rand_string() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
}

fn rand_vec() -> Vec<u8> {
pub(crate) fn rand_vec() -> Vec<u8> {
rand::thread_rng().gen::<[u8; 16]>().to_vec()
}

Expand Down Expand Up @@ -296,24 +255,6 @@ mod tests {
fs::remove_file(db_path).unwrap();
}

#[test]
fn can_only_store_one_identity() {
let store = EncryptedMessageStore::new(
StorageOption::Ephemeral,
EncryptedMessageStore::generate_enc_key(),
)
.unwrap();
let conn = &mut store.conn().unwrap();

StoredIdentity::new("".to_string(), rand_vec(), rand_vec())
.store(conn)
.unwrap();

let duplicate_insertion =
StoredIdentity::new("".to_string(), rand_vec(), rand_vec()).store(conn);
assert!(duplicate_insertion.is_err());
}

#[test]
fn it_returns_ok_when_given_ok_result() {
let result: Result<(), diesel::result::Error> = Ok(());
Expand Down
Loading
Loading