zcash-sync/src/db.rs

312 lines
12 KiB
Rust
Raw Normal View History

2021-06-24 05:08:20 -07:00
use rusqlite::{Connection, params, OptionalExtension, NO_PARAMS};
2021-06-21 17:33:13 -07:00
use crate::{Witness, CTree};
2021-06-24 05:08:20 -07:00
use zcash_primitives::sapling::{Note, Diversifier, Rseed, Node};
use zcash_primitives::zip32::ExtendedFullViewingKey;
use zcash_primitives::merkle_tree::IncrementalWitness;
use std::collections::HashMap;
use crate::chain::Nf;
use zcash_primitives::consensus::{Parameters, NetworkUpgrade};
2021-06-21 17:33:13 -07:00
pub struct DbAdapter {
connection: Connection,
}
pub struct ReceivedNote {
pub height: u32,
pub output_index: u32,
pub diversifier: Vec<u8>,
pub value: u64,
pub rcm: Vec<u8>,
pub nf: Vec<u8>,
2021-06-24 05:08:20 -07:00
pub spent: Option<u32>,
}
pub struct SpendableNote {
pub note: Note,
pub diversifier: Diversifier,
pub witness: IncrementalWitness<Node>,
2021-06-21 17:33:13 -07:00
}
impl DbAdapter {
pub fn new(db_path: &str) -> anyhow::Result<DbAdapter> {
let connection = Connection::open(db_path)?;
Ok(DbAdapter {
connection,
})
}
pub fn init_db(&self) -> anyhow::Result<()> {
2021-06-24 05:08:20 -07:00
self.connection.execute("CREATE TABLE IF NOT EXISTS accounts (
id_account INTEGER PRIMARY KEY,
sk TEXT NOT NULL UNIQUE,
ivk TEXT NOT NULL,
address TEXT NOT NULL)", NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self.connection.execute("CREATE TABLE IF NOT EXISTS blocks (
height INTEGER PRIMARY KEY,
hash BLOB NOT NULL,
2021-06-24 05:08:20 -07:00
sapling_tree BLOB NOT NULL)", NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self.connection.execute("CREATE TABLE IF NOT EXISTS transactions (
id_tx INTEGER PRIMARY KEY,
txid BLOB NOT NULL UNIQUE,
height INTEGER,
2021-06-24 05:08:20 -07:00
tx_index INTEGER)", NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self.connection.execute("CREATE TABLE IF NOT EXISTS received_notes (
id_note INTEGER PRIMARY KEY,
position INTEGER NOT NULL,
tx INTEGER NOT NULL,
height INTEGER NOT NULL,
output_index INTEGER NOT NULL,
diversifier BLOB NOT NULL,
value INTEGER NOT NULL,
rcm BLOB NOT NULL,
nf BLOB NOT NULL UNIQUE,
spent INTEGER,
2021-06-24 05:08:20 -07:00
CONSTRAINT tx_output UNIQUE (tx, output_index))", NO_PARAMS)?;
2021-06-21 17:33:13 -07:00
self.connection.execute("CREATE TABLE IF NOT EXISTS sapling_witnesses (
id_witness INTEGER PRIMARY KEY,
note INTEGER NOT NULL,
height INTEGER NOT NULL,
witness BLOB NOT NULL,
2021-06-24 05:08:20 -07:00
CONSTRAINT witness_height UNIQUE (note, height))", NO_PARAMS)?;
Ok(())
}
2021-06-21 17:33:13 -07:00
2021-06-24 05:08:20 -07:00
pub fn store_account(&self, sk: &str, ivk: &str, address: &str) -> anyhow::Result<()> {
self.connection.execute("INSERT INTO accounts(sk, ivk, address) VALUES (?1, ?2, ?3)
ON CONFLICT DO NOTHING", params![sk, ivk, address])?;
2021-06-21 17:33:13 -07:00
Ok(())
}
pub fn trim_to_height(&mut self, height: u32) -> anyhow::Result<()> {
let tx = self.connection.transaction()?;
tx.execute("DELETE FROM blocks WHERE height >= ?1", params![height])?;
tx.execute("DELETE FROM sapling_witnesses WHERE height >= ?1", params![height])?;
tx.execute("DELETE FROM received_notes WHERE height >= ?1", params![height])?;
tx.execute("DELETE FROM transactions WHERE height >= ?1", params![height])?;
tx.commit()?;
Ok(())
}
pub fn store_block(&self, height: u32, hash: &[u8], tree: &CTree) -> anyhow::Result<()> {
2021-06-24 05:08:20 -07:00
log::info!("+block");
2021-06-21 17:33:13 -07:00
let mut bb: Vec<u8> = vec![];
tree.write(&mut bb)?;
self.connection.execute("INSERT INTO blocks(height, hash, sapling_tree)
VALUES (?1, ?2, ?3)
ON CONFLICT DO NOTHING", params![height, hash, &bb])?;
2021-06-24 05:08:20 -07:00
log::info!("-block");
2021-06-21 17:33:13 -07:00
Ok(())
}
pub fn store_transaction(&self, txid: &[u8], height: u32, tx_index: u32) -> anyhow::Result<u32> {
2021-06-24 05:08:20 -07:00
log::info!("+transaction");
2021-06-21 17:33:13 -07:00
self.connection.execute("INSERT INTO transactions(txid, height, tx_index)
VALUES (?1, ?2, ?3)
ON CONFLICT DO NOTHING", params![txid, height, tx_index])?;
let id_tx: u32 = self.connection.query_row("SELECT id_tx FROM transactions WHERE txid = ?1", params![txid], |row| row.get(0))?;
2021-06-24 05:08:20 -07:00
log::info!("-transaction {}", id_tx);
2021-06-21 17:33:13 -07:00
Ok(id_tx)
}
pub fn store_received_note(&self, note: &ReceivedNote, id_tx: u32, position: usize) -> anyhow::Result<u32> {
2021-06-24 05:08:20 -07:00
log::info!("+received_note {}", id_tx);
self.connection.execute("INSERT INTO received_notes(tx, height, position, output_index, diversifier, value, rcm, nf, spent)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
ON CONFLICT DO NOTHING", params![id_tx, note.height, position as u32, note.output_index, note.diversifier, note.value as i64, note.rcm, note.nf, note.spent])?;
2021-06-21 17:33:13 -07:00
let id_note: u32 = self.connection.query_row("SELECT id_note FROM received_notes WHERE tx = ?1 AND output_index = ?2", params![id_tx, note.output_index], |row| row.get(0))?;
2021-06-24 05:08:20 -07:00
log::info!("-received_note");
2021-06-21 17:33:13 -07:00
Ok(id_note)
}
pub fn store_witnesses(&self, witness: &Witness, height: u32, id_note: u32) -> anyhow::Result<()> {
2021-06-24 05:08:20 -07:00
log::info!("+witnesses");
2021-06-21 17:33:13 -07:00
let mut bb: Vec<u8> = vec![];
witness.write(&mut bb)?;
self.connection.execute("INSERT INTO sapling_witnesses(note, height, witness) VALUES (?1, ?2, ?3)
ON CONFLICT DO NOTHING", params![id_note, height, bb])?;
2021-06-24 05:08:20 -07:00
log::info!("-witnesses");
2021-06-21 17:33:13 -07:00
Ok(())
}
pub fn get_balance(&self) -> anyhow::Result<u64> {
2021-06-24 05:08:20 -07:00
let balance: Option<i64> = self.connection.query_row("SELECT SUM(value) FROM received_notes WHERE spent IS NULL", NO_PARAMS, |row| row.get(0))?;
Ok(balance.unwrap_or(0) as u64)
2021-06-21 17:33:13 -07:00
}
2021-06-24 05:08:20 -07:00
pub fn get_last_sync_height(&self) -> anyhow::Result<Option<u32>> {
let height: Option<u32> = self.connection.query_row("SELECT MAX(height) FROM blocks", NO_PARAMS, |row| row.get(0))?;
Ok(height)
}
pub fn get_db_height(&self) -> anyhow::Result<u32> {
let height: u32 = self.get_last_sync_height()?.unwrap_or_else(|| {
crate::NETWORK
.activation_height(NetworkUpgrade::Sapling)
.unwrap()
.into()
});
2021-06-21 17:33:13 -07:00
Ok(height)
}
pub fn get_tree(&self) -> anyhow::Result<(CTree, Vec<Witness>)> {
let res = self.connection.query_row(
"SELECT height, sapling_tree FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)",
2021-06-24 05:08:20 -07:00
NO_PARAMS, |row| {
2021-06-21 17:33:13 -07:00
let height: u32 = row.get(0)?;
let tree: Vec<u8> = row.get(1)?;
Ok((height, tree))
}).optional()?;
Ok(match res {
Some((height, tree)) => {
let tree = CTree::read(&*tree)?;
let mut statement = self.connection.prepare(
"SELECT id_note, position, witness FROM sapling_witnesses w, received_notes n WHERE w.height = ?1 AND w.note = n.id_note")?;
let ws = statement.query_map(params![height], |row| {
let id_note: u32 = row.get(0)?;
let position: u32 = row.get(1)?;
let witness: Vec<u8> = row.get(2)?;
Ok(Witness::read(position as usize, id_note, &*witness).unwrap())
})?;
let mut witnesses: Vec<Witness> = vec![];
for w in ws {
witnesses.push(w?);
}
(tree, witnesses)
},
None => (CTree::new(), vec![])
})
}
2021-06-24 05:08:20 -07:00
pub fn get_nullifiers(&self) -> anyhow::Result<HashMap<Nf, u32>> {
let mut statement = self.connection.prepare(
"SELECT id_note, nf FROM received_notes WHERE spent = 0")?;
let nfs_res = statement.query_map(NO_PARAMS, |row| {
let id_note: u32 = row.get(0)?;
let nf_vec: Vec<u8> = row.get(1)?;
let mut nf = [0u8; 32];
nf.clone_from_slice(&nf_vec);
Ok((id_note, nf))
})?;
let mut nfs: HashMap<Nf, u32> = HashMap::new();
for n in nfs_res {
let n = n?;
nfs.insert(Nf(n.1), n.0);
}
Ok(nfs)
}
pub fn get_spendable_notes(&self, anchor_height: u32, fvk: &ExtendedFullViewingKey) -> anyhow::Result<Vec<SpendableNote>> {
let mut statement = self.connection.prepare(
"SELECT diversifier, value, rcm, witness FROM received_notes r, sapling_witnesses w WHERE spent IS NULL
AND w.height = (
SELECT MAX(height) FROM sapling_witnesses WHERE height <= ?1
) AND r.id_note = w.note")?;
let notes = statement.query_map(params![anchor_height], |row| {
let diversifier: Vec<u8> = row.get(0)?;
let value: i64 = row.get(1)?;
let rcm: Vec<u8> = row.get(2)?;
let witness: Vec<u8> = row.get(3)?;
let mut diversifer_bytes = [0u8; 11];
diversifer_bytes.copy_from_slice(&diversifier);
let diversifier = Diversifier(diversifer_bytes);
let mut rcm_bytes = [0u8; 32];
rcm_bytes.copy_from_slice(&rcm);
let rcm = jubjub::Fr::from_bytes(&rcm_bytes).unwrap();
let rseed = Rseed::BeforeZip212(rcm);
let witness = IncrementalWitness::<Node>::read(&*witness).unwrap();
let pa = fvk.fvk.vk.to_payment_address(diversifier).unwrap();
let note = pa.create_note(value as u64, rseed).unwrap();
Ok(SpendableNote {
note,
diversifier,
witness
})
})?;
let mut spendable_notes: Vec<SpendableNote> = vec![];
for n in notes {
spendable_notes.push(n?);
}
Ok(spendable_notes)
}
pub fn mark_spent(&self, id: u32, height: u32) -> anyhow::Result<()> {
log::info!("+mark_spent");
self.connection.execute("UPDATE received_notes SET spent = ?1 WHERE id_note = ?2", params![height, id])?;
log::info!("-mark_spent");
Ok(())
}
pub fn get_sk(&self, account: u32) -> anyhow::Result<String> {
log::info!("+get_sk");
let ivk = self.connection.query_row("SELECT sk FROM accounts WHERE id_account = ?1", params![account], |row | {
let ivk: String = row.get(0)?;
Ok(ivk)
})?;
log::info!("-get_sk");
Ok(ivk)
}
pub fn get_ivk(&self, account: u32) -> anyhow::Result<String> {
log::info!("+get_ivk");
let ivk = self.connection.query_row("SELECT ivk FROM accounts WHERE id_account = ?1", params![account], |row | {
let ivk: String = row.get(0)?;
Ok(ivk)
})?;
log::info!("-get_ivk");
Ok(ivk)
}
2021-06-21 17:33:13 -07:00
}
#[cfg(test)]
mod tests {
use crate::db::{DbAdapter, ReceivedNote};
use crate::{Witness, CTree};
const DB_PATH: &str = "zec.db";
#[test]
fn test_db() {
let mut db = DbAdapter::new(DB_PATH).unwrap();
db.init_db().unwrap();
db.trim_to_height(0).unwrap();
db.store_block(1, &[0u8; 32], &CTree::new()).unwrap();
let id_tx = db.store_transaction(&[0; 32], 1, 20).unwrap();
db.store_received_note(&ReceivedNote {
height: 1,
output_index: 0,
diversifier: vec![],
value: 0,
rcm: vec![],
nf: vec![],
2021-06-24 05:08:20 -07:00
spent: None
2021-06-21 17:33:13 -07:00
}, id_tx, 5).unwrap();
let witness = Witness {
position: 10,
id_note: 0,
note: None,
tree: CTree::new(),
filled: vec![],
cursor: CTree::new(),
};
db.store_witnesses(&witness, 1000, 1).unwrap();
}
#[test]
fn test_balance() {
let db = DbAdapter::new(DB_PATH).unwrap();
let balance = db.get_balance().unwrap();
println!("{}", balance);
}
}