rust: Migrate `OrchardMerkleFrontier` to `cxx`

Closes zcash/zcash#6333.
This commit is contained in:
Jack Grigg 2023-01-24 02:37:00 +00:00
parent 908675b5b9
commit 817276c02f
9 changed files with 196 additions and 314 deletions

View File

@ -54,6 +54,7 @@ CXXBRIDGE_RS = \
rust/src/blake2b.rs \
rust/src/bundlecache.rs \
rust/src/equihash.rs \
rust/src/merkle_frontier.rs \
rust/src/orchard_bundle.rs \
rust/src/sapling.rs \
rust/src/streams.rs \
@ -62,6 +63,7 @@ CXXBRIDGE_H = \
rust/gen/include/rust/blake2b.h \
rust/gen/include/rust/bundlecache.h \
rust/gen/include/rust/equihash.h \
rust/gen/include/rust/merkle_frontier.h \
rust/gen/include/rust/orchard_bundle.h \
rust/gen/include/rust/sapling.h \
rust/gen/include/rust/streams.h \
@ -70,6 +72,7 @@ CXXBRIDGE_CPP = \
rust/gen/src/blake2b.cpp \
rust/gen/src/bundlecache.cpp \
rust/gen/src/equihash.cpp \
rust/gen/src/merkle_frontier.cpp \
rust/gen/src/orchard_bundle.cpp \
rust/gen/src/sapling.cpp \
rust/gen/src/streams.cpp \

View File

@ -1,96 +0,0 @@
// Copyright (c) 2021-2022 The Zcash developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
#ifndef ZCASH_RUST_INCLUDE_RUST_ORCHARD_INCREMENTAL_MERKLE_TREE_H
#define ZCASH_RUST_INCLUDE_RUST_ORCHARD_INCREMENTAL_MERKLE_TREE_H
#include "rust/streams.h"
#include "rust/orchard.h"
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SINSEMILLA_DIGEST_LEN 32U
/// Pointer to an Orchard incremental merkle tree frontier
struct OrchardMerkleFrontierPtr;
typedef struct OrchardMerkleFrontierPtr OrchardMerkleFrontierPtr;
// Create an empty Orchard Merkle frontier.
//
// Memory allocated to the resulting value must be manually freed.
OrchardMerkleFrontierPtr* orchard_merkle_frontier_empty();
// Clones the given Orchard Merkle frontier and returns
// a pointer to the newly created tree. Both the original
// tree's memory and the newly allocated one need to be freed
// independently.
OrchardMerkleFrontierPtr* orchard_merkle_frontier_clone(
const OrchardMerkleFrontierPtr* tree_ptr);
// Free the memory allocated for the given Orchard Merkle frontier.
void orchard_merkle_frontier_free(
OrchardMerkleFrontierPtr* tree_ptr);
// Parses an Orchard Merkle frontier from a stream. If parsing
// fails, this will return the null pointer.
//
// Memory allocated to the resulting value must be manually freed.
OrchardMerkleFrontierPtr* orchard_merkle_frontier_parse(
void* stream,
read_callback_t read_cb);
// Serializes an Orchard Merkle frontier to a stream.
//
// Returns `false` if an error occurs while writing to the stream.
bool orchard_merkle_frontier_serialize(
const OrchardMerkleFrontierPtr* tree_ptr,
void* stream,
write_callback_t write_cb);
// Serializes an Orchard Merkle frontier to a stream using the
// zcash_primitives::merkle_tree::CommitmentTree sparse encoding.
//
// Returns `false` if an error occurs while writing to the stream.
bool orchard_merkle_frontier_serialize_legacy(
const OrchardMerkleFrontierPtr* tree_ptr,
void* stream,
write_callback_t write_cb);
// For each action in the provided bundle, append its
// commitment to the frontier.
//
// Returns `true` if the append succeeds, `false` if the
// tree is full.
bool orchard_merkle_frontier_append_bundle(
OrchardMerkleFrontierPtr* tree_ptr,
const OrchardBundlePtr* bundle);
// Computes the root of the provided orchard Merkle frontier
void orchard_merkle_frontier_root(
const OrchardMerkleFrontierPtr* tree_ptr,
unsigned char* digest_ret);
// The total number of leaves that have been appended to obtain
// the current state of the frontier. Subtract 1 from this value
// to obtain the position of the most recently appended leaf.
uint64_t orchard_merkle_frontier_num_leaves(
const OrchardMerkleFrontierPtr* tree_ptr);
// Estimate the amount of memory consumed by the merkle frontier.
size_t orchard_merkle_frontier_dynamic_mem_usage(
const OrchardMerkleFrontierPtr* tree_ptr);
// Computes the empty leaf value for the incremental Merkle tree.
void orchard_merkle_tree_empty_root(
unsigned char* digest_ret);
#ifdef __cplusplus
}
#endif
#endif // ZCASH_RUST_INCLUDE_RUST_ORCHARD_INCREMENTAL_MERKLE_TREE_H

