Initial skeleton of low-level database access API.
This commit is contained in:
parent
7ced7f3c56
commit
a437df191e
|
@ -0,0 +1,82 @@
|
|||
use zcash_primitives::consensus::{self, NetworkUpgrade};
|
||||
|
||||
use crate::data_api::{
|
||||
error::{ChainInvalid, Error},
|
||||
CacheOps, DBOps,
|
||||
};
|
||||
|
||||
pub const ANCHOR_OFFSET: u32 = 10;
|
||||
|
||||
/// Checks that the scanned blocks in the data database, when combined with the recent
|
||||
/// `CompactBlock`s in the cache database, form a valid chain.
|
||||
///
|
||||
/// This function is built on the core assumption that the information provided in the
|
||||
/// cache database is more likely to be accurate than the previously-scanned information.
|
||||
/// This follows from the design (and trust) assumption that the `lightwalletd` server
|
||||
/// provides accurate block information as of the time it was requested.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `Ok(())` if the combined chain is valid.
|
||||
/// - `Err(ErrorKind::InvalidChain(upper_bound, cause))` if the combined chain is invalid.
|
||||
/// `upper_bound` is the height of the highest invalid block (on the assumption that the
|
||||
/// highest block in the cache database is correct).
|
||||
/// - `Err(e)` if there was an error during validation unrelated to chain validity.
|
||||
///
|
||||
/// This function does not mutate either of the databases.
|
||||
pub fn validate_combined_chain<
|
||||
E,
|
||||
P: consensus::Parameters,
|
||||
C: CacheOps<Error = Error<E>>,
|
||||
D: DBOps<Error = Error<E>>,
|
||||
>(
|
||||
parameters: &P,
|
||||
cache: &C,
|
||||
data: &D,
|
||||
) -> Result<(), Error<E>> {
|
||||
let sapling_activation_height = parameters
|
||||
.activation_height(NetworkUpgrade::Sapling)
|
||||
.ok_or(Error::SaplingNotActive)?;
|
||||
|
||||
// Recall where we synced up to previously.
|
||||
// If we have never synced, use Sapling activation height to select all cached CompactBlocks.
|
||||
let data_scan_max_height = data
|
||||
.block_height_extrema()?
|
||||
.map(|(_, max)| max)
|
||||
.unwrap_or(sapling_activation_height - 1);
|
||||
|
||||
// The cache will contain blocks above the maximum height of data in the database;
|
||||
// validate from that maximum height up to the chain tip, returning the
|
||||
// hash of the block at data_scan_max_height
|
||||
let cached_hash_opt = cache.validate_chain(data_scan_max_height, |top_block, next_block| {
|
||||
if next_block.height() != top_block.height() - 1 {
|
||||
Err(ChainInvalid::block_height_mismatch(
|
||||
top_block.height() - 1,
|
||||
next_block.height(),
|
||||
))
|
||||
} else if next_block.hash() != top_block.prev_hash() {
|
||||
Err(ChainInvalid::prev_hash_mismatch(next_block.height()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
})?;
|
||||
|
||||
match (cached_hash_opt, data.get_block_hash(data_scan_max_height)?) {
|
||||
(Some(cached_hash), Some(data_scan_max_hash)) =>
|
||||
// Cached blocks must hash-chain to the last scanned block.
|
||||
{
|
||||
if cached_hash == data_scan_max_hash {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ChainInvalid::prev_hash_mismatch::<E>(data_scan_max_height))
|
||||
}
|
||||
}
|
||||
(Some(_), None) => Err(Error::CorruptedData(
|
||||
"No block hash available at last scanned height.",
|
||||
)),
|
||||
(None, _) =>
|
||||
// No cached blocks are present, this is fine.
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
use std::error;
|
||||
use std::fmt;
|
||||
use zcash_primitives::{
|
||||
consensus::BlockHeight,
|
||||
sapling::Node,
|
||||
transaction::{builder, TxId},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ChainInvalid {
|
||||
PrevHashMismatch,
|
||||
/// (expected_height, actual_height)
|
||||
BlockHeightMismatch(BlockHeight),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error<DbError> {
|
||||
CorruptedData(&'static str),
|
||||
IncorrectHRPExtFVK,
|
||||
InsufficientBalance(u64, u64),
|
||||
InvalidChain(BlockHeight, ChainInvalid),
|
||||
InvalidExtSK(u32),
|
||||
InvalidMemo(std::str::Utf8Error),
|
||||
InvalidNewWitnessAnchor(usize, TxId, BlockHeight, Node),
|
||||
InvalidNote,
|
||||
InvalidWitnessAnchor(i64, BlockHeight),
|
||||
ScanRequired,
|
||||
TableNotEmpty,
|
||||
Bech32(bech32::Error),
|
||||
Base58(bs58::decode::Error),
|
||||
Builder(builder::Error),
|
||||
Database(DbError),
|
||||
Io(std::io::Error),
|
||||
Protobuf(protobuf::ProtobufError),
|
||||
SaplingNotActive,
|
||||
}
|
||||
|
||||
impl ChainInvalid {
|
||||
pub fn prev_hash_mismatch<E>(at_height: BlockHeight) -> Error<E> {
|
||||
Error::InvalidChain(at_height, ChainInvalid::PrevHashMismatch)
|
||||
}
|
||||
|
||||
pub fn block_height_mismatch<E>(at_height: BlockHeight, found: BlockHeight) -> Error<E> {
|
||||
Error::InvalidChain(at_height, ChainInvalid::BlockHeightMismatch(found))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: fmt::Display> fmt::Display for Error<E> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match &self {
|
||||
Error::CorruptedData(reason) => write!(f, "Data DB is corrupted: {}", reason),
|
||||
Error::IncorrectHRPExtFVK => write!(f, "Incorrect HRP for extfvk"),
|
||||
Error::InsufficientBalance(have, need) => write!(
|
||||
f,
|
||||
"Insufficient balance (have {}, need {} including fee)",
|
||||
have, need
|
||||
),
|
||||
Error::InvalidChain(upper_bound, cause) => {
|
||||
write!(f, "Invalid chain (upper bound: {}): {:?}", u32::from(*upper_bound), cause)
|
||||
}
|
||||
Error::InvalidExtSK(account) => {
|
||||
write!(f, "Incorrect ExtendedSpendingKey for account {}", account)
|
||||
}
|
||||
Error::InvalidMemo(e) => write!(f, "{}", e),
|
||||
Error::InvalidNewWitnessAnchor(output, txid, last_height, anchor) => write!(
|
||||
f,
|
||||
"New witness for output {} in tx {} has incorrect anchor after scanning block {}: {:?}",
|
||||
output, txid, last_height, anchor,
|
||||
),
|
||||
Error::InvalidNote => write!(f, "Invalid note"),
|
||||
Error::InvalidWitnessAnchor(id_note, last_height) => write!(
|
||||
f,
|
||||
"Witness for note {} has incorrect anchor after scanning block {}",
|
||||
id_note, last_height
|
||||
),
|
||||
Error::ScanRequired => write!(f, "Must scan blocks first"),
|
||||
Error::TableNotEmpty => write!(f, "Table is not empty"),
|
||||
Error::Bech32(e) => write!(f, "{}", e),
|
||||
Error::Base58(e) => write!(f, "{}", e),
|
||||
Error::Builder(e) => write!(f, "{:?}", e),
|
||||
Error::Database(e) => write!(f, "{}", e),
|
||||
Error::Io(e) => write!(f, "{}", e),
|
||||
Error::Protobuf(e) => write!(f, "{}", e),
|
||||
Error::SaplingNotActive => write!(f, "Could not determine Sapling upgrade activation height."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E: error::Error + 'static> error::Error for Error<E> {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match &self {
|
||||
Error::InvalidMemo(e) => Some(e),
|
||||
Error::Bech32(e) => Some(e),
|
||||
Error::Builder(e) => Some(e),
|
||||
Error::Database(e) => Some(e),
|
||||
Error::Io(e) => Some(e),
|
||||
Error::Protobuf(e) => Some(e),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<bech32::Error> for Error<E> {
|
||||
fn from(e: bech32::Error) -> Self {
|
||||
Error::Bech32(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<bs58::decode::Error> for Error<E> {
|
||||
fn from(e: bs58::decode::Error) -> Self {
|
||||
Error::Base58(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<builder::Error> for Error<E> {
|
||||
fn from(e: builder::Error) -> Self {
|
||||
Error::Builder(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<std::io::Error> for Error<E> {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Error::Io(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<protobuf::ProtobufError> for Error<E> {
|
||||
fn from(e: protobuf::ProtobufError) -> Self {
|
||||
Error::Protobuf(e)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
use zcash_primitives::{
|
||||
block::BlockHash,
|
||||
//merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
//sapling::Node,
|
||||
//transaction::{
|
||||
// Transaction,
|
||||
// TxId,
|
||||
// components::Amount,
|
||||
//},
|
||||
//zip32::ExtendedFullViewingKey,
|
||||
consensus::{self, BlockHeight},
|
||||
};
|
||||
|
||||
use crate::proto::compact_formats::CompactBlock;
|
||||
|
||||
pub mod chain;
|
||||
pub mod error;
|
||||
|
||||
pub trait DBOps {
|
||||
type Error;
|
||||
// type TxRef; // Backend-specific transaction handle
|
||||
// type NoteRef; // Backend-specific note identifier`
|
||||
|
||||
// fn init_db() -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn init_accounts(extfvks: &[ExtendedFullViewingKey]) -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn init_blocks(
|
||||
// height: i32,
|
||||
// hash: BlockHash,
|
||||
// time: u32,
|
||||
// sapling_tree: &[u8],
|
||||
// ) -> Result<(), Self::Error>;
|
||||
//
|
||||
fn block_height_extrema(&self) -> Result<Option<(BlockHeight, BlockHeight)>, Self::Error>;
|
||||
|
||||
fn get_block_hash(&self, block_height: BlockHeight) -> Result<Option<BlockHash>, Self::Error>;
|
||||
|
||||
fn rewind_to_height<P: consensus::Parameters>(
|
||||
&self,
|
||||
parameters: &P,
|
||||
block_height: BlockHeight,
|
||||
) -> Result<(), Self::Error>;
|
||||
//
|
||||
// // fn get_target_and_anchor_heights() -> Result<(u32, u32), Self::Error>;
|
||||
//
|
||||
// fn get_address(account: Account) -> Result<String, Self::Error>;
|
||||
//
|
||||
// fn get_balance(account: Account) -> Result<Amount, Self::Error>;
|
||||
//
|
||||
// fn get_verified_balance(account: Account) -> Result<Amount, Self::Error>;
|
||||
//
|
||||
// fn get_received_memo_as_utf8(id_note: i64) -> Result<Option<String>, Self::Error>;
|
||||
//
|
||||
// fn get_extended_full_viewing_keys() -> Result<Box<dyn Iterator<Item = ExtendedFullViewingKey>>, Self::Error>;
|
||||
//
|
||||
// fn get_commitment_tree(block_height: BlockHeight) -> Result<Option<CommitmentTree<Node>>, Self::Error>;
|
||||
//
|
||||
// fn get_witnesses(block_height: BlockHeight) -> Result<Box<dyn Iterator<Item = IncrementalWitness<Node>>>, Self::Error>;
|
||||
//
|
||||
// fn get_nullifiers() -> Result<(Vec<u8>, Account), Self::Error>;
|
||||
//
|
||||
// fn create_block(block_height: BlockHeight, hash: BlockHash, time: u32, sapling_tree: CommitmentTree<Node>) -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn put_transaction(transaction: Transaction, block_height: BlockHeight) -> Result<Self::TxRef, Self::Error>;
|
||||
//
|
||||
// fn get_txref(txid: TxId) -> Result<Option<Self::TxRef>, Self::Error>;
|
||||
//
|
||||
// fn mark_spent(tx_ref: Self::TxRef, nullifier: Vec<u8>) -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn put_note(output: WalletShieldedOutput, tx_ref: Self::TxRef, nullifier: Vec<u8>) -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn get_note(tx_ref: Self::TxRef, output_index: i64) -> Result<Self::NoteRef, Self::Error>;
|
||||
//
|
||||
// fn prune_witnesses(to_height: BlockHeight) -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn mark_expired_unspent(to_height: BlockHeight) -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn put_sent_note(tx_ref: Self::TxRef, output: DecryptedOutput) -> Result<(), Self::Error>;
|
||||
//
|
||||
// fn put_received_note(tx_ref: Self::TxRef, output: DecryptedOutput) -> Result<(), Self::Error>;
|
||||
}
|
||||
|
||||
pub trait CacheOps {
|
||||
type Error;
|
||||
|
||||
// Validate the cached chain by applying a function that checks pairwise constraints
|
||||
// (top_block :: &CompactBlock, next_block :: &CompactBlock) -> Result<(), Self::Error)
|
||||
// beginning with the current maximum height walking backward through the chain, terminating
|
||||
// with the block at `from_height`. Returns the hash of the block at height `from_height`
|
||||
fn validate_chain<F>(
|
||||
&self,
|
||||
from_height: BlockHeight,
|
||||
validate: F,
|
||||
) -> Result<Option<BlockHash>, Self::Error>
|
||||
where
|
||||
F: Fn(&CompactBlock, &CompactBlock) -> Result<(), Self::Error>;
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
#![deny(intra_doc_link_resolution_failure)]
|
||||
|
||||
pub mod address;
|
||||
pub mod data_api;
|
||||
mod decrypt;
|
||||
pub mod encoding;
|
||||
pub mod keys;
|
||||
|
|
|
@ -46,6 +46,16 @@ impl compact_formats::CompactBlock {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the [`BlockHeight`] value for this block
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if [`CompactBlock.height`] is not
|
||||
/// representable within a u32.
|
||||
pub fn height(&self) -> BlockHeight {
|
||||
self.height.try_into().unwrap()
|
||||
}
|
||||
|
||||
/// Returns the [`BlockHeader`] for this block if present.
|
||||
///
|
||||
/// A convenience method that parses [`CompactBlock.header`] if present.
|
||||
|
@ -58,15 +68,6 @@ impl compact_formats::CompactBlock {
|
|||
BlockHeader::read(&self.header[..]).ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`BlockHeight`] for this block.
|
||||
///
|
||||
/// A convenience method that wraps [`CompactBlock.height`]
|
||||
///
|
||||
/// [`CompactBlock.height`]: #structfield.height
|
||||
pub fn height(&self) -> BlockHeight {
|
||||
BlockHeight::from(self.height)
|
||||
}
|
||||
}
|
||||
|
||||
impl compact_formats::CompactOutput {
|
||||
|
|
|
@ -45,9 +45,23 @@ impl From<u32> for BlockHeight {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<u64> for BlockHeight {
|
||||
fn from(value: u64) -> Self {
|
||||
BlockHeight(value as u32)
|
||||
impl From<BlockHeight> for u32 {
|
||||
fn from(value: BlockHeight) -> u32 {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u64> for BlockHeight {
|
||||
type Error = std::num::TryFromIntError;
|
||||
|
||||
fn try_from(value: u64) -> Result<Self, Self::Error> {
|
||||
u32::try_from(value).map(BlockHeight)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockHeight> for u64 {
|
||||
fn from(value: BlockHeight) -> u64 {
|
||||
value.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,18 +81,6 @@ impl TryFrom<i64> for BlockHeight {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<BlockHeight> for u32 {
|
||||
fn from(value: BlockHeight) -> u32 {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockHeight> for u64 {
|
||||
fn from(value: BlockHeight) -> u64 {
|
||||
value.0 as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BlockHeight> for i64 {
|
||||
fn from(value: BlockHeight) -> i64 {
|
||||
value.0 as i64
|
||||
|
|
Loading…
Reference in New Issue