Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable faster sync #3108

Merged
merged 10 commits into from
Nov 26, 2019
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion api/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ impl TxHashSet {
pub fn from_head(head: Arc<chain::Chain>) -> TxHashSet {
let roots = head.get_txhashset_roots();
TxHashSet {
output_root_hash: roots.output_root.to_hex(),
output_root_hash: roots.output_root().to_hex(),
range_proof_root_hash: roots.rproof_root.to_hex(),
kernel_root_hash: roots.kernel_root.to_hex(),
}
Expand Down
1 change: 1 addition & 0 deletions chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ workspace = ".."
edition = "2018"

[dependencies]
bit-vec = "0.6"
bitflags = "1"
byteorder = "1"
failure = "0.1"
Expand Down
2 changes: 1 addition & 1 deletion chain/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ impl Chain {
b.header.prev_root = prev_root;

// Set the output, rangeproof and kernel MMR roots.
b.header.output_root = roots.output_root;
b.header.output_root = roots.output_root();
b.header.range_proof_root = roots.rproof_root;
b.header.kernel_root = roots.kernel_root;

Expand Down
2 changes: 2 additions & 0 deletions chain/src/txhashset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
//! Utility structs to handle the 3 hashtrees (output, range proof,
//! kernel) more conveniently and transactionally.
mod bitmap_accumulator;
mod rewindable_kernel_view;
mod txhashset;
mod utxo_view;

pub use self::bitmap_accumulator::*;
pub use self::rewindable_kernel_view::*;
pub use self::txhashset::*;
pub use self::utxo_view::*;
239 changes: 239 additions & 0 deletions chain/src/txhashset/bitmap_accumulator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Copyright 2019 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::convert::TryFrom;
use std::time::Instant;

use bit_vec::BitVec;
use croaring::Bitmap;

use crate::core::core::hash::{DefaultHashable, Hash};
use crate::core::core::pmmr::{self, ReadonlyPMMR, VecBackend, PMMR};
use crate::core::ser::{self, FixedLength, PMMRable, Readable, Reader, Writeable, Writer};
use crate::error::{Error, ErrorKind};

/// The "bitmap accumulator" allows us to commit to a specific bitmap by splitting it into
/// fragments and inserting these fragments into an MMR to produce an overall root hash.
/// Leaves in the MMR are fragments of the bitmap consisting of 1024 contiguous bits
/// from the overall bitmap. The first (leftmost) leaf in the MMR represents the first 1024 bits
/// of the bitmap, the next leaf is the next 1024 bits of the bitmap etc.
///
/// Flipping a single bit does not require the full bitmap to be rehashed, only the path from the
/// relevant leaf up to its associated peak.
///
/// Flipping multiple bits *within* a single chunk is no more expensive than flipping a single bit
/// as a leaf node in the MMR represents a sequence of 1024 bits. Flipping multiple bits located
/// close together is a relatively cheap operation with minimal rehashing required to update the
/// relevant peaks and the overall MMR root.
///
/// It is also possible to generate Merkle proofs for these 1024 bit fragments, proving
/// both inclusion and location in the overall "accumulator" MMR. We plan to take advantage of
/// this during fast sync, allowing for validation of partial data.
///
#[derive(Clone)]
pub struct BitmapAccumulator {
backend: VecBackend<BitmapChunk>,
}

impl BitmapAccumulator {
/// Crate a new empty bitmap accumulator.
pub fn new() -> BitmapAccumulator {
BitmapAccumulator {
backend: VecBackend::new_hash_only(),
}
}

/// Initialize a bitmap accumulator given the provided idx iterator.
pub fn init<T: IntoIterator<Item = u64>>(&mut self, idx: T, size: u64) -> Result<(), Error> {
self.apply_from(idx, 0, size)
}

/// Find the start of the first "chunk" of 1024 bits from the provided idx.
/// Zero the last 10 bits to round down to multiple of 1024.
pub fn chunk_start_idx(idx: u64) -> u64 {
idx & !0x3ff
}

/// The first 1024 belong to chunk 0, the next 1024 to chunk 1 etc.
fn chunk_idx(idx: u64) -> u64 {
idx / 1024
}

/// Apply the provided idx iterator to our bitmap accumulator.
/// We start at the chunk containing from_idx and rebuild chunks as necessary
/// for the bitmap, limiting it to size (in bits).
/// If from_idx is 1023 and size is 1024 then we rebuild a single chunk.
fn apply_from<T>(&mut self, idx: T, from_idx: u64, size: u64) -> Result<(), Error>
where
T: IntoIterator<Item = u64>,
{
let now = Instant::now();

// Find the (1024 bit chunk) chunk_idx for the (individual bit) from_idx.
let from_chunk_idx = BitmapAccumulator::chunk_idx(from_idx);
let mut chunk_idx = from_chunk_idx;

let mut chunk = BitmapChunk::new();

let mut idx_iter = idx.into_iter().filter(|&x| x < size).peekable();
while let Some(x) = idx_iter.peek() {
if *x < chunk_idx * 1024 {
// skip until we reach our first chunk
idx_iter.next();
} else if *x < (chunk_idx + 1) * 1024 {
let idx = idx_iter.next().expect("next after peek");
chunk.set(idx % 1024, true);
} else {
self.append_chunk(chunk)?;
chunk_idx += 1;
chunk = BitmapChunk::new();
}
}
if chunk.any() {
self.append_chunk(chunk)?;
}
debug!(
"applied {} chunks from idx {} to idx {} ({}ms)",
1 + chunk_idx - from_chunk_idx,
from_chunk_idx,
chunk_idx,
now.elapsed().as_millis(),
);
Ok(())
}

/// Apply updates to the bitmap accumulator given an iterator of invalidated idx and
/// an iterator of idx to be set to true.
/// We determine the existing chunks to be rebuilt given the invalidated idx.
/// We then rebuild given idx, extending the accumulator with new chunk(s) as necessary.
/// Resulting bitmap accumulator will contain sufficient bitmap chunks to cover size.
/// If size is 1 then we will have a single chunk.
/// If size is 1023 then we will have a single chunk (bits 0 to 1023 inclusive).
/// If the size is 1024 then we will have two chunks.
pub fn apply<T, U>(&mut self, invalidated_idx: T, idx: U, size: u64) -> Result<(), Error>
where
T: IntoIterator<Item = u64>,
U: IntoIterator<Item = u64>,
{
// Determine the earliest chunk by looking at the min invalidated idx (assume sorted).
// Rewind prior to this and reapply new_idx.
// Note: We rebuild everything after rewind point but much of the bitmap may be
// unchanged. This can be further optimized by only rebuilding necessary chunks and
// rehashing.
if let Some(from_idx) = invalidated_idx.into_iter().next() {
self.rewind_prior(from_idx)?;
self.pad_left(from_idx)?;
self.apply_from(idx, from_idx, size)?;
}

Ok(())
}

/// Given the provided (bit) idx rewind the bitmap accumulator to the end of the
/// previous chunk ready for the updated chunk to be appended.
fn rewind_prior(&mut self, from_idx: u64) -> Result<(), Error> {
let chunk_idx = BitmapAccumulator::chunk_idx(from_idx);
let last_pos = self.backend.size();
let mut pmmr = PMMR::at(&mut self.backend, last_pos);
let chunk_pos = pmmr::insertion_to_pmmr_index(chunk_idx + 1);
let rewind_pos = chunk_pos.saturating_sub(1);
pmmr.rewind(rewind_pos, &Bitmap::create())
.map_err(|e| ErrorKind::Other(e))?;
Ok(())
}

/// Make sure we append empty chunks to fill in any gap before we append the chunk
/// we actually care about. This effectively pads the bitmap with 1024 chunks of 0s
/// as necessary to put the new chunk at the correct place.
fn pad_left(&mut self, from_idx: u64) -> Result<(), Error> {
let chunk_idx = BitmapAccumulator::chunk_idx(from_idx);
let current_chunk_idx = pmmr::n_leaves(self.backend.size());
for _ in current_chunk_idx..chunk_idx {
self.append_chunk(BitmapChunk::new())?;
}
Ok(())
}

/// Append a new chunk to the BitmapAccumulator.
/// Append parent hashes (if any) as necessary to build associated peak.
pub fn append_chunk(&mut self, chunk: BitmapChunk) -> Result<u64, Error> {
let last_pos = self.backend.size();
PMMR::at(&mut self.backend, last_pos)
.push(&chunk)
.map_err(|e| ErrorKind::Other(e).into())
}

/// The root hash of the bitmap accumulator MMR.
pub fn root(&self) -> Hash {
ReadonlyPMMR::at(&self.backend, self.backend.size()).root()
}
}

/// A bitmap "chunk" representing 1024 contiguous bits of the overall bitmap.
/// The first 1024 bits belong in one chunk. The next 1024 bits in the next chunk, etc.
#[derive(Clone, Debug)]
pub struct BitmapChunk(BitVec);

impl BitmapChunk {
const LEN_BITS: usize = 1024;
const LEN_BYTES: usize = Self::LEN_BITS / 8;

/// Create a new bitmap chunk, defaulting all bits in the chunk to false.
pub fn new() -> BitmapChunk {
BitmapChunk(BitVec::from_elem(Self::LEN_BITS, false))
}

/// Set a single bit in this chunk.
/// 0-indexed from start of chunk.
/// Panics if idx is outside the valid range of bits in a chunk.
pub fn set(&mut self, idx: u64, value: bool) {
let idx = usize::try_from(idx).expect("usize from u64");
assert!(idx < Self::LEN_BITS);
self.0.set(idx, value)
}

/// Does this bitmap chunk have any bits set to 1?
pub fn any(&self) -> bool {
self.0.any()
}
}

impl PMMRable for BitmapChunk {
type E = Self;

fn as_elmt(&self) -> Self::E {
self.clone()
}
}

impl FixedLength for BitmapChunk {
const LEN: usize = Self::LEN_BYTES;
}

impl DefaultHashable for BitmapChunk {}

impl Writeable for BitmapChunk {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.0.to_bytes().write(writer)
}
}

impl Readable for BitmapChunk {
/// Reading is not currently supported, just return an empty one for now.
/// We store the underlying roaring bitmap externally for the bitmap accumulator
/// and the "hash only" backend means we never actually read these chunks.
fn read(_reader: &mut dyn Reader) -> Result<BitmapChunk, ser::Error> {
Ok(BitmapChunk::new())
}
}
Loading