use diesel::prelude::*;
use miden_node_db::DatabaseError;
use miden_node_proto::domain::account::NetworkAccountId;
use miden_protocol::account::delta::AccountUpdateDetails;
use miden_protocol::block::{BlockHeader, BlockNumber};
use miden_protocol::note::Nullifier;
use miden_protocol::transaction::TransactionId;
use miden_standards::note::AccountTargetNetworkNote;
use miden_tx::utils::Serializable;
use crate::actor::account_effect::NetworkAccountEffect;
use crate::db::models::conv as conversions;
use crate::db::schema;
mod accounts;
pub use accounts::*;
mod chain_state;
pub use chain_state::*;
mod note_scripts;
pub use note_scripts::*;
mod notes;
pub use notes::*;
#[cfg(test)]
mod tests;
pub fn purge_inflight(conn: &mut SqliteConnection) -> Result<(), DatabaseError> {
diesel::delete(schema::accounts::table.filter(schema::accounts::transaction_id.is_not_null()))
.execute(conn)?;
diesel::delete(schema::notes::table.filter(schema::notes::created_by.is_not_null()))
.execute(conn)?;
diesel::update(schema::notes::table.filter(schema::notes::consumed_by.is_not_null()))
.set(schema::notes::consumed_by.eq(None::<Vec<u8>>))
.execute(conn)?;
Ok(())
}
pub fn add_transaction(
conn: &mut SqliteConnection,
tx_id: &TransactionId,
account_delta: Option<&AccountUpdateDetails>,
notes: &[AccountTargetNetworkNote],
nullifiers: &[Nullifier],
) -> Result<(), DatabaseError> {
let tx_id_bytes = conversions::transaction_id_to_bytes(tx_id);
if let Some(update) = account_delta.and_then(NetworkAccountEffect::from_protocol) {
let account_id = update.network_account_id();
match update {
NetworkAccountEffect::Updated(ref account_delta) => {
let current_account =
get_account(conn, account_id)?.expect("account must exist to apply delta");
let mut updated = current_account;
updated.apply_delta(account_delta).expect(
"network account delta should apply since it was accepted by the mempool",
);
let insert = AccountInsert {
account_id: conversions::network_account_id_to_bytes(account_id),
transaction_id: Some(tx_id_bytes.clone()),
account_data: conversions::account_to_bytes(&updated),
};
diesel::insert_into(schema::accounts::table).values(&insert).execute(conn)?;
},
NetworkAccountEffect::Created(ref account) => {
let insert = AccountInsert {
account_id: conversions::network_account_id_to_bytes(account_id),
transaction_id: Some(tx_id_bytes.clone()),
account_data: conversions::account_to_bytes(account),
};
diesel::insert_into(schema::accounts::table).values(&insert).execute(conn)?;
},
}
}
for note in notes {
let insert = NoteInsert {
nullifier: conversions::nullifier_to_bytes(¬e.as_note().nullifier()),
account_id: conversions::network_account_id_to_bytes(
note.target_account_id()
.try_into()
.expect("network note's target account must be a network account"),
),
note_data: note.as_note().to_bytes(),
attempt_count: 0,
last_attempt: None,
created_by: Some(tx_id_bytes.clone()),
consumed_by: None,
};
diesel::insert_or_ignore_into(schema::notes::table)
.values(&insert)
.execute(conn)?;
}
for nullifier in nullifiers {
let nullifier_bytes = conversions::nullifier_to_bytes(nullifier);
diesel::update(
schema::notes::table
.find(&nullifier_bytes)
.filter(schema::notes::consumed_by.is_null()),
)
.set(schema::notes::consumed_by.eq(Some(&tx_id_bytes)))
.execute(conn)?;
}
Ok(())
}
pub fn commit_block(
conn: &mut SqliteConnection,
tx_ids: &[TransactionId],
block_num: BlockNumber,
block_header: &BlockHeader,
) -> Result<(), DatabaseError> {
for tx_id in tx_ids {
let tx_id_bytes = conversions::transaction_id_to_bytes(tx_id);
let inflight_account_ids: Vec<Vec<u8>> = schema::accounts::table
.filter(schema::accounts::transaction_id.eq(&tx_id_bytes))
.select(schema::accounts::account_id)
.load(conn)?;
for account_id_bytes in &inflight_account_ids {
diesel::delete(
schema::accounts::table
.filter(schema::accounts::account_id.eq(account_id_bytes))
.filter(schema::accounts::transaction_id.is_null()),
)
.execute(conn)?;
diesel::update(
schema::accounts::table
.filter(schema::accounts::account_id.eq(account_id_bytes))
.filter(schema::accounts::transaction_id.eq(&tx_id_bytes)),
)
.set(schema::accounts::transaction_id.eq(None::<Vec<u8>>))
.execute(conn)?;
}
diesel::delete(schema::notes::table.filter(schema::notes::consumed_by.eq(&tx_id_bytes)))
.execute(conn)?;
diesel::update(schema::notes::table.filter(schema::notes::created_by.eq(&tx_id_bytes)))
.set(schema::notes::created_by.eq(None::<Vec<u8>>))
.execute(conn)?;
}
upsert_chain_state(conn, block_num, block_header)?;
Ok(())
}
pub fn revert_transaction(
conn: &mut SqliteConnection,
tx_ids: &[TransactionId],
) -> Result<Vec<NetworkAccountId>, DatabaseError> {
let mut reverted_accounts = Vec::new();
for tx_id in tx_ids {
let tx_id_bytes = conversions::transaction_id_to_bytes(tx_id);
let affected_account_ids: Vec<Vec<u8>> = schema::accounts::table
.filter(schema::accounts::transaction_id.eq(&tx_id_bytes))
.select(schema::accounts::account_id)
.load(conn)?;
diesel::delete(
schema::accounts::table.filter(schema::accounts::transaction_id.eq(&tx_id_bytes)),
)
.execute(conn)?;
for account_id_bytes in &affected_account_ids {
let remaining: i64 = schema::accounts::table
.filter(schema::accounts::account_id.eq(account_id_bytes))
.count()
.get_result(conn)?;
if remaining == 0 {
let account_id = conversions::network_account_id_from_bytes(account_id_bytes)?;
reverted_accounts.push(account_id);
}
}
diesel::delete(schema::notes::table.filter(schema::notes::created_by.eq(&tx_id_bytes)))
.execute(conn)?;
diesel::update(schema::notes::table.filter(schema::notes::consumed_by.eq(&tx_id_bytes)))
.set(schema::notes::consumed_by.eq(None::<Vec<u8>>))
.execute(conn)?;
}
Ok(reverted_accounts)
}