View File

@ -5,7 +5,6 @@
#ifndef ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
#define ZCASH_RUST_INCLUDE_RUST_ORCHARD_WALLET_H
#include "rust/orchard/incremental_merkle_tree.h"
#include "rust/orchard/keys.h"
#include "rust/builder.h"
@ -19,6 +18,10 @@ extern "C" {
struct OrchardWalletPtr;
typedef struct OrchardWalletPtr OrchardWalletPtr;
/// Pointer to an Orchard incremental merkle tree frontier
struct OrchardMerkleFrontierPtr;
typedef struct OrchardMerkleFrontierPtr OrchardMerkleFrontierPtr;
/**
* Constructs a new empty Orchard wallet and return a pointer to it.
* Memory is allocated by Rust and must be manually freed using

View File

@ -1,186 +0,0 @@
use incrementalmerkletree::{bridgetree, Altitude, Frontier, Hashable};
use std::mem::size_of_val;
use std::ptr;
use orchard::{bundle::Authorized, tree::MerkleHashOrchard};
use tracing::error;
use zcash_primitives::{
merkle_tree::{
incremental::{read_frontier_v1, write_frontier_v1},
CommitmentTree,
},
transaction::components::Amount,
};
use crate::streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb};
pub const MERKLE_DEPTH: u8 = 32;
//
// Operations on Merkle frontiers.
//
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_empty(
) -> *mut bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH> {
let empty_tree = bridgetree::Frontier::<MerkleHashOrchard, MERKLE_DEPTH>::empty();
Box::into_raw(Box::new(empty_tree))
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_clone(
tree: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
) -> *mut bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH> {
unsafe { tree.as_ref() }
.map(|tree| Box::into_raw(Box::new(tree.clone())))
.unwrap_or(std::ptr::null_mut())
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_free(
tree: *mut bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
) {
if !tree.is_null() {
drop(unsafe { Box::from_raw(tree) });
}
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_parse(
stream: Option<StreamObj>,
read_cb: Option<ReadCb>,
) -> *mut bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH> {
let reader = CppStreamReader::from_raw_parts(stream, read_cb.unwrap());
match read_frontier_v1(reader) {
Ok(parsed) => Box::into_raw(Box::new(parsed)),
Err(e) => {
error!("Failed to parse Orchard bundle: {}", e);
ptr::null_mut()
}
}
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_serialize(
frontier: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
stream: Option<StreamObj>,
write_cb: Option<WriteCb>,
) -> bool {
let frontier = unsafe {
frontier
.as_ref()
.expect("Orchard note commitment tree pointer may not be null.")
};
let writer = CppStreamWriter::from_raw_parts(stream, write_cb.unwrap());
match write_frontier_v1(writer, frontier) {
Ok(()) => true,
Err(e) => {
error!("{}", e);
false
}
}
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_serialize_legacy(
frontier: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
stream: Option<StreamObj>,
write_cb: Option<WriteCb>,
) -> bool {
let frontier = unsafe {
frontier
.as_ref()
.expect("Orchard note commitment tree pointer may not be null.")
};
let writer = CppStreamWriter::from_raw_parts(stream, write_cb.unwrap());
let commitment_tree = CommitmentTree::from_frontier(frontier);
match commitment_tree.write(writer) {
Ok(()) => true,
Err(e) => {
error!("{}", e);
false
}
}
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_append_bundle(
tree: *mut bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
bundle: *const orchard::Bundle<Authorized, Amount>,
) -> bool {
let tree = unsafe {
tree.as_mut()
.expect("Orchard note commitment tree pointer may not be null.")
};
if let Some(bundle) = unsafe { bundle.as_ref() } {
for action in bundle.actions().iter() {
if !tree.append(&MerkleHashOrchard::from_cmx(action.cmx())) {
error!("Orchard note commitment tree is full.");
return false;
}
}
}
true
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_root(
tree: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
root_ret: *mut [u8; 32],
) {
let tree = unsafe {
tree.as_ref()
.expect("Orchard note commitment tree pointer may not be null.")
};
let root_ret = unsafe {
root_ret
.as_mut()
.expect("Cannot return to the null pointer.")
};
*root_ret = tree.root().to_bytes();
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_num_leaves(
tree: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
) -> u64 {
let tree = unsafe {
tree.as_ref()
.expect("Orchard note commitment tree pointer may not be null.")
};
tree.position().map_or(0, |p| <u64>::from(p) + 1)
}
#[no_mangle]
pub extern "C" fn orchard_merkle_frontier_dynamic_mem_usage(
tree: *const bridgetree::Frontier<MerkleHashOrchard, MERKLE_DEPTH>,
) -> usize {
let tree = unsafe {
tree.as_ref()
.expect("Orchard note commitment tree pointer may not be null.")
};
size_of_val(tree) + tree.dynamic_memory_usage()
}
#[no_mangle]
pub extern "C" fn orchard_merkle_tree_empty_root(root_ret: *mut [u8; 32]) {
let root_ret = unsafe {
root_ret
.as_mut()
.expect("Cannot return to the null pointer.")
};
let altitude = Altitude::from(MERKLE_DEPTH);
let digest = MerkleHashOrchard::empty_root(altitude).to_bytes();
*root_ret = digest;
}

View File

@ -0,0 +1,161 @@
use core::mem::size_of_val;
use core::pin::Pin;
use incrementalmerkletree::{bridgetree, Altitude, Frontier, Hashable};
use orchard::{bundle::Authorized, tree::MerkleHashOrchard};
use tracing::error;
use zcash_primitives::{
merkle_tree::{
incremental::{read_frontier_v1, write_frontier_v1},
CommitmentTree, HashSer,
},
transaction::components::Amount,
};
use crate::{streams::CppStream, wallet::Wallet};
pub const MERKLE_DEPTH: u8 = 32;
#[cxx::bridge]
mod ffi {
extern "C++" {
include!("streams.h");
#[cxx_name = "RustDataStream"]
type RustStream = crate::streams::ffi::RustStream;
}
#[namespace = "merkle_frontier"]
extern "Rust" {
type Orchard;
type OrchardBundle;
type OrchardWallet;
fn orchard_empty_root() -> [u8; 32];
fn new_orchard() -> Box<Orchard>;
fn box_clone(self: &Orchard) -> Box<Orchard>;
fn parse_orchard(stream: Pin<&mut RustStream>) -> Result<Box<Orchard>>;
fn serialize(self: &Orchard, stream: Pin<&mut RustStream>) -> Result<()>;
fn serialize_legacy(self: &Orchard, stream: Pin<&mut RustStream>) -> Result<()>;
fn dynamic_memory_usage(self: &Orchard) -> usize;
fn root(self: &Orchard) -> [u8; 32];
fn size(self: &Orchard) -> u64;
unsafe fn append_bundle(self: &mut Orchard, bundle: *const OrchardBundle) -> bool;
unsafe fn init_wallet(self: &Orchard, wallet: *mut OrchardWallet) -> bool;
}
}
type Inner<H> = bridgetree::Frontier<H, MERKLE_DEPTH>;
/// A incremental Merkle frontier.
#[derive(Clone)]
struct MerkleFrontier<H>(Inner<H>);
impl<H: Copy + Hashable + HashSer> MerkleFrontier<H> {
/// Returns a copy of the value.
fn box_clone(&self) -> Box<Self> {
Box::new(self.clone())
}
/// Attempts to parse a Merkle frontier from the given C++ stream.
fn parse(stream: Pin<&mut ffi::RustStream>) -> Result<Box<Self>, String> {
let reader = CppStream::from(stream);
match read_frontier_v1(reader) {
Ok(parsed) => Ok(Box::new(MerkleFrontier(parsed))),
Err(e) => Err(format!("Failed to parse v5 Merkle frontier: {}", e)),
}
}
/// Serializes the frontier to the given C++ stream.
fn serialize(&self, stream: Pin<&mut ffi::RustStream>) -> Result<(), String> {
let writer = CppStream::from(stream);
write_frontier_v1(writer, &self.0)
.map_err(|e| format!("Failed to serialize v5 Merkle frontier: {}", e))
}
/// Serializes the frontier to the given C++ stream in the legacy frontier encoding.
fn serialize_legacy(&self, stream: Pin<&mut ffi::RustStream>) -> Result<(), String> {
let writer = CppStream::from(stream);
let commitment_tree = CommitmentTree::from_frontier(&self.0);
commitment_tree.write(writer).map_err(|e| {
format!(
"Failed to serialize Merkle frontier in legacy format: {}",
e,
)
})
}
/// Returns the amount of memory dynamically allocated for the frontier.
///
/// Includes `self` because this type is stored on the heap when passed to C++.
fn dynamic_memory_usage(&self) -> usize {
size_of_val(&self.0) + self.0.dynamic_memory_usage()
}
/// Obtains the current root of this Merkle frontier by hashing against empty nodes up
/// to the maximum height of the pruned tree that the frontier represents.
fn root(&self) -> [u8; 32] {
let mut root = [0; 32];
self.0
.root()
.write(&mut root[..])
.expect("root is 32 bytes");
root
}
/// Returns the number of leaves appended to the frontier.
fn size(&self) -> u64 {
self.0.position().map_or(0, |p| <u64>::from(p) + 1)
}
}
/// Returns the root of an empty Orchard Merkle tree.
fn orchard_empty_root() -> [u8; 32] {
let altitude = Altitude::from(MERKLE_DEPTH);
MerkleHashOrchard::empty_root(altitude).to_bytes()
}
/// An Orchard incremental Merkle frontier.
type Orchard = MerkleFrontier<MerkleHashOrchard>;
/// Constructs a new empty Orchard Merkle frontier.
fn new_orchard() -> Box<Orchard> {
Box::new(MerkleFrontier(Inner::empty()))
}
/// Attempts to parse an Orchard Merkle frontier from the given C++ stream.
fn parse_orchard(stream: Pin<&mut ffi::RustStream>) -> Result<Box<Orchard>, String> {
Orchard::parse(stream)
}
struct OrchardBundle;
struct OrchardWallet;
impl Orchard {
/// Appends the note commitments in the given bundle to this frontier.
fn append_bundle(&mut self, bundle: *const OrchardBundle) -> bool {
let bundle = unsafe { (bundle as *const orchard::Bundle<Authorized, Amount>).as_ref() };
if let Some(bundle) = bundle {
for action in bundle.actions().iter() {
if !self.0.append(&MerkleHashOrchard::from_cmx(action.cmx())) {
error!("Orchard note commitment tree is full.");
return false;
}
}
}
true
}
/// Overwrites the first bridge of the Orchard wallet's note commitment tree to have
/// `self` as its latest state.
///
/// This will fail with an assertion error if any checkpoints exist in the tree.
///
/// TODO: Remove once `crate::wallet` is migrated to `cxx`.
fn init_wallet(&self, wallet: *mut OrchardWallet) -> bool {
crate::wallet::orchard_wallet_init_from_frontier(wallet as *mut Wallet, &self.0)
}
}

View File

@ -66,8 +66,8 @@ mod builder_ffi;
mod bundlecache;
mod history_ffi;
mod incremental_merkle_tree;
mod incremental_merkle_tree_ffi;
mod init_ffi;
mod merkle_frontier;
mod orchard_bundle;
mod orchard_ffi;
mod orchard_keys_ffi;

View File

@ -26,7 +26,7 @@ use orchard::{
use crate::{
builder_ffi::OrchardSpendInfo,
incremental_merkle_tree::{read_tree, write_tree},
incremental_merkle_tree_ffi::MERKLE_DEPTH,
merkle_frontier::MERKLE_DEPTH,
streams_ffi::{CppStreamReader, CppStreamWriter, ReadCb, StreamObj, WriteCb},
zcashd_orchard::OrderedAddress,
};

View File

@ -226,7 +226,8 @@ public:
*/
void InitNoteCommitmentTree(const OrchardMerkleFrontier& frontier) {
assert(!GetLastCheckpointHeight().has_value());
assert(orchard_wallet_init_from_frontier(inner.get(), frontier.inner.get()));
assert(frontier.inner->init_wallet(
reinterpret_cast<merkle_frontier::OrchardWallet*>(inner.get())));
}
/**

View File

@ -12,7 +12,7 @@
#include "zcash/util.h"
#include <primitives/orchard.h>
#include <rust/orchard/incremental_merkle_tree.h>
#include <rust/merkle_frontier.h>
namespace libzcash {
@ -265,19 +265,18 @@ class OrchardMerkleFrontierLegacySer;
class OrchardMerkleFrontier
{
private:
/// An incremental Sinsemilla tree; this pointer may never be null.
/// Memory is allocated by Rust.
std::unique_ptr<OrchardMerkleFrontierPtr, decltype(&orchard_merkle_frontier_free)> inner;
/// An incremental Sinsemilla tree. Memory is allocated by Rust.
rust::Box<merkle_frontier::Orchard> inner;
friend class OrchardWallet;
friend class OrchardMerkleFrontierLegacySer;
public:
OrchardMerkleFrontier() : inner(orchard_merkle_frontier_empty(), orchard_merkle_frontier_free) {}
OrchardMerkleFrontier() : inner(merkle_frontier::new_orchard()) {}
OrchardMerkleFrontier(OrchardMerkleFrontier&& frontier) : inner(std::move(frontier.inner)) {}
OrchardMerkleFrontier(const OrchardMerkleFrontier& frontier) :
inner(orchard_merkle_frontier_clone(frontier.inner.get()), orchard_merkle_frontier_free) {}
inner(frontier.inner->box_clone()) {}
OrchardMerkleFrontier& operator=(OrchardMerkleFrontier&& frontier)
{
@ -289,52 +288,48 @@ public:
OrchardMerkleFrontier& operator=(const OrchardMerkleFrontier& frontier)
{
if (this != &frontier) {
inner.reset(orchard_merkle_frontier_clone(frontier.inner.get()));
inner = frontier.inner->box_clone();
}
return *this;
}
template<typename Stream>
void Serialize(Stream& s) const {
RustStream rs(s);
if (!orchard_merkle_frontier_serialize(inner.get(), &rs, RustStream<Stream>::write_callback)) {
throw std::ios_base::failure("Failed to serialize v5 Orchard tree");
try {
inner->serialize(s);
} catch (const std::exception& e) {
throw std::ios_base::failure(e.what());
}
}
template<typename Stream>
void Unserialize(Stream& s) {
RustStream rs(s);
OrchardMerkleFrontierPtr* tree = orchard_merkle_frontier_parse(
&rs, RustStream<Stream>::read_callback);
if (tree == nullptr) {
throw std::ios_base::failure("Failed to parse v5 Orchard tree");
try {
inner = merkle_frontier::parse_orchard(s);
} catch (const std::exception& e) {
throw std::ios_base::failure(e.what());
}
inner.reset(tree);
}
size_t DynamicMemoryUsage() const {
return orchard_merkle_frontier_dynamic_mem_usage(inner.get());
return inner->dynamic_memory_usage();
}
bool AppendBundle(const OrchardBundle& bundle) {
return orchard_merkle_frontier_append_bundle(inner.get(), bundle.inner.get());
return inner->append_bundle(
reinterpret_cast<merkle_frontier::OrchardBundle*>(bundle.inner.get()));
}
const uint256 root() const {
uint256 value;
orchard_merkle_frontier_root(inner.get(), value.begin());
return value;
return uint256::FromRawBytes(inner->root());
}
static uint256 empty_root() {
uint256 value;
orchard_merkle_tree_empty_root(value.begin());
return value;
return uint256::FromRawBytes(merkle_frontier::orchard_empty_root());
}
size_t size() const {
return orchard_merkle_frontier_num_leaves(inner.get());
return inner->size();
}
};
@ -346,9 +341,10 @@ public:
template<typename Stream>
void Serialize(Stream& s) const {
RustStream rs(s);
if (!orchard_merkle_frontier_serialize_legacy(frontier.inner.get(), &rs, RustStream<Stream>::write_callback)) {
throw std::ios_base::failure("Failed to serialize Orchard merkle frontier in legacy format.");
try {
frontier.inner->serialize_legacy(s);
} catch (const std::exception& e) {
throw std::ios_base::failure(e.what());
}
}
};