Take the 'Sum' out of 'Sumtree' (#702)
* beginning to remove sum * continuing to remove sumtree sums * finished removing sums from pmmr core * renamed sumtree files, and completed changes+test updates in core and store * updating grin/chain to include removelogs * integration of flatfile structure, changes to chain/sumtree to start using them * tests on chain, core and store passing * cleaning up api and tests * formatting * flatfiles stored as part of PMMR backend instead * all compiling and tests running * documentation * added remove + pruning to flatfiles * remove unneeded enum * adding sumtree root struct
This commit is contained in:
@@ -20,13 +20,13 @@
|
||||
use std::cmp::min;
|
||||
use std::{fmt, ops};
|
||||
use std::convert::AsRef;
|
||||
use std::ops::Add;
|
||||
|
||||
use blake2::blake2b::Blake2b;
|
||||
|
||||
use consensus;
|
||||
use ser::{self, AsFixedBytes, Error, Readable, Reader, Writeable, Writer};
|
||||
use util;
|
||||
use util::LOGGER;
|
||||
|
||||
/// A hash consisting of all zeroes, used as a sentinel. No known preimage.
|
||||
pub const ZERO_HASH: Hash = Hash([0; 32]);
|
||||
@@ -147,6 +147,13 @@ impl Writeable for Hash {
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Hash {
|
||||
type Output = Hash;
|
||||
fn add(self, other: Hash) -> Hash {
|
||||
self.hash_with(other)
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializer that outputs a hash of the serialized object
|
||||
pub struct HashWriter {
|
||||
state: Blake2b,
|
||||
@@ -205,7 +212,6 @@ impl<W: ser::Writeable> Hashed for W {
|
||||
fn hash_with<T: Writeable>(&self, other: T) -> Hash {
|
||||
let mut hasher = HashWriter::default();
|
||||
ser::Writeable::write(self, &mut hasher).unwrap();
|
||||
trace!(LOGGER, "Hashing with additional data");
|
||||
ser::Writeable::write(&other, &mut hasher).unwrap();
|
||||
let mut ret = [0; 32];
|
||||
hasher.finalize(&mut ret);
|
||||
|
||||
+167
-257
@@ -29,169 +29,30 @@
|
||||
//! position of siblings, parents, etc. As all those functions only rely on
|
||||
//! binary operations, they're extremely fast. For more information, see the
|
||||
//! doc on bintree_jump_left_sibling.
|
||||
//! 2. The implementation of a prunable MMR sum tree using the above. Each leaf
|
||||
//! is required to be Summable and Hashed. Tree roots can be trivially and
|
||||
//! 2. The implementation of a prunable MMR tree using the above. Each leaf
|
||||
//! is required to be Writeable (which implements Hashed). Tree roots can be trivially and
|
||||
//! efficiently calculated without materializing the full tree. The underlying
|
||||
//! (Hash, Sum) pais are stored in a Backend implementation that can either be
|
||||
//! Hashes are stored in a Backend implementation that can either be
|
||||
//! a simple Vec or a database.
|
||||
|
||||
use std::clone::Clone;
|
||||
use std::ops::Deref;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::{self, Deref};
|
||||
|
||||
use core::hash::{Hash, Hashed};
|
||||
use ser::{self, Readable, Reader, Writeable, Writer};
|
||||
use ser::PMMRable;
|
||||
use util::LOGGER;
|
||||
|
||||
/// Trait for an element of the tree that has a well-defined sum and hash that
|
||||
/// the tree can sum over
|
||||
pub trait Summable {
|
||||
/// The type of the sum
|
||||
type Sum: Clone + ops::Add<Output = Self::Sum> + Readable + Writeable + PartialEq;
|
||||
|
||||
/// Obtain the sum of the element
|
||||
fn sum(&self) -> Self::Sum;
|
||||
|
||||
/// Length of the Sum type when serialized. Can be used as a hint by
|
||||
/// underlying storages.
|
||||
fn sum_len() -> usize;
|
||||
}
|
||||
|
||||
/// An empty sum that takes no space, to store elements that do not need summing
|
||||
/// but can still leverage the hierarchical hashing.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct NullSum;
|
||||
impl ops::Add for NullSum {
|
||||
type Output = NullSum;
|
||||
fn add(self, _: NullSum) -> NullSum {
|
||||
NullSum
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for NullSum {
|
||||
fn read(_: &mut Reader) -> Result<NullSum, ser::Error> {
|
||||
Ok(NullSum)
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for NullSum {
|
||||
fn write<W: Writer>(&self, _: &mut W) -> Result<(), ser::Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NullSum {
|
||||
fn eq(&self, _other: &NullSum) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for a type that allows it to be inserted in a tree without summing
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NoSum<T>(pub T);
|
||||
impl<T> Summable for NoSum<T> {
|
||||
type Sum = NullSum;
|
||||
fn sum(&self) -> NullSum {
|
||||
NullSum
|
||||
}
|
||||
fn sum_len() -> usize {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
impl<T> Writeable for NoSum<T>
|
||||
where
|
||||
T: Writeable,
|
||||
{
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
self.0.write(writer)
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility type to handle (Hash, Sum) pairs more conveniently. The addition
|
||||
/// of two HashSums is the (Hash(h1|h2), h1 + h2) HashSum.
|
||||
#[derive(Debug, Clone, Eq)]
|
||||
pub struct HashSum<T>
|
||||
where
|
||||
T: Summable,
|
||||
{
|
||||
/// The hash
|
||||
pub hash: Hash,
|
||||
/// The sum
|
||||
pub sum: T::Sum,
|
||||
}
|
||||
|
||||
impl<T> HashSum<T>
|
||||
where
|
||||
T: Summable + Hashed,
|
||||
{
|
||||
/// Create a hash sum from a summable
|
||||
pub fn from_summable(idx: u64, elmt: &T) -> HashSum<T> {
|
||||
let hash = elmt.hash();
|
||||
let sum = elmt.sum();
|
||||
let node_hash = (idx, &sum, hash).hash();
|
||||
HashSum {
|
||||
hash: node_hash,
|
||||
sum: sum,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for HashSum<T>
|
||||
where
|
||||
T: Summable,
|
||||
{
|
||||
fn eq(&self, other: &HashSum<T>) -> bool {
|
||||
self.hash == other.hash && self.sum == other.sum
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Readable for HashSum<T>
|
||||
where
|
||||
T: Summable,
|
||||
{
|
||||
fn read(r: &mut Reader) -> Result<HashSum<T>, ser::Error> {
|
||||
Ok(HashSum {
|
||||
hash: Hash::read(r)?,
|
||||
sum: T::Sum::read(r)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Writeable for HashSum<T>
|
||||
where
|
||||
T: Summable,
|
||||
{
|
||||
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ser::Error> {
|
||||
self.hash.write(w)?;
|
||||
self.sum.write(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Add for HashSum<T>
|
||||
where
|
||||
T: Summable,
|
||||
{
|
||||
type Output = HashSum<T>;
|
||||
fn add(self, other: HashSum<T>) -> HashSum<T> {
|
||||
HashSum {
|
||||
hash: (self.hash, other.hash).hash(),
|
||||
sum: self.sum + other.sum,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Storage backend for the MMR, just needs to be indexed by order of insertion.
|
||||
/// The PMMR itself does not need the Backend to be accurate on the existence
|
||||
/// of an element (i.e. remove could be a no-op) but layers above can
|
||||
/// depend on an accurate Backend to check existence.
|
||||
pub trait Backend<T>
|
||||
where
|
||||
T: Summable,
|
||||
{
|
||||
/// Append the provided HashSums to the backend storage. The position of the
|
||||
/// first element of the Vec in the MMR is provided to help the
|
||||
/// implementation.
|
||||
fn append(&mut self, position: u64, data: Vec<HashSum<T>>) -> Result<(), String>;
|
||||
pub trait Backend<T> where
|
||||
T:PMMRable {
|
||||
/// Append the provided Hashes to the backend storage, and optionally an associated
|
||||
/// data element to flatfile storage (for leaf nodes only). The position of the
|
||||
/// first element of the Vec in the MMR is provided to help the implementation.
|
||||
fn append(&mut self, position: u64, data: Vec<(Hash, Option<T>)>) -> Result<(), String>;
|
||||
|
||||
/// Rewind the backend state to a previous position, as if all append
|
||||
/// operations after that had been canceled. Expects a position in the PMMR
|
||||
@@ -199,14 +60,20 @@ where
|
||||
/// occurred (see remove).
|
||||
fn rewind(&mut self, position: u64, index: u32) -> Result<(), String>;
|
||||
|
||||
/// Get a HashSum by insertion position
|
||||
fn get(&self, position: u64) -> Option<HashSum<T>>;
|
||||
/// Get a Hash/Element by insertion position. If include_data is true, will
|
||||
/// also return the associated data element
|
||||
fn get(&self, position: u64, include_data: bool) -> Option<(Hash, Option<T>)>;
|
||||
|
||||
/// Remove HashSums by insertion position. An index is also provided so the
|
||||
/// Remove Hashes/Data by insertion position. An index is also provided so the
|
||||
/// underlying backend can implement some rollback of positions up to a
|
||||
/// given index (practically the index is a the height of a block that
|
||||
/// triggered removal).
|
||||
fn remove(&mut self, positions: Vec<u64>, index: u32) -> Result<(), String>;
|
||||
|
||||
/// Returns the data file path.. this is a bit of a hack now that doesn't
|
||||
/// sit well with the design, but TxKernels have to be summed and the
|
||||
/// fastest way to to be able to allow direct access to the file
|
||||
fn get_data_file_path(&self) -> String;
|
||||
}
|
||||
|
||||
/// Prunable Merkle Mountain Range implementation. All positions within the tree
|
||||
@@ -218,18 +85,18 @@ where
|
||||
/// we are in the sequence of nodes making up the MMR.
|
||||
pub struct PMMR<'a, T, B>
|
||||
where
|
||||
T: Summable,
|
||||
T: PMMRable,
|
||||
B: 'a + Backend<T>,
|
||||
{
|
||||
last_pos: u64,
|
||||
backend: &'a mut B,
|
||||
// only needed for parameterizing Backend
|
||||
summable: PhantomData<T>,
|
||||
writeable: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T, B> PMMR<'a, T, B>
|
||||
where
|
||||
T: Summable + Hashed + Clone,
|
||||
T: PMMRable,
|
||||
B: 'a + Backend<T>,
|
||||
{
|
||||
/// Build a new prunable Merkle Mountain Range using the provided backend.
|
||||
@@ -237,7 +104,7 @@ where
|
||||
PMMR {
|
||||
last_pos: 0,
|
||||
backend: backend,
|
||||
summable: PhantomData,
|
||||
writeable: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,48 +115,51 @@ where
|
||||
PMMR {
|
||||
last_pos: last_pos,
|
||||
backend: backend,
|
||||
summable: PhantomData,
|
||||
writeable: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes the root of the MMR. Find all the peaks in the current
|
||||
/// tree and "bags" them to get a single peak.
|
||||
pub fn root(&self) -> HashSum<T> {
|
||||
pub fn root(&self) -> Hash {
|
||||
let peaks_pos = peaks(self.last_pos);
|
||||
let peaks: Vec<Option<HashSum<T>>> = map_vec!(peaks_pos, |&pi| self.backend.get(pi));
|
||||
let peaks: Vec<Option<(Hash, Option<T>)>> = peaks_pos.into_iter()
|
||||
.map(|pi| self.backend.get(pi, false))
|
||||
.collect();
|
||||
|
||||
|
||||
let mut ret = None;
|
||||
for peak in peaks {
|
||||
ret = match (ret, peak) {
|
||||
(None, x) => x,
|
||||
(Some(hsum), None) => Some(hsum),
|
||||
(Some(lhsum), Some(rhsum)) => Some(lhsum + rhsum),
|
||||
(Some(hash), None) => Some(hash),
|
||||
(Some(lhash), Some(rhash)) => Some((lhash.0.hash_with(rhash.0), None)),
|
||||
}
|
||||
}
|
||||
ret.expect("no root, invalid tree")
|
||||
ret.expect("no root, invalid tree").0
|
||||
}
|
||||
|
||||
/// Push a new Summable element in the MMR. Computes new related peaks at
|
||||
/// Push a new element into the MMR. Computes new related peaks at
|
||||
/// the same time if applicable.
|
||||
pub fn push(&mut self, elmt: T) -> Result<u64, String> {
|
||||
let elmt_pos = self.last_pos + 1;
|
||||
let mut current_hashsum = HashSum::from_summable(elmt_pos, &elmt);
|
||||
let mut to_append = vec![current_hashsum.clone()];
|
||||
let mut current_hash = elmt.hash();
|
||||
let mut to_append = vec![(current_hash, Some(elmt))];
|
||||
let mut height = 0;
|
||||
let mut pos = elmt_pos;
|
||||
|
||||
// we look ahead one position in the MMR, if the expected node has a higher
|
||||
// height it means we have to build a higher peak by summing with a previous
|
||||
// height it means we have to build a higher peak by hashing with a previous
|
||||
// sibling. we do it iteratively in case the new peak itself allows the
|
||||
// creation of another parent.
|
||||
while bintree_postorder_height(pos + 1) > height {
|
||||
let left_sibling = bintree_jump_left_sibling(pos);
|
||||
let left_hashsum = self.backend.get(left_sibling).expect(
|
||||
let left_elem = self.backend.get(left_sibling, false).expect(
|
||||
"missing left sibling in tree, should not have been pruned",
|
||||
);
|
||||
current_hashsum = left_hashsum + current_hashsum;
|
||||
current_hash = left_elem.0 + current_hash;
|
||||
|
||||
to_append.push(current_hashsum.clone());
|
||||
to_append.push((current_hash.clone(), None));
|
||||
height += 1;
|
||||
pos += 1;
|
||||
}
|
||||
@@ -322,7 +192,7 @@ where
|
||||
/// to keep an index of elements to positions in the tree. Prunes parent
|
||||
/// nodes as well when they become childless.
|
||||
pub fn prune(&mut self, position: u64, index: u32) -> Result<bool, String> {
|
||||
if let None = self.backend.get(position) {
|
||||
if let None = self.backend.get(position, false) {
|
||||
return Ok(false);
|
||||
}
|
||||
let prunable_height = bintree_postorder_height(position);
|
||||
@@ -345,7 +215,7 @@ where
|
||||
|
||||
// if we have a pruned sibling, we can continue up the tree
|
||||
// otherwise we're done
|
||||
if let None = self.backend.get(sibling) {
|
||||
if let None = self.backend.get(sibling, false) {
|
||||
current = parent;
|
||||
} else {
|
||||
break;
|
||||
@@ -356,26 +226,27 @@ where
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Helper function to get the HashSum of a node at a given position from
|
||||
/// Helper function to get a node at a given position from
|
||||
/// the backend.
|
||||
pub fn get(&self, position: u64) -> Option<HashSum<T>> {
|
||||
pub fn get(&self, position: u64, include_data: bool) -> Option<(Hash, Option<T>)> {
|
||||
if position > self.last_pos {
|
||||
None
|
||||
} else {
|
||||
self.backend.get(position)
|
||||
self.backend.get(position, include_data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Helper function to get the last N nodes inserted, i.e. the last
|
||||
/// n nodes along the bottom of the tree
|
||||
pub fn get_last_n_insertions(&self, n: u64) -> Vec<HashSum<T>> {
|
||||
pub fn get_last_n_insertions(&self, n: u64) -> Vec<(Hash, Option<T>)> {
|
||||
let mut return_vec = Vec::new();
|
||||
let mut last_leaf = self.last_pos;
|
||||
let size = self.unpruned_size();
|
||||
// Special case that causes issues in bintree functions,
|
||||
// just return
|
||||
if size == 1 {
|
||||
return_vec.push(self.backend.get(last_leaf).unwrap());
|
||||
return_vec.push(self.backend.get(last_leaf, true).unwrap());
|
||||
return return_vec;
|
||||
}
|
||||
// if size is even, we're already at the bottom, otherwise
|
||||
@@ -390,7 +261,7 @@ where
|
||||
if bintree_postorder_height(last_leaf) > 0 {
|
||||
last_leaf = bintree_rightmost(last_leaf);
|
||||
}
|
||||
return_vec.push(self.backend.get(last_leaf).unwrap());
|
||||
return_vec.push(self.backend.get(last_leaf, true).unwrap());
|
||||
|
||||
last_leaf = bintree_jump_left_sibling(last_leaf);
|
||||
}
|
||||
@@ -398,21 +269,20 @@ where
|
||||
}
|
||||
|
||||
/// Walks all unpruned nodes in the MMR and revalidate all parent hashes
|
||||
/// and sums.
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
// iterate on all parent nodes
|
||||
for n in 1..(self.last_pos + 1) {
|
||||
if bintree_postorder_height(n) > 0 {
|
||||
if let Some(hs) = self.get(n) {
|
||||
if let Some(hs) = self.get(n, false) {
|
||||
// take the left and right children, if they exist
|
||||
let left_pos = bintree_move_down_left(n).unwrap();
|
||||
let right_pos = bintree_jump_right_sibling(left_pos);
|
||||
|
||||
if let Some(left_child_hs) = self.get(left_pos) {
|
||||
if let Some(right_child_hs) = self.get(right_pos) {
|
||||
// sum and compare
|
||||
if left_child_hs + right_child_hs != hs {
|
||||
return Err(format!("Invalid MMR, hashsum of parent at {} does \
|
||||
if let Some(left_child_hs) = self.get(left_pos, false) {
|
||||
if let Some(right_child_hs) = self.get(right_pos, false) {
|
||||
// add hashes and compare
|
||||
if left_child_hs.0+right_child_hs.0 != hs.0 {
|
||||
return Err(format!("Invalid MMR, hash of parent at {} does \
|
||||
not match children.", n));
|
||||
}
|
||||
}
|
||||
@@ -429,6 +299,11 @@ where
|
||||
self.last_pos
|
||||
}
|
||||
|
||||
/// Return the path of the data file (needed to sum kernels efficiently)
|
||||
pub fn data_file_path(&self) -> String {
|
||||
self.backend.get_data_file_path()
|
||||
}
|
||||
|
||||
/// Debugging utility to print information about the MMRs. Short version
|
||||
/// only prints the last 8 nodes.
|
||||
pub fn dump(&self, short: bool) {
|
||||
@@ -445,40 +320,36 @@ where
|
||||
break;
|
||||
}
|
||||
idx.push_str(&format!("{:>8} ", m + 1));
|
||||
let ohs = self.get(m + 1);
|
||||
let ohs = self.get(m + 1, false);
|
||||
match ohs {
|
||||
Some(hs) => hashes.push_str(&format!("{} ", hs.hash)),
|
||||
Some(hs) => hashes.push_str(&format!("{} ", hs.0)),
|
||||
None => hashes.push_str(&format!("{:>8} ", "??")),
|
||||
}
|
||||
}
|
||||
debug!(LOGGER, "{}", idx);
|
||||
debug!(LOGGER, "{}", hashes);
|
||||
trace!(LOGGER, "{}", idx);
|
||||
trace!(LOGGER, "{}", hashes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple MMR backend implementation based on a Vector. Pruning does not
|
||||
/// compact the Vector itself but still frees the reference to the
|
||||
/// underlying HashSum.
|
||||
/// underlying Hash.
|
||||
#[derive(Clone)]
|
||||
pub struct VecBackend<T>
|
||||
where
|
||||
T: Summable + Clone,
|
||||
{
|
||||
where T:PMMRable {
|
||||
/// Backend elements
|
||||
pub elems: Vec<Option<HashSum<T>>>,
|
||||
pub elems: Vec<Option<(Hash, Option<T>)>>,
|
||||
}
|
||||
|
||||
impl<T> Backend<T> for VecBackend<T>
|
||||
where
|
||||
T: Summable + Clone,
|
||||
{
|
||||
impl <T> Backend <T> for VecBackend<T>
|
||||
where T: PMMRable {
|
||||
#[allow(unused_variables)]
|
||||
fn append(&mut self, position: u64, data: Vec<HashSum<T>>) -> Result<(), String> {
|
||||
fn append(&mut self, position: u64, data: Vec<(Hash, Option<T>)>) -> Result<(), String> {
|
||||
self.elems.append(&mut map_vec!(data, |d| Some(d.clone())));
|
||||
Ok(())
|
||||
}
|
||||
fn get(&self, position: u64) -> Option<HashSum<T>> {
|
||||
fn get(&self, position: u64, _include_data:bool) -> Option<(Hash, Option<T>)> {
|
||||
self.elems[(position - 1) as usize].clone()
|
||||
}
|
||||
#[allow(unused_variables)]
|
||||
@@ -493,18 +364,19 @@ where
|
||||
self.elems = self.elems[0..(position as usize) + 1].to_vec();
|
||||
Ok(())
|
||||
}
|
||||
fn get_data_file_path(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> VecBackend<T>
|
||||
where
|
||||
T: Summable + Clone,
|
||||
{
|
||||
impl <T> VecBackend <T>
|
||||
where T:PMMRable {
|
||||
/// Instantiates a new VecBackend<T>
|
||||
pub fn new() -> VecBackend<T> {
|
||||
VecBackend { elems: vec![] }
|
||||
}
|
||||
|
||||
/// Current number of HashSum elements in the underlying Vec.
|
||||
/// Current number of elements in the underlying Vec.
|
||||
pub fn used_size(&self) -> usize {
|
||||
let mut usz = self.elems.len();
|
||||
for elem in self.elems.deref() {
|
||||
@@ -568,6 +440,28 @@ impl PruneList {
|
||||
}
|
||||
}
|
||||
|
||||
/// As above, but only returning the number of leaf nodes to skip for a
|
||||
/// given leaf. Helpful if, for instance, data for each leaf is being stored
|
||||
/// separately in a continuous flat-file
|
||||
pub fn get_leaf_shift(&self, pos: u64) -> Option<u64> {
|
||||
|
||||
// get the position where the node at pos would fit in the pruned list, if
|
||||
// it's already pruned, nothing to skip
|
||||
match self.pruned_pos(pos) {
|
||||
None => None,
|
||||
Some(idx) => {
|
||||
// skip by the number of leaf nodes pruned in the preceeding subtrees
|
||||
// which just 2^height
|
||||
Some(
|
||||
self.pruned_nodes[0..(idx as usize)]
|
||||
.iter()
|
||||
.map(|n| 1 << bintree_postorder_height(*n))
|
||||
.sum(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Push the node at the provided position in the prune list. Compacts the
|
||||
/// list if pruning the additional node means a parent can get pruned as
|
||||
/// well.
|
||||
@@ -591,7 +485,7 @@ impl PruneList {
|
||||
}
|
||||
|
||||
/// Gets the position a new pruned node should take in the prune list.
|
||||
/// If the node has already bee pruned, either directly or through one of
|
||||
/// If the node has already been pruned, either directly or through one of
|
||||
/// its parents contained in the prune list, returns None.
|
||||
pub fn pruned_pos(&self, pos: u64) -> Option<usize> {
|
||||
match self.pruned_nodes.binary_search(&pos) {
|
||||
@@ -833,7 +727,20 @@ fn most_significant_pos(num: u64) -> u64 {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use core::hash::Hashed;
|
||||
use ser::{Writeable, Readable, Error};
|
||||
use core::{Writer, Reader};
|
||||
use core::hash::{Hash};
|
||||
|
||||
#[test]
|
||||
fn test_leaf_index(){
|
||||
assert_eq!(n_leaves(1),1);
|
||||
assert_eq!(n_leaves(2),2);
|
||||
assert_eq!(n_leaves(4),3);
|
||||
assert_eq!(n_leaves(5),4);
|
||||
assert_eq!(n_leaves(8),5);
|
||||
assert_eq!(n_leaves(9),6);
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn some_all_ones() {
|
||||
@@ -890,23 +797,17 @@ mod test {
|
||||
assert_eq!(peaks(42), vec![31, 38, 41, 42]);
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
struct TestElem([u32; 4]);
|
||||
impl Summable for TestElem {
|
||||
type Sum = u64;
|
||||
fn sum(&self) -> u64 {
|
||||
// sums are not allowed to overflow, so we use this simple
|
||||
// non-injective "sum" function that will still be homomorphic
|
||||
self.0[0] as u64 * 0x1000 + self.0[1] as u64 * 0x100 + self.0[2] as u64 * 0x10 +
|
||||
self.0[3] as u64
|
||||
}
|
||||
fn sum_len() -> usize {
|
||||
8
|
||||
|
||||
impl PMMRable for TestElem {
|
||||
fn len() -> usize {
|
||||
16
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for TestElem {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
|
||||
try!(writer.write_u32(self.0[0]));
|
||||
try!(writer.write_u32(self.0[1]));
|
||||
try!(writer.write_u32(self.0[2]));
|
||||
@@ -914,6 +815,19 @@ mod test {
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for TestElem {
|
||||
fn read(reader: &mut Reader) -> Result<TestElem, Error> {
|
||||
Ok(TestElem (
|
||||
[
|
||||
reader.read_u32()?,
|
||||
reader.read_u32()?,
|
||||
reader.read_u32()?,
|
||||
reader.read_u32()?,
|
||||
]
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(unused_variables)]
|
||||
fn pmmr_push_root() {
|
||||
@@ -934,72 +848,67 @@ mod test {
|
||||
|
||||
// one element
|
||||
pmmr.push(elems[0]).unwrap();
|
||||
let hash = Hashed::hash(&elems[0]);
|
||||
let sum = elems[0].sum();
|
||||
let node_hash = (1 as u64, &sum, hash).hash();
|
||||
let node_hash = elems[0].hash();
|
||||
assert_eq!(
|
||||
pmmr.root(),
|
||||
HashSum {
|
||||
hash: node_hash,
|
||||
sum: sum,
|
||||
}
|
||||
node_hash,
|
||||
);
|
||||
assert_eq!(pmmr.unpruned_size(), 1);
|
||||
pmmr.dump(false);
|
||||
|
||||
// two elements
|
||||
pmmr.push(elems[1]).unwrap();
|
||||
let sum2 = HashSum::from_summable(1, &elems[0]) +
|
||||
HashSum::from_summable(2, &elems[1]);
|
||||
let sum2 = elems[0].hash() + elems[1].hash();
|
||||
pmmr.dump(false);
|
||||
assert_eq!(pmmr.root(), sum2);
|
||||
assert_eq!(pmmr.unpruned_size(), 3);
|
||||
|
||||
// three elements
|
||||
pmmr.push(elems[2]).unwrap();
|
||||
let sum3 = sum2.clone() + HashSum::from_summable(4, &elems[2]);
|
||||
let sum3 = sum2 + elems[2].hash();
|
||||
pmmr.dump(false);
|
||||
assert_eq!(pmmr.root(), sum3);
|
||||
assert_eq!(pmmr.unpruned_size(), 4);
|
||||
|
||||
// four elements
|
||||
pmmr.push(elems[3]).unwrap();
|
||||
let sum4 = sum2 +
|
||||
(HashSum::from_summable(4, &elems[2]) +
|
||||
HashSum::from_summable(5, &elems[3]));
|
||||
let sum_one = elems[2].hash() + elems[3].hash();
|
||||
let sum4 = sum2 + sum_one;
|
||||
pmmr.dump(false);
|
||||
assert_eq!(pmmr.root(), sum4);
|
||||
assert_eq!(pmmr.unpruned_size(), 7);
|
||||
|
||||
// five elements
|
||||
pmmr.push(elems[4]).unwrap();
|
||||
let sum5 = sum4.clone() + HashSum::from_summable(8, &elems[4]);
|
||||
assert_eq!(pmmr.root(), sum5);
|
||||
let sum3 = sum4 + elems[4].hash();
|
||||
pmmr.dump(false);
|
||||
assert_eq!(pmmr.root(), sum3);
|
||||
assert_eq!(pmmr.unpruned_size(), 8);
|
||||
|
||||
// six elements
|
||||
pmmr.push(elems[5]).unwrap();
|
||||
let sum6 = sum4.clone() +
|
||||
(HashSum::from_summable(8, &elems[4]) +
|
||||
HashSum::from_summable(9, &elems[5]));
|
||||
let sum6 = sum4 +
|
||||
(elems[4].hash() + elems[5].hash());
|
||||
assert_eq!(pmmr.root(), sum6.clone());
|
||||
assert_eq!(pmmr.unpruned_size(), 10);
|
||||
|
||||
// seven elements
|
||||
pmmr.push(elems[6]).unwrap();
|
||||
let sum7 = sum6 + HashSum::from_summable(11, &elems[6]);
|
||||
let sum7 = sum6 + elems[6].hash();
|
||||
assert_eq!(pmmr.root(), sum7);
|
||||
assert_eq!(pmmr.unpruned_size(), 11);
|
||||
|
||||
// eight elements
|
||||
pmmr.push(elems[7]).unwrap();
|
||||
let sum8 = sum4 +
|
||||
((HashSum::from_summable(8, &elems[4]) +
|
||||
HashSum::from_summable(9, &elems[5])) +
|
||||
(HashSum::from_summable(11, &elems[6]) +
|
||||
HashSum::from_summable(12, &elems[7])));
|
||||
((elems[4].hash() + elems[5].hash()) +
|
||||
(elems[6].hash() + elems[7].hash()));
|
||||
assert_eq!(pmmr.root(), sum8);
|
||||
assert_eq!(pmmr.unpruned_size(), 15);
|
||||
|
||||
// nine elements
|
||||
pmmr.push(elems[8]).unwrap();
|
||||
let sum9 = sum8 + HashSum::from_summable(16, &elems[8]);
|
||||
let sum9 = sum8 + elems[8].hash();
|
||||
assert_eq!(pmmr.root(), sum9);
|
||||
assert_eq!(pmmr.unpruned_size(), 16);
|
||||
}
|
||||
@@ -1015,8 +924,9 @@ mod test {
|
||||
TestElem([0, 0, 0, 6]),
|
||||
TestElem([0, 0, 0, 7]),
|
||||
TestElem([0, 0, 0, 8]),
|
||||
TestElem([0, 0, 0, 9]),
|
||||
TestElem([1, 0, 0, 0]),
|
||||
];
|
||||
|
||||
let mut ba = VecBackend::new();
|
||||
let mut pmmr = PMMR::new(&mut ba);
|
||||
|
||||
@@ -1026,23 +936,23 @@ mod test {
|
||||
|
||||
pmmr.push(elems[0]).unwrap();
|
||||
let res = pmmr.get_last_n_insertions(19);
|
||||
assert!(res.len() == 1 && res[0].sum == 1);
|
||||
assert!(res.len() == 1);
|
||||
|
||||
pmmr.push(elems[1]).unwrap();
|
||||
|
||||
let res = pmmr.get_last_n_insertions(12);
|
||||
assert!(res[0].sum == 2 && res[1].sum == 1);
|
||||
assert!(res.len() == 2);
|
||||
|
||||
pmmr.push(elems[2]).unwrap();
|
||||
|
||||
let res = pmmr.get_last_n_insertions(2);
|
||||
assert!(res[0].sum == 3 && res[1].sum == 2);
|
||||
assert!(res.len() == 2);
|
||||
|
||||
pmmr.push(elems[3]).unwrap();
|
||||
|
||||
let res = pmmr.get_last_n_insertions(19);
|
||||
assert!(
|
||||
res[0].sum == 4 && res[1].sum == 3 && res[2].sum == 2 && res[3].sum == 1 && res.len() == 4
|
||||
res.len() == 4
|
||||
);
|
||||
|
||||
pmmr.push(elems[5]).unwrap();
|
||||
@@ -1052,7 +962,7 @@ mod test {
|
||||
|
||||
let res = pmmr.get_last_n_insertions(7);
|
||||
assert!(
|
||||
res[0].sum == 9 && res[1].sum == 8 && res[2].sum == 7 && res[3].sum == 6 && res.len() == 7
|
||||
res.len() == 7
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1071,7 +981,7 @@ mod test {
|
||||
TestElem([1, 0, 0, 0]),
|
||||
];
|
||||
|
||||
let orig_root: HashSum<TestElem>;
|
||||
let orig_root: Hash;
|
||||
let sz: u64;
|
||||
let mut ba = VecBackend::new();
|
||||
{
|
||||
@@ -1085,7 +995,7 @@ mod test {
|
||||
|
||||
// pruning a leaf with no parent should do nothing
|
||||
{
|
||||
let mut pmmr = PMMR::at(&mut ba, sz);
|
||||
let mut pmmr:PMMR<TestElem, _> = PMMR::at(&mut ba, sz);
|
||||
pmmr.prune(16, 0).unwrap();
|
||||
assert_eq!(orig_root, pmmr.root());
|
||||
}
|
||||
@@ -1093,14 +1003,14 @@ mod test {
|
||||
|
||||
// pruning leaves with no shared parent just removes 1 element
|
||||
{
|
||||
let mut pmmr = PMMR::at(&mut ba, sz);
|
||||
let mut pmmr:PMMR<TestElem, _> = PMMR::at(&mut ba, sz);
|
||||
pmmr.prune(2, 0).unwrap();
|
||||
assert_eq!(orig_root, pmmr.root());
|
||||
}
|
||||
assert_eq!(ba.used_size(), 15);
|
||||
|
||||
{
|
||||
let mut pmmr = PMMR::at(&mut ba, sz);
|
||||
let mut pmmr:PMMR<TestElem, _> = PMMR::at(&mut ba, sz);
|
||||
pmmr.prune(4, 0).unwrap();
|
||||
assert_eq!(orig_root, pmmr.root());
|
||||
}
|
||||
@@ -1108,7 +1018,7 @@ mod test {
|
||||
|
||||
// pruning a non-leaf node has no effect
|
||||
{
|
||||
let mut pmmr = PMMR::at(&mut ba, sz);
|
||||
let mut pmmr:PMMR<TestElem, _> = PMMR::at(&mut ba, sz);
|
||||
pmmr.prune(3, 0).unwrap_err();
|
||||
assert_eq!(orig_root, pmmr.root());
|
||||
}
|
||||
@@ -1116,7 +1026,7 @@ mod test {
|
||||
|
||||
// pruning sibling removes subtree
|
||||
{
|
||||
let mut pmmr = PMMR::at(&mut ba, sz);
|
||||
let mut pmmr:PMMR<TestElem, _> = PMMR::at(&mut ba, sz);
|
||||
pmmr.prune(5, 0).unwrap();
|
||||
assert_eq!(orig_root, pmmr.root());
|
||||
}
|
||||
@@ -1124,7 +1034,7 @@ mod test {
|
||||
|
||||
// pruning all leaves under level >1 removes all subtree
|
||||
{
|
||||
let mut pmmr = PMMR::at(&mut ba, sz);
|
||||
let mut pmmr:PMMR<TestElem, _> = PMMR::at(&mut ba, sz);
|
||||
pmmr.prune(1, 0).unwrap();
|
||||
assert_eq!(orig_root, pmmr.root());
|
||||
}
|
||||
@@ -1132,7 +1042,7 @@ mod test {
|
||||
|
||||
// pruning everything should only leave us the peaks
|
||||
{
|
||||
let mut pmmr = PMMR::at(&mut ba, sz);
|
||||
let mut pmmr:PMMR<TestElem, _> = PMMR::at(&mut ba, sz);
|
||||
for n in 1..16 {
|
||||
let _ = pmmr.prune(n, 0);
|
||||
}
|
||||
|
||||
+40
-123
@@ -19,16 +19,14 @@ use util::{static_secp_instance, kernel_sig_msg};
|
||||
use util::secp::pedersen::{Commitment, RangeProof};
|
||||
use std::cmp::{min, max};
|
||||
use std::cmp::Ordering;
|
||||
use std::ops;
|
||||
|
||||
use consensus;
|
||||
use consensus::VerifySortOrder;
|
||||
use core::Committed;
|
||||
use core::hash::{Hash, Hashed, ZERO_HASH};
|
||||
use core::pmmr::Summable;
|
||||
use keychain;
|
||||
use keychain::{Identifier, Keychain, BlindingFactor};
|
||||
use ser::{self, read_and_verify_sorted, Readable, Reader, Writeable, WriteableSorted, Writer};
|
||||
use keychain;
|
||||
use ser::{self, read_and_verify_sorted, PMMRable, Readable, Reader, Writeable, WriteableSorted, Writer};
|
||||
use util;
|
||||
|
||||
/// The size of the blake2 hash of a switch commitment (256 bits)
|
||||
@@ -217,9 +215,10 @@ impl TxKernel {
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Size in bytes of a kernel, necessary for binary storage
|
||||
pub fn size() -> usize {
|
||||
impl PMMRable for TxKernel {
|
||||
fn len() -> usize {
|
||||
17 + // features plus fee and lock_height
|
||||
secp::constants::PEDERSEN_COMMITMENT_SIZE +
|
||||
secp::constants::AGG_SIGNATURE_SIZE
|
||||
@@ -671,17 +670,13 @@ impl SwitchCommitHash {
|
||||
/// provides future-proofing against quantum-based attacks, as well as providing
|
||||
/// wallet implementations with a way to identify their outputs for wallet
|
||||
/// reconstruction.
|
||||
///
|
||||
/// The hash of an output only covers its features, commitment,
|
||||
/// and switch commitment. The range proof is expected to have its own hash
|
||||
/// and is stored and committed to separately.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct Output {
|
||||
/// Options for an output's structure or use
|
||||
pub features: OutputFeatures,
|
||||
/// The homomorphic commitment representing the output amount
|
||||
pub commit: Commitment,
|
||||
/// The switch commitment hash, a 160 bit length blake2 hash of blind*J
|
||||
/// The switch commitment hash, a 256 bit length blake2 hash of blind*J
|
||||
pub switch_commit_hash: SwitchCommitHash,
|
||||
/// A proof that the commitment is in the right range
|
||||
pub proof: RangeProof,
|
||||
@@ -704,9 +699,13 @@ impl Writeable for Output {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(self.features.bits())?;
|
||||
writer.write_fixed_bytes(&self.commit)?;
|
||||
writer.write_fixed_bytes(&self.switch_commit_hash)?;
|
||||
|
||||
// The hash of an output doesn't include the range proof
|
||||
// Hash of an output doesn't cover the switch commit, it should
|
||||
// be wound into the range proof separately
|
||||
if writer.serialization_mode() != ser::SerializationMode::Hash {
|
||||
writer.write_fixed_bytes(&self.switch_commit_hash)?;
|
||||
}
|
||||
// The hash of an output doesn't include the range proof, which
|
||||
// is commit to separately
|
||||
if writer.serialization_mode() == ser::SerializationMode::Full {
|
||||
writer.write_bytes(&self.proof)?
|
||||
}
|
||||
@@ -818,21 +817,6 @@ impl OutputIdentifier {
|
||||
util::to_hex(self.commit.0.to_vec()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert an output_indentifier to a sum_commit representation
|
||||
/// so we can use it to query the the output MMR
|
||||
pub fn as_sum_commit(&self) -> SumCommit {
|
||||
SumCommit {
|
||||
features: self.features,
|
||||
commit: self.commit,
|
||||
switch_commit_hash: SwitchCommitHash::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert a sum_commit back to an output_identifier.
|
||||
pub fn from_sum_commit(sum_commit: &SumCommit) -> OutputIdentifier {
|
||||
OutputIdentifier::new(sum_commit.features, &sum_commit.commit)
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for OutputIdentifier {
|
||||
@@ -855,140 +839,73 @@ impl Readable for OutputIdentifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper to Output commitments to provide the Summable trait.
|
||||
/// Yet another output version to read/write from disk. Ends up being far too awkward
|
||||
/// to use the write serialisation property to do this
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||
pub struct SumCommit {
|
||||
pub struct OutputStoreable {
|
||||
/// Output features (coinbase vs. regular transaction output)
|
||||
/// We need to include this when hashing to ensure coinbase maturity can be enforced.
|
||||
pub features: OutputFeatures,
|
||||
/// Output commitment
|
||||
pub commit: Commitment,
|
||||
/// The corresponding switch commit hash
|
||||
/// Switch commit hash
|
||||
pub switch_commit_hash: SwitchCommitHash,
|
||||
}
|
||||
|
||||
impl SumCommit {
|
||||
/// Build a new sum_commit.
|
||||
pub fn new(
|
||||
features: OutputFeatures,
|
||||
commit: &Commitment,
|
||||
switch_commit_hash: &SwitchCommitHash,
|
||||
) -> SumCommit {
|
||||
SumCommit {
|
||||
features: features.clone(),
|
||||
commit: commit.clone(),
|
||||
switch_commit_hash: switch_commit_hash.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a new sum_commit from an existing output.
|
||||
pub fn from_output(output: &Output) -> SumCommit {
|
||||
SumCommit {
|
||||
impl OutputStoreable {
|
||||
/// Build a StoreableOutput from an existing output.
|
||||
pub fn from_output(output: &Output) -> OutputStoreable {
|
||||
OutputStoreable {
|
||||
features: output.features,
|
||||
commit: output.commit,
|
||||
switch_commit_hash: output.switch_commit_hash,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a new sum_commit from an existing input.
|
||||
pub fn from_input(input: &Input) -> SumCommit {
|
||||
SumCommit {
|
||||
features: input.features,
|
||||
commit: input.commit,
|
||||
switch_commit_hash: SwitchCommitHash::zero(),
|
||||
/// Return a regular output
|
||||
pub fn to_output(self) -> Output {
|
||||
Output{
|
||||
features: self.features,
|
||||
commit: self.commit,
|
||||
switch_commit_hash: self.switch_commit_hash,
|
||||
proof: RangeProof{
|
||||
proof:[0; secp::constants::MAX_PROOF_SIZE],
|
||||
plen: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Hex string representation of a sum_commit.
|
||||
pub fn to_hex(&self) -> String {
|
||||
format!(
|
||||
"{:b}{}{}",
|
||||
self.features.bits(),
|
||||
util::to_hex(self.commit.0.to_vec()),
|
||||
self.switch_commit_hash.to_hex(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Outputs get summed through their commitments.
|
||||
impl Summable for SumCommit {
|
||||
type Sum = SumCommit;
|
||||
|
||||
fn sum(&self) -> SumCommit {
|
||||
SumCommit {
|
||||
commit: self.commit.clone(),
|
||||
features: self.features.clone(),
|
||||
switch_commit_hash: self.switch_commit_hash.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sum_len() -> usize {
|
||||
secp::constants::PEDERSEN_COMMITMENT_SIZE + SWITCH_COMMIT_HASH_SIZE + 1
|
||||
impl PMMRable for OutputStoreable {
|
||||
fn len() -> usize {
|
||||
1 + secp::constants::PEDERSEN_COMMITMENT_SIZE + SWITCH_COMMIT_HASH_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for SumCommit {
|
||||
impl Writeable for OutputStoreable {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
|
||||
writer.write_u8(self.features.bits())?;
|
||||
self.commit.write(writer)?;
|
||||
if writer.serialization_mode() == ser::SerializationMode::Full {
|
||||
if writer.serialization_mode() != ser::SerializationMode::Hash {
|
||||
self.switch_commit_hash.write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SumCommit {
|
||||
fn read(reader: &mut Reader) -> Result<SumCommit, ser::Error> {
|
||||
impl Readable for OutputStoreable {
|
||||
fn read(reader: &mut Reader) -> Result<OutputStoreable, ser::Error> {
|
||||
let features = OutputFeatures::from_bits(reader.read_u8()?).ok_or(
|
||||
ser::Error::CorruptedData,
|
||||
)?;
|
||||
Ok(SumCommit {
|
||||
features: features,
|
||||
Ok(OutputStoreable {
|
||||
commit: Commitment::read(reader)?,
|
||||
switch_commit_hash: SwitchCommitHash::read(reader)?,
|
||||
features: features,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add for SumCommit {
|
||||
type Output = SumCommit;
|
||||
|
||||
fn add(self, other: SumCommit) -> SumCommit {
|
||||
// Build a new commitment by summing the two commitments.
|
||||
let secp = static_secp_instance();
|
||||
let sum = match secp.lock().unwrap().commit_sum(
|
||||
vec![
|
||||
self.commit.clone(),
|
||||
other.commit.clone(),
|
||||
],
|
||||
vec![],
|
||||
) {
|
||||
Ok(s) => s,
|
||||
Err(_) => Commitment::from_vec(vec![1; 33]),
|
||||
};
|
||||
|
||||
// Now build a new switch_commit_hash by concatenating the two switch_commit_hash value
|
||||
// and hashing the result.
|
||||
let mut bytes = self.switch_commit_hash.0.to_vec();
|
||||
bytes.extend(other.switch_commit_hash.0.iter().cloned());
|
||||
let key = SwitchCommitHashKey::zero();
|
||||
let hash = blake2b(SWITCH_COMMIT_HASH_SIZE, &key.0, &bytes);
|
||||
let hash = hash.as_bytes();
|
||||
let mut h = [0; SWITCH_COMMIT_HASH_SIZE];
|
||||
for i in 0..SWITCH_COMMIT_HASH_SIZE {
|
||||
h[i] = hash[i];
|
||||
}
|
||||
let switch_commit_hash_sum = SwitchCommitHash(h);
|
||||
|
||||
SumCommit {
|
||||
features: self.features | other.features,
|
||||
commit: sum,
|
||||
switch_commit_hash: switch_commit_hash_sum,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
+19
-1
@@ -37,6 +37,8 @@ use util::secp::constants::{
|
||||
SECRET_KEY_SIZE,
|
||||
};
|
||||
|
||||
const BULLET_PROOF_SIZE:usize = 674;
|
||||
|
||||
/// Possible errors deriving from serializing or deserializing.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@@ -119,6 +121,9 @@ pub enum SerializationMode {
|
||||
Hash,
|
||||
/// Serialize everything that a signer of the object should know
|
||||
SigHash,
|
||||
/// Serialize for local storage, for instance in the case where
|
||||
/// an output doesn't wish to store its range proof
|
||||
Storage,
|
||||
}
|
||||
|
||||
/// Implementations defined how different numbers and binary structures are
|
||||
@@ -255,6 +260,7 @@ pub fn ser_vec<W: Writeable>(thing: &W) -> Result<Vec<u8>, Error> {
|
||||
Ok(vec)
|
||||
}
|
||||
|
||||
/// Utility to read from a binary source
|
||||
struct BinReader<'a> {
|
||||
source: &'a mut Read,
|
||||
}
|
||||
@@ -364,7 +370,7 @@ impl Writeable for RangeProof {
|
||||
|
||||
impl Readable for RangeProof {
|
||||
fn read(reader: &mut Reader) -> Result<RangeProof, Error> {
|
||||
let p = try!(reader.read_limited_vec(MAX_PROOF_SIZE));
|
||||
let p = try!(reader.read_limited_vec(BULLET_PROOF_SIZE));
|
||||
let mut a = [0; MAX_PROOF_SIZE];
|
||||
for i in 0..p.len() {
|
||||
a[i] = p[i];
|
||||
@@ -376,6 +382,12 @@ impl Readable for RangeProof {
|
||||
}
|
||||
}
|
||||
|
||||
impl PMMRable for RangeProof {
|
||||
fn len() -> usize {
|
||||
BULLET_PROOF_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for Signature {
|
||||
fn read(reader: &mut Reader) -> Result<Signature, Error> {
|
||||
let a = try!(reader.read_fixed_bytes(AGG_SIGNATURE_SIZE));
|
||||
@@ -542,6 +554,12 @@ impl Writeable for [u8; 4] {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can serialize and report their size
|
||||
pub trait PMMRable: Readable + Writeable + Hashed + Clone {
|
||||
/// Length in bytes
|
||||
fn len() -> usize;
|
||||
}
|
||||
|
||||
/// Useful marker trait on types that can be sized byte slices
|
||||
pub trait AsFixedBytes: Sized + AsRef<[u8]> {
|
||||
/// The length in bytes
|
||||
|
||||
Reference in New Issue
Block a user