zcash_client_sqlite: Support Orchard scanning
This commit is contained in:
parent
50f5df4c1d
commit
1181566401
|
@ -550,6 +550,10 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
for spend in tx.sapling_spends() {
|
||||
wallet::sapling::mark_sapling_note_spent(wdb.conn.0, tx_row, spend.nf())?;
|
||||
}
|
||||
#[cfg(feature = "orchard")]
|
||||
for spend in tx.orchard_spends() {
|
||||
wallet::orchard::mark_orchard_note_spent(wdb.conn.0, tx_row, spend.nf())?;
|
||||
}
|
||||
|
||||
for output in tx.sapling_outputs() {
|
||||
// Check whether this note was spent in a later block range that
|
||||
|
@ -568,6 +572,24 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
|
||||
wallet::sapling::put_received_note(wdb.conn.0, output, tx_row, spent_in)?;
|
||||
}
|
||||
#[cfg(feature = "orchard")]
|
||||
for output in tx.orchard_outputs() {
|
||||
// Check whether this note was spent in a later block range that
|
||||
// we previously scanned.
|
||||
let spent_in = output
|
||||
.nf()
|
||||
.map(|nf| {
|
||||
wallet::query_nullifier_map::<_, Scope>(
|
||||
wdb.conn.0,
|
||||
ShieldedProtocol::Orchard,
|
||||
&nf.to_bytes(),
|
||||
)
|
||||
})
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
wallet::orchard::put_received_note(wdb.conn.0, output, tx_row, spent_in)?;
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the new nullifiers from this block into the nullifier map.
|
||||
|
@ -577,14 +599,37 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
|||
ShieldedProtocol::Sapling,
|
||||
block.sapling().nullifier_map(),
|
||||
)?;
|
||||
#[cfg(feature = "orchard")]
|
||||
wallet::insert_nullifier_map(
|
||||
wdb.conn.0,
|
||||
block.height(),
|
||||
ShieldedProtocol::Orchard,
|
||||
&block
|
||||
.orchard()
|
||||
.nullifier_map()
|
||||
.iter()
|
||||
.map(|(txid, idx, nfs)| {
|
||||
(*txid, *idx, nfs.iter().map(|nf| nf.to_bytes()).collect())
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
|
||||
note_positions.extend(block.transactions().iter().flat_map(|wtx| {
|
||||
wtx.sapling_outputs().iter().map(|out| {
|
||||
let iter = wtx.sapling_outputs().iter().map(|out| {
|
||||
(
|
||||
ShieldedProtocol::Sapling,
|
||||
out.note_commitment_tree_position(),
|
||||
)
|
||||
})
|
||||
});
|
||||
#[cfg(feature = "orchard")]
|
||||
let iter = iter.chain(wtx.orchard_outputs().iter().map(|out| {
|
||||
(
|
||||
ShieldedProtocol::Orchard,
|
||||
out.note_commitment_tree_position(),
|
||||
)
|
||||
}));
|
||||
|
||||
iter
|
||||
}));
|
||||
|
||||
last_scanned_height = Some(block.height());
|
||||
|
|
|
@ -380,7 +380,7 @@ pub(crate) fn add_account<P: consensus::Parameters>(
|
|||
// If a birthday frontier is available, insert it into the note commitment tree. If the
|
||||
// birthday frontier is the empty frontier, we don't need to do anything.
|
||||
if let Some(frontier) = birthday.sapling_frontier().value() {
|
||||
debug!("Inserting frontier into ShardTree: {:?}", frontier);
|
||||
debug!("Inserting Sapling frontier into ShardTree: {:?}", frontier);
|
||||
let shard_store =
|
||||
SqliteShardStore::<_, ::sapling::Node, SAPLING_SHARD_HEIGHT>::from_connection(
|
||||
conn,
|
||||
|
@ -405,6 +405,34 @@ pub(crate) fn add_account<P: consensus::Parameters>(
|
|||
)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "orchard")]
|
||||
if let Some(frontier) = birthday.orchard_frontier().value() {
|
||||
debug!("Inserting Orchard frontier into ShardTree: {:?}", frontier);
|
||||
let shard_store = SqliteShardStore::<
|
||||
_,
|
||||
::orchard::tree::MerkleHashOrchard,
|
||||
ORCHARD_SHARD_HEIGHT,
|
||||
>::from_connection(conn, ORCHARD_TABLES_PREFIX)?;
|
||||
let mut shard_tree: ShardTree<
|
||||
_,
|
||||
{ ::orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
|
||||
ORCHARD_SHARD_HEIGHT,
|
||||
> = ShardTree::new(shard_store, PRUNING_DEPTH.try_into().unwrap());
|
||||
shard_tree.insert_frontier_nodes(
|
||||
frontier.clone(),
|
||||
Retention::Checkpoint {
|
||||
// This subtraction is safe, because all leaves in the tree appear in blocks, and
|
||||
// the invariant that birthday.height() always corresponds to the block for which
|
||||
// `frontier` is the tree state at the start of the block. Together, this means
|
||||
// there exists a prior block for which frontier is the tree state at the end of
|
||||
// the block.
|
||||
id: birthday.height() - 1,
|
||||
is_marked: false,
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// The ignored range always starts at Sapling activation
|
||||
let sapling_activation_height = params
|
||||
.activation_height(NetworkUpgrade::Sapling)
|
||||
.expect("Sapling activation height must be available.");
|
||||
|
@ -2050,13 +2078,20 @@ pub(crate) fn update_expired_notes(
|
|||
conn: &rusqlite::Connection,
|
||||
expiry_height: BlockHeight,
|
||||
) -> Result<(), SqliteClientError> {
|
||||
let mut stmt_update_expired = conn.prepare_cached(
|
||||
let mut stmt_update_sapling_expired = conn.prepare_cached(
|
||||
"UPDATE sapling_received_notes SET spent = NULL WHERE EXISTS (
|
||||
SELECT id_tx FROM transactions
|
||||
WHERE id_tx = sapling_received_notes.spent AND block IS NULL AND expiry_height < ?
|
||||
)",
|
||||
)?;
|
||||
stmt_update_expired.execute([u32::from(expiry_height)])?;
|
||||
stmt_update_sapling_expired.execute([u32::from(expiry_height)])?;
|
||||
let mut stmt_update_orchard_expired = conn.prepare_cached(
|
||||
"UPDATE orchard_received_notes SET spent = NULL WHERE EXISTS (
|
||||
SELECT id_tx FROM transactions
|
||||
WHERE id_tx = orchard_received_notes.spent AND block IS NULL AND expiry_height < ?
|
||||
)",
|
||||
)?;
|
||||
stmt_update_orchard_expired.execute([u32::from(expiry_height)])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use incrementalmerkletree::Position;
|
||||
use rusqlite::{named_params, Connection};
|
||||
use rusqlite::{named_params, params, Connection};
|
||||
|
||||
use zcash_client_backend::{
|
||||
data_api::NullifierQuery, wallet::WalletOrchardOutput, DecryptedOutput, TransferType,
|
||||
|
@ -190,6 +190,26 @@ pub(crate) fn get_orchard_nullifiers(
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
/// Marks a given nullifier as having been revealed in the construction
|
||||
/// of the specified transaction.
|
||||
///
|
||||
/// Marking a note spent in this fashion does NOT imply that the
|
||||
/// spending transaction has been mined.
|
||||
pub(crate) fn mark_orchard_note_spent(
|
||||
conn: &Connection,
|
||||
tx_ref: i64,
|
||||
nf: &orchard::note::Nullifier,
|
||||
) -> Result<bool, SqliteClientError> {
|
||||
let mut stmt_mark_orchard_note_spent =
|
||||
conn.prepare_cached("UPDATE orchard_received_notes SET spent = ? WHERE nf = ?")?;
|
||||
|
||||
match stmt_mark_orchard_note_spent.execute(params![tx_ref, nf.to_bytes()])? {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
_ => unreachable!("nf column is marked as UNIQUE"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use incrementalmerkletree::{Hashable, Level};
|
||||
|
|
Loading…
Reference in New Issue