718 lines
24 KiB
Rust
718 lines
24 KiB
Rust
use crate::db::data_generated::fb::*;
|
|
use crate::unified::orchard_as_unified;
|
|
use anyhow::Result;
|
|
use orchard::keys::{FullViewingKey, Scope};
|
|
use rusqlite::{params, Connection, OptionalExtension};
|
|
use std::collections::HashMap;
|
|
use zcash_client_backend::address::RecipientAddress;
|
|
use zcash_client_backend::encoding::AddressCodec;
|
|
use zcash_primitives::consensus::Network;
|
|
|
|
pub fn get_account_list(connection: &Connection) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection.prepare("WITH notes AS (SELECT a.id_account, a.name, CASE WHEN r.spent IS NULL THEN r.value ELSE 0 END AS nv FROM accounts a LEFT JOIN received_notes r ON a.id_account = r.account), \
|
|
accounts2 AS (SELECT id_account, name, COALESCE(sum(nv), 0) AS balance FROM notes GROUP by id_account) \
|
|
SELECT a.id_account, a.name, a.balance FROM accounts2 a")?;
|
|
let rows = stmt.query_map([], |row| {
|
|
let id: u32 = row.get("id_account")?;
|
|
let name: String = row.get("name")?;
|
|
let balance: i64 = row.get("balance")?;
|
|
let name = builder.create_string(&name);
|
|
let account = Account::create(
|
|
&mut builder,
|
|
&AccountArgs {
|
|
id,
|
|
name: Some(name),
|
|
balance: balance as u64,
|
|
},
|
|
);
|
|
Ok(account)
|
|
})?;
|
|
let mut accounts = vec![];
|
|
for r in rows {
|
|
accounts.push(r?);
|
|
}
|
|
let accounts = builder.create_vector(&accounts);
|
|
let accounts = AccountVec::create(
|
|
&mut builder,
|
|
&AccountVecArgs {
|
|
accounts: Some(accounts),
|
|
},
|
|
);
|
|
builder.finish(accounts, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_active_account(connection: &Connection) -> Result<u32> {
|
|
let id = connection
|
|
.query_row(
|
|
"SELECT value FROM properties WHERE name = 'account'",
|
|
[],
|
|
|row| {
|
|
let value: String = row.get(0)?;
|
|
let value: u32 = value.parse().unwrap();
|
|
Ok(value)
|
|
},
|
|
)
|
|
.optional()?
|
|
.unwrap_or(0);
|
|
let id = get_available_account_id(connection, id)?;
|
|
set_active_account(connection, id)?;
|
|
Ok(id)
|
|
}
|
|
|
|
pub fn set_active_account(connection: &Connection, id: u32) -> Result<()> {
|
|
connection.execute(
|
|
"INSERT INTO properties(name, value) VALUES ('account',?1) \
|
|
ON CONFLICT (name) DO UPDATE SET value = excluded.value",
|
|
[id],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_available_account_id(connection: &Connection, id: u32) -> Result<u32> {
|
|
let r = connection
|
|
.query_row("SELECT 1 FROM accounts WHERE id_account = ?1", [id], |_| {
|
|
Ok(())
|
|
})
|
|
.optional()?;
|
|
if r.is_some() {
|
|
return Ok(id);
|
|
}
|
|
let r = connection
|
|
.query_row("SELECT MAX(id_account) FROM accounts", [], |row| {
|
|
let id: Option<u32> = row.get(0)?;
|
|
Ok(id)
|
|
})?
|
|
.unwrap_or(0);
|
|
Ok(r)
|
|
}
|
|
|
|
pub fn get_t_addr(connection: &Connection, id: u32) -> Result<String> {
|
|
let address = connection
|
|
.query_row(
|
|
"SELECT address FROM taddrs WHERE account = ?1",
|
|
[id],
|
|
|row| {
|
|
let address: String = row.get(0)?;
|
|
Ok(address)
|
|
},
|
|
)
|
|
.optional()?;
|
|
Ok(address.unwrap_or(String::new()))
|
|
}
|
|
|
|
pub fn get_sk(connection: &Connection, id: u32) -> Result<String> {
|
|
let sk = connection.query_row(
|
|
"SELECT sk FROM accounts WHERE id_account = ?1",
|
|
[id],
|
|
|row| {
|
|
let sk: Option<String> = row.get(0)?;
|
|
Ok(sk.unwrap_or(String::new()))
|
|
},
|
|
)?;
|
|
Ok(sk)
|
|
}
|
|
|
|
pub fn update_account_name(connection: &Connection, id: u32, name: &str) -> Result<()> {
|
|
connection.execute(
|
|
"UPDATE accounts SET name = ?2 WHERE id_account = ?1",
|
|
params![id, name],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_balances(connection: &Connection, id: u32, confirmed_height: u32) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let shielded = connection.query_row(
|
|
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL",
|
|
params![id],
|
|
|row| {
|
|
let value: Option<i64> = row.get(0)?;
|
|
Ok(value.unwrap_or(0) as u64)
|
|
},
|
|
)?; // funds not spent yet
|
|
let unconfirmed_spent = connection.query_row(
|
|
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent = 0",
|
|
params![id],
|
|
|row| {
|
|
let value: Option<i64> = row.get(0)?;
|
|
Ok(value.unwrap_or(0) as u64)
|
|
},
|
|
)?; // funds used in unconfirmed tx
|
|
let balance = shielded + unconfirmed_spent;
|
|
let under_confirmed = connection.query_row(
|
|
"SELECT SUM(value) AS value FROM received_notes WHERE account = ?1 AND spent IS NULL AND height > ?2",
|
|
params![id, confirmed_height], |row| {
|
|
let value: Option<i64> = row.get(0)?;
|
|
Ok(value.unwrap_or(0) as u64)
|
|
})?; // funds received but not old enough
|
|
let excluded = connection.query_row(
|
|
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL \
|
|
AND height <= ?2 AND excluded",
|
|
params![id, confirmed_height],
|
|
|row| {
|
|
let value: Option<i64> = row.get(0)?;
|
|
Ok(value.unwrap_or(0) as u64)
|
|
},
|
|
)?; // funds excluded from spending
|
|
let sapling = connection.query_row(
|
|
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL AND orchard = 0 AND height <= ?2",
|
|
params![id, confirmed_height], |row| {
|
|
let value: Option<i64> = row.get(0)?;
|
|
Ok(value.unwrap_or(0) as u64)
|
|
})?;
|
|
let orchard = connection.query_row(
|
|
"SELECT SUM(value) FROM received_notes WHERE account = ?1 AND spent IS NULL AND orchard = 1 AND height <= ?2",
|
|
params![id, confirmed_height], |row| {
|
|
let value: Option<i64> = row.get(0)?;
|
|
Ok(value.unwrap_or(0) as u64)
|
|
})?;
|
|
|
|
let balance = Balance::create(
|
|
&mut builder,
|
|
&BalanceArgs {
|
|
shielded,
|
|
unconfirmed_spent,
|
|
balance,
|
|
under_confirmed,
|
|
excluded,
|
|
sapling,
|
|
orchard,
|
|
},
|
|
);
|
|
builder.finish(balance, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_db_height(connection: &Connection) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let height = connection
|
|
.query_row(
|
|
"SELECT height, timestamp FROM blocks WHERE height = (SELECT MAX(height) FROM blocks)",
|
|
[],
|
|
|row| {
|
|
let height: u32 = row.get(0)?;
|
|
let timestamp: u32 = row.get(1)?;
|
|
let height = Height::create(&mut builder, &HeightArgs { height, timestamp });
|
|
Ok(height)
|
|
},
|
|
)
|
|
.optional()?;
|
|
let data = height
|
|
.map(|h| {
|
|
builder.finish(h, None);
|
|
builder.finished_data().to_vec()
|
|
})
|
|
.unwrap_or(vec![]);
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_notes(connection: &Connection, id: u32) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection.prepare(
|
|
"SELECT n.id_note, n.height, n.value, t.timestamp, n.orchard, n.excluded, n.spent FROM received_notes n, transactions t \
|
|
WHERE n.account = ?1 AND (n.spent IS NULL OR n.spent = 0) \
|
|
AND n.tx = t.id_tx ORDER BY n.height DESC")?;
|
|
let rows = stmt.query_map(params![id], |row| {
|
|
let id: u32 = row.get("id_note")?;
|
|
let height: u32 = row.get("height")?;
|
|
let value: i64 = row.get("value")?;
|
|
let timestamp: u32 = row.get("timestamp")?;
|
|
let orchard: u8 = row.get("orchard")?;
|
|
let excluded: Option<bool> = row.get("excluded")?;
|
|
let spent: Option<u32> = row.get("spent")?;
|
|
let note = ShieldedNote::create(
|
|
&mut builder,
|
|
&ShieldedNoteArgs {
|
|
id,
|
|
height,
|
|
value: value as u64,
|
|
timestamp,
|
|
orchard: orchard == 1,
|
|
excluded: excluded.unwrap_or(false),
|
|
spent: spent.is_some(),
|
|
},
|
|
);
|
|
Ok(note)
|
|
})?;
|
|
let mut notes = vec![];
|
|
for r in rows {
|
|
notes.push(r?);
|
|
}
|
|
let notes = builder.create_vector(¬es);
|
|
let notes = ShieldedNoteVec::create(&mut builder, &ShieldedNoteVecArgs { notes: Some(notes) });
|
|
builder.finish(notes, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_txs(network: &Network, connection: &Connection, id: u32) -> Result<ShieldedTxVecT> {
|
|
let addresses = resolve_addresses(network, connection)?;
|
|
let mut stmt = connection.prepare(
|
|
"SELECT id_tx, txid, height, timestamp, t.address, value, memo FROM transactions t \
|
|
WHERE account = ?1 ORDER BY height DESC",
|
|
)?;
|
|
let rows = stmt.query_map(params![id], |row| {
|
|
let id_tx: u32 = row.get("id_tx")?;
|
|
let height: u32 = row.get("height")?;
|
|
let mut tx_id: Vec<u8> = row.get("txid")?;
|
|
tx_id.reverse();
|
|
let tx_id = hex::encode(&tx_id);
|
|
let short_tx_id = tx_id[..8].to_string();
|
|
let timestamp: u32 = row.get("timestamp")?;
|
|
let address: Option<String> = row.get("address")?;
|
|
let value: i64 = row.get("value")?;
|
|
let memo: Option<String> = row.get("memo")?;
|
|
let name = address.as_ref().and_then(|a| addresses.get(a)).cloned();
|
|
let tx = ShieldedTxT {
|
|
id: id_tx,
|
|
height,
|
|
tx_id: Some(tx_id),
|
|
short_tx_id: Some(short_tx_id),
|
|
timestamp,
|
|
name,
|
|
value: value as u64,
|
|
address,
|
|
memo,
|
|
};
|
|
Ok(tx)
|
|
})?;
|
|
let mut txs = vec![];
|
|
for r in rows {
|
|
txs.push(r?);
|
|
}
|
|
let txs = ShieldedTxVecT { txs: Some(txs) };
|
|
Ok(txs)
|
|
}
|
|
|
|
pub fn get_messages(network: &Network, connection: &Connection, id: u32) -> Result<MessageVecT> {
|
|
let addresses = resolve_addresses(network, connection)?;
|
|
|
|
let mut stmt = connection.prepare(
|
|
"SELECT m.id, m.id_tx, m.timestamp, m.sender, m.recipient, m.incoming, \
|
|
subject, body, height, read FROM messages m \
|
|
WHERE account = ?1 ORDER BY timestamp DESC",
|
|
)?;
|
|
let rows = stmt.query_map(params![id], |row| {
|
|
let id_msg: u32 = row.get("id")?;
|
|
let id_tx: Option<u32> = row.get("id_tx")?;
|
|
let timestamp: u32 = row.get("timestamp")?;
|
|
let height: u32 = row.get("height")?;
|
|
let sender: Option<String> = row.get("sender")?;
|
|
let recipient: Option<String> = row.get("recipient")?;
|
|
let subject: String = row.get("subject")?;
|
|
let body: String = row.get("body")?;
|
|
let read: bool = row.get("read")?;
|
|
let incoming: bool = row.get("incoming")?;
|
|
|
|
let id_tx = id_tx.unwrap_or(0);
|
|
let from = sender.map(|s| addresses.get(&s).cloned().unwrap_or(String::new()));
|
|
let to = recipient.map(|s| addresses.get(&s).cloned().unwrap_or(String::new()));
|
|
|
|
let message = MessageT {
|
|
id_msg,
|
|
id_tx,
|
|
height,
|
|
timestamp,
|
|
from,
|
|
to,
|
|
subject: Some(subject),
|
|
body: Some(body),
|
|
read,
|
|
incoming,
|
|
};
|
|
Ok(message)
|
|
})?;
|
|
let mut messages = vec![];
|
|
for r in rows {
|
|
messages.push(r?);
|
|
}
|
|
let messages = MessageVecT {
|
|
messages: Some(messages),
|
|
};
|
|
Ok(messages)
|
|
}
|
|
|
|
pub fn get_prev_next_message(
|
|
connection: &Connection,
|
|
subject: &str,
|
|
height: u32,
|
|
account: u32,
|
|
) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let prev = connection
|
|
.query_row(
|
|
"SELECT MAX(id) FROM messages WHERE subject = ?1 AND height < ?2 and account = ?3",
|
|
params![subject, height, account],
|
|
|row| {
|
|
let id: Option<u32> = row.get(0)?;
|
|
Ok(id)
|
|
},
|
|
)?
|
|
.unwrap_or(0);
|
|
let next = connection
|
|
.query_row(
|
|
"SELECT MIN(id) FROM messages WHERE subject = ?1 AND height > ?2 and account = ?3",
|
|
params![subject, height, account],
|
|
|row| {
|
|
let id: Option<u32> = row.get(0)?;
|
|
Ok(id)
|
|
},
|
|
)?
|
|
.unwrap_or(0);
|
|
let prev_next = PrevNext::create(&mut builder, &PrevNextArgs { prev, next });
|
|
builder.finish(prev_next, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_templates(connection: &Connection) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection.prepare(
|
|
"SELECT id_send_template, title, address, amount, fiat_amount, fee_included, fiat, include_reply_to, subject, body FROM send_templates")?;
|
|
let rows = stmt.query_map([], |row| {
|
|
let id_msg: u32 = row.get("id_send_template")?;
|
|
let title: String = row.get("title")?;
|
|
let address: String = row.get("address")?;
|
|
let amount: i64 = row.get("amount")?;
|
|
let fiat_amount: f64 = row.get("fiat_amount")?;
|
|
let fee_included: bool = row.get("fee_included")?;
|
|
let fiat: Option<String> = row.get("fiat")?;
|
|
let include_reply_to: bool = row.get("include_reply_to")?;
|
|
let subject: String = row.get("subject")?;
|
|
let body: String = row.get("body")?;
|
|
|
|
let title = builder.create_string(&title);
|
|
let address = builder.create_string(&address);
|
|
let fiat = fiat.map(|fiat| builder.create_string(&fiat));
|
|
let subject = builder.create_string(&subject);
|
|
let body = builder.create_string(&body);
|
|
|
|
let template = SendTemplate::create(
|
|
&mut builder,
|
|
&SendTemplateArgs {
|
|
id: id_msg,
|
|
title: Some(title),
|
|
address: Some(address),
|
|
amount: amount as u64,
|
|
fiat_amount,
|
|
fee_included,
|
|
fiat,
|
|
include_reply_to,
|
|
subject: Some(subject),
|
|
body: Some(body),
|
|
},
|
|
);
|
|
Ok(template)
|
|
})?;
|
|
let mut templates = vec![];
|
|
for r in rows {
|
|
templates.push(r?);
|
|
}
|
|
let templates = builder.create_vector(&templates);
|
|
let templates = SendTemplateVec::create(
|
|
&mut builder,
|
|
&SendTemplateVecArgs {
|
|
templates: Some(templates),
|
|
},
|
|
);
|
|
builder.finish(templates, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_contacts(connection: &Connection) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection
|
|
.prepare("SELECT id, name, address FROM contacts WHERE address <> '' ORDER BY name")?;
|
|
let rows = stmt.query_map([], |row| {
|
|
let id: u32 = row.get("id")?;
|
|
let name: String = row.get("name")?;
|
|
let address: String = row.get("address")?;
|
|
let name = builder.create_string(&name);
|
|
let address = builder.create_string(&address);
|
|
let contact = Contact::create(
|
|
&mut builder,
|
|
&ContactArgs {
|
|
id,
|
|
name: Some(name),
|
|
address: Some(address),
|
|
},
|
|
);
|
|
Ok(contact)
|
|
})?;
|
|
let mut contacts = vec![];
|
|
for r in rows {
|
|
contacts.push(r?);
|
|
}
|
|
let contacts = builder.create_vector(&contacts);
|
|
let contacts = ContactVec::create(
|
|
&mut builder,
|
|
&ContactVecArgs {
|
|
contacts: Some(contacts),
|
|
},
|
|
);
|
|
builder.finish(contacts, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_pnl_txs(connection: &Connection, id: u32, timestamp: u32) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection.prepare(
|
|
"SELECT timestamp, value FROM transactions WHERE timestamp >= ?2 AND account = ?1 ORDER BY timestamp DESC")?;
|
|
let rows = stmt.query_map([id, timestamp], |row| {
|
|
let timestamp: u32 = row.get(0)?;
|
|
let value: i64 = row.get(1)?;
|
|
let tx = TxTimeValue::create(
|
|
&mut builder,
|
|
&TxTimeValueArgs {
|
|
timestamp,
|
|
value: value as u64,
|
|
},
|
|
);
|
|
Ok(tx)
|
|
})?;
|
|
let mut txs = vec![];
|
|
for r in rows {
|
|
txs.push(r?);
|
|
}
|
|
let txs = builder.create_vector(&txs);
|
|
let txs = TxTimeValueVec::create(&mut builder, &TxTimeValueVecArgs { values: Some(txs) });
|
|
builder.finish(txs, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_historical_prices(
|
|
connection: &Connection,
|
|
timestamp: u32,
|
|
currency: &str,
|
|
) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection.prepare(
|
|
"SELECT timestamp, price FROM historical_prices WHERE timestamp >= ?2 AND currency = ?1",
|
|
)?;
|
|
let rows = stmt.query_map(params![currency, timestamp], |row| {
|
|
let timestamp: u32 = row.get(0)?;
|
|
let price: f64 = row.get(1)?;
|
|
let quote = Quote::create(&mut builder, &QuoteArgs { timestamp, price });
|
|
Ok(quote)
|
|
})?;
|
|
let mut quotes = vec![];
|
|
for r in rows {
|
|
quotes.push(r?);
|
|
}
|
|
let quotes = builder.create_vector("es);
|
|
let quotes = QuoteVec::create(
|
|
&mut builder,
|
|
&QuoteVecArgs {
|
|
values: Some(quotes),
|
|
},
|
|
);
|
|
builder.finish(quotes, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn get_spendings(connection: &Connection, id: u32, timestamp: u32) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection.prepare(
|
|
"SELECT SUM(value) as v, t.address, c.name FROM transactions t LEFT JOIN contacts c ON t.address = c.address \
|
|
WHERE account = ?1 AND timestamp >= ?2 AND value < 0 GROUP BY t.address ORDER BY v ASC LIMIT 5")?;
|
|
let rows = stmt.query_map([id, timestamp], |row| {
|
|
let value: i64 = row.get(0)?;
|
|
let address: Option<String> = row.get(1)?;
|
|
let name: Option<String> = row.get(2)?;
|
|
|
|
let recipient = name.or(address);
|
|
let recipient = recipient.unwrap_or(String::new());
|
|
let recipient = builder.create_string(&recipient);
|
|
|
|
let spending = Spending::create(
|
|
&mut builder,
|
|
&SpendingArgs {
|
|
recipient: Some(recipient),
|
|
amount: (-value) as u64,
|
|
},
|
|
);
|
|
Ok(spending)
|
|
})?;
|
|
let mut spendings = vec![];
|
|
for r in rows {
|
|
spendings.push(r?);
|
|
}
|
|
let spendings = builder.create_vector(&spendings);
|
|
let spendings = SpendingVec::create(
|
|
&mut builder,
|
|
&SpendingVecArgs {
|
|
values: Some(spendings),
|
|
},
|
|
);
|
|
builder.finish(spendings, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn update_excluded(connection: &Connection, id: u32, excluded: bool) -> Result<()> {
|
|
connection.execute(
|
|
"UPDATE received_notes SET excluded = ?2 WHERE id_note = ?1",
|
|
params![id, excluded],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn invert_excluded(connection: &Connection, id: u32) -> Result<()> {
|
|
connection.execute(
|
|
"UPDATE received_notes SET excluded = NOT(COALESCE(excluded, 0)) WHERE account = ?1",
|
|
[id],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_checkpoints(connection: &Connection) -> Result<Vec<u8>> {
|
|
let mut builder = flatbuffers::FlatBufferBuilder::new();
|
|
let mut stmt = connection.prepare("SELECT height, timestamp FROM blocks ORDER by height")?;
|
|
let rows = stmt.query_map([], |row| {
|
|
let height: u32 = row.get(0)?;
|
|
let timestamp: u32 = row.get(1)?;
|
|
|
|
let checkpoint = Checkpoint::create(&mut builder, &CheckpointArgs { height, timestamp });
|
|
Ok(checkpoint)
|
|
})?;
|
|
let mut checkpoints = vec![];
|
|
for r in rows {
|
|
checkpoints.push(r?);
|
|
}
|
|
let checkpoints = builder.create_vector(&checkpoints);
|
|
let checkpoints = CheckpointVec::create(
|
|
&mut builder,
|
|
&CheckpointVecArgs {
|
|
values: Some(checkpoints),
|
|
},
|
|
);
|
|
builder.finish(checkpoints, None);
|
|
let data = builder.finished_data().to_vec();
|
|
Ok(data)
|
|
}
|
|
|
|
pub fn resolve_addresses(
|
|
network: &Network,
|
|
connection: &Connection,
|
|
) -> Result<HashMap<String, String>> {
|
|
let mut addresses: HashMap<String, String> = HashMap::new();
|
|
let mut stmt = connection.prepare("SELECT name, address FROM contacts WHERE address <> ''")?;
|
|
let rows = stmt.query_map([], |row| {
|
|
let name: String = row.get(0)?;
|
|
let address: String = row.get(1)?;
|
|
let ra = RecipientAddress::decode(network, &address);
|
|
if let Some(ra) = ra {
|
|
match ra {
|
|
RecipientAddress::Unified(ua) => {
|
|
if let Some(ta) = ua.transparent() {
|
|
addresses.insert(ta.encode(network), name.clone());
|
|
}
|
|
if let Some(pa) = ua.sapling() {
|
|
addresses.insert(pa.encode(network), name.clone());
|
|
}
|
|
if let Some(oa) = ua.orchard() {
|
|
let oa = orchard_as_unified(network, oa);
|
|
addresses.insert(oa.encode(), name.clone());
|
|
}
|
|
}
|
|
_ => {
|
|
addresses.insert(address, name);
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
})?;
|
|
for r in rows {
|
|
r?;
|
|
}
|
|
|
|
let mut stmt = connection.prepare(
|
|
"SELECT a.name, a.address, t.address, o.fvk FROM accounts a LEFT JOIN taddrs t ON a.id_account = t.account \
|
|
LEFT JOIN orchard_addrs o ON a.id_account = o.account",
|
|
)?;
|
|
let rows = stmt.query_map([], |row| {
|
|
let name: String = row.get(0)?;
|
|
let z_addr: String = row.get(1)?;
|
|
let t_addr: Option<String> = row.get(2)?;
|
|
let o_fvk: Option<Vec<u8>> = row.get(3)?;
|
|
addresses.insert(z_addr, name.clone());
|
|
if let Some(t_addr) = t_addr {
|
|
addresses.insert(t_addr, name.clone());
|
|
}
|
|
if let Some(o_fvk) = o_fvk {
|
|
let o_fvk = FullViewingKey::from_bytes(&o_fvk.try_into().unwrap()).unwrap();
|
|
let o_addr = o_fvk.address_at(0usize, Scope::External);
|
|
let o_addr = orchard_as_unified(network, &o_addr);
|
|
addresses.insert(o_addr.encode(), name.clone());
|
|
}
|
|
Ok(())
|
|
})?;
|
|
for r in rows {
|
|
r?;
|
|
}
|
|
|
|
Ok(addresses)
|
|
}
|
|
|
|
pub fn get_property(connection: &Connection, name: &str) -> anyhow::Result<String> {
|
|
let url = connection
|
|
.query_row(
|
|
"SELECT value FROM properties WHERE name = ?1",
|
|
[name],
|
|
|row| {
|
|
let url: String = row.get(0)?;
|
|
Ok(url)
|
|
},
|
|
)
|
|
.optional()?;
|
|
Ok(url.unwrap_or(String::new()))
|
|
}
|
|
|
|
pub fn set_property(connection: &Connection, name: &str, value: &str) -> anyhow::Result<()> {
|
|
connection.execute(
|
|
"INSERT INTO properties(name, value) VALUES (?1, ?2) ON CONFLICT (name) \
|
|
DO UPDATE SET value = excluded.value",
|
|
params![name, value],
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_available_addrs(connection: &Connection, account: u32) -> anyhow::Result<u8> {
|
|
let has_transparent = connection
|
|
.query_row(
|
|
"SELECT 1 FROM taddrs WHERE account = ?1",
|
|
[account],
|
|
|_row| Ok(()),
|
|
)
|
|
.optional()?
|
|
.is_some();
|
|
let has_sapling = true;
|
|
let has_orchard = connection
|
|
.query_row(
|
|
"SELECT 1 FROM orchard_addrs WHERE account = ?1",
|
|
[account],
|
|
|_row| Ok(()),
|
|
)
|
|
.optional()?
|
|
.is_some();
|
|
let res = if has_transparent { 1 } else { 0 }
|
|
| if has_sapling { 2 } else { 0 }
|
|
| if has_orchard { 4 } else { 0 };
|
|
Ok(res)
|
|
}
|
|
|
|
pub fn get_account_by_address(connection: &Connection, address: &str) -> Result<Option<u32>> {
|
|
let id = connection.query_row("SELECT id_account FROM accounts WHERE address = ?1", [address], |row| {
|
|
row.get::<_, u32>(0)
|
|
}).optional()?;
|
|
Ok(id)
|
|
}
|