Antoine Poinsot caaca1fd1a
descriptors: rename derive into derive_received
We'll be able to derive change addresses too
2022-10-24 15:00:12 +02:00

132 lines
4.0 KiB
Rust

use crate::database::sqlite::{schema::SCHEMA, FreshDbOptions, SqliteDbError, DB_VERSION};
use std::{convert::TryInto, fs, path, time};
use miniscript::bitcoin::secp256k1;
pub const LOOK_AHEAD_LIMIT: u32 = 200;
/// Perform a set of modifications to the database inside a single transaction
pub fn db_exec<F>(conn: &mut rusqlite::Connection, modifications: F) -> Result<(), rusqlite::Error>
where
F: FnOnce(&rusqlite::Transaction) -> rusqlite::Result<()>,
{
let tx = conn.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
modifications(&tx)?;
tx.commit()
}
/// Internal helper for queries boilerplate
pub fn db_tx_query<P, F, T>(
tx: &rusqlite::Transaction,
stmt_str: &str,
params: P,
f: F,
) -> Result<Vec<T>, rusqlite::Error>
where
P: IntoIterator + rusqlite::Params,
P::Item: rusqlite::ToSql,
F: FnMut(&rusqlite::Row<'_>) -> rusqlite::Result<T>,
{
tx.prepare(stmt_str)?
.query_map(params, f)?
.collect::<rusqlite::Result<Vec<T>>>()
}
/// Internal helper for queries boilerplate
pub fn db_query<P, F, T>(
conn: &mut rusqlite::Connection,
stmt_str: &str,
params: P,
f: F,
) -> Result<Vec<T>, rusqlite::Error>
where
P: IntoIterator + rusqlite::Params,
P::Item: rusqlite::ToSql,
F: FnMut(&rusqlite::Row<'_>) -> rusqlite::Result<T>,
{
conn.prepare(stmt_str)?
.query_map(params, f)?
.collect::<rusqlite::Result<Vec<T>>>()
}
// Sqlite supports up to i64, thus rusqlite prevents us from inserting u64's.
// We use this to panic rather than inserting a truncated integer into the database (as we'd have
// done by using `n as u32`).
fn timestamp_to_u32(n: u64) -> u32 {
n.try_into()
.expect("Is this the year 2106 yet? Misconfigured system clock.")
}
// Create the db file with RW permissions only for the user
pub fn create_db_file(db_path: &path::Path) -> Result<(), std::io::Error> {
let mut options = fs::OpenOptions::new();
let options = options.read(true).write(true).create_new(true);
#[cfg(unix)]
return {
use std::os::unix::fs::OpenOptionsExt;
options.mode(0o600).open(db_path)?;
Ok(())
};
#[cfg(not(unix))]
return {
// TODO: permissions for Windows...
options.open(db_path)?;
Ok(())
};
}
pub fn create_fresh_db(
db_path: &path::Path,
options: FreshDbOptions,
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
) -> Result<(), SqliteDbError> {
create_db_file(db_path)?;
let timestamp = time::SystemTime::now()
.duration_since(time::UNIX_EPOCH)
.map(|dur| timestamp_to_u32(dur.as_secs()))
.expect("System clock went backward the epoch?");
// Fill the initial addresses. On a fresh database, the deposit_derivation_index is
// necessarily 0.
let mut query = String::with_capacity(100 * LOOK_AHEAD_LIMIT as usize);
for index in 0..LOOK_AHEAD_LIMIT {
// TODO: have this as a helper in descriptors.rs
let address = options
.main_descriptor
.derive_receive(index.into(), secp)
.address(options.bitcoind_network);
query += &format!(
"INSERT INTO addresses (address, derivation_index) VALUES (\"{}\", {});\n",
address, index
);
}
let mut conn = rusqlite::Connection::open(db_path)?;
db_exec(&mut conn, |tx| {
tx.execute_batch(SCHEMA)?;
tx.execute(
"INSERT INTO version (version) VALUES (?1)",
rusqlite::params![DB_VERSION],
)?;
tx.execute(
"INSERT INTO tip (network, blockheight, blockhash) VALUES (?1, NULL, NULL)",
rusqlite::params![options.bitcoind_network.to_string()],
)?;
tx.execute(
"INSERT INTO wallets (timestamp, main_descriptor, deposit_derivation_index) \
VALUES (?1, ?2, ?3)",
rusqlite::params![timestamp, options.main_descriptor.to_string(), 0,],
)?;
tx.execute_batch(&query)?;
Ok(())
})?;
Ok(())
}