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

🙋 Implement feed auditing #55

Merged
merged 4 commits into from
Dec 22, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/audit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/// The audit report for a feed, created by the `.audit()` method.
#[derive(Debug, PartialEq, Clone)]
pub struct Audit {
/// The number of valid blocks identified
pub valid_blocks: usize,
/// The number of invalid blocks identified
pub invalid_blocks: usize,
}

impl Audit {
/// Access the `valid_blocks` field from the proof.
pub fn valid_blocks(&self) -> usize {
self.valid_blocks
}

/// Access the `invalid_blocks` field from the proof.
pub fn invalid_blocks(&self) -> usize {
self.invalid_blocks
}
}
9 changes: 5 additions & 4 deletions src/bitfield/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,11 @@ impl Bitfield {

self.iterator.seek(start);

while self.iterator.index() < max_len && self
.index
.set_byte(self.iterator.index(), byte)
.is_changed()
while self.iterator.index() < max_len
&& self
.index
.set_byte(self.iterator.index(), byte)
.is_changed()
{
if self.iterator.is_left() {
let index: usize = self.index.get_byte(self.iterator.sibling()).into();
Expand Down
26 changes: 26 additions & 0 deletions src/feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::feed_builder::FeedBuilder;
use crate::replicate::{Message, Peer};
pub use crate::storage::{Node, NodeTrait, Storage, Store};

use crate::audit::Audit;
use crate::bitfield::Bitfield;
use crate::crypto::{generate_keypair, sign, verify, Hash, Merkle};
use crate::proof::Proof;
Expand Down Expand Up @@ -514,6 +515,31 @@ where
Ok(extra_nodes)
}

/// Audit all data in the feed. Checks that all current data matches
/// the hashes in the merkle tree, and clears the bitfield if not.
/// The tuple returns is (valid_blocks, invalid_blocks)
pub fn audit(&mut self) -> Result<Audit> {
let mut valid_blocks = 0;
let mut invalid_blocks = 0;
for index in 0..self.length {
if self.bitfield.get(index) {
let node = self.storage.get_node(2 * index)?;
let data = self.storage.get_data(index)?;
let data_hash = Hash::from_leaf(&data);
if node.hash == data_hash.as_bytes() {
valid_blocks += 1;
} else {
invalid_blocks += 1;
self.bitfield.set(index, false);
}
}
}
Ok(Audit {
valid_blocks: valid_blocks,
invalid_blocks: invalid_blocks,
})
}

/// (unimplemented) Provide a range of data to download.
pub fn download(&mut self, _range: Range<usize>) -> Result<()> {
unimplemented!();
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ extern crate tree_index;
pub mod bitfield;
pub mod prelude;

mod audit;
mod crypto;
mod event;
mod feed;
Expand Down
2 changes: 1 addition & 1 deletion src/storage/node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::Result;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use flat_tree;
use merkle_tree_stream::Node as NodeTrait;
use pretty_hash::fmt as pretty_fmt;
Expand Down
69 changes: 69 additions & 0 deletions tests/feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ use common::create_feed;
use hypercore::{
generate_keypair, Feed, NodeTrait, PublicKey, SecretKey, Storage,
};
use std::env::temp_dir;
use std::fmt::Debug;
use std::fs;
use std::io::Write;

#[test]
fn create_with_key() {
Expand Down Expand Up @@ -183,3 +186,69 @@ fn copy_keys(
_ => panic!("<tests/common>: Could not access secret key"),
}
}

#[test]
fn audit() {
let mut feed = create_feed(50).unwrap();
feed.append(b"hello").unwrap();
feed.append(b"world").unwrap();
match feed.audit() {
Ok(audit_report) => {
assert_eq!(audit_report.valid_blocks, 2);
assert_eq!(audit_report.invalid_blocks, 0);
}
Err(e) => {
panic!(e);
}
}
}

#[test]
fn audit_bad_data() {
let mut dir = temp_dir();
dir.push("audit_bad_data");
let storage = Storage::new_disk(&dir).unwrap();
let mut feed = Feed::with_storage(storage).unwrap();
feed.append(b"hello").unwrap();
feed.append(b"world").unwrap();
let datapath = dir.join("data");
let mut hypercore_data = fs::OpenOptions::new()
.write(true)
.open(datapath)
.expect("Unable to open the hypercore's data file!");
hypercore_data
.write("yello".as_bytes())
.expect("Unable to corrupt the hypercore data file!");

match feed.audit() {
Ok(audit_report) => {
assert_eq!(audit_report.valid_blocks, 1);
assert_eq!(audit_report.invalid_blocks, 1);
// Ensure that audit has cleared up the invalid block
match feed.audit() {
Ok(audit_report) => {
assert_eq!(
audit_report.valid_blocks, 1,
"Audit did not clean up the invalid block!"
);
assert_eq!(
audit_report.invalid_blocks, 0,
"Audit did not clean up the invalid block!"
);
fs::remove_dir_all(dir)
.expect("Should be able to remove our temporary directory");
}
Err(e) => {
fs::remove_dir_all(dir)
.expect("Should be able to remove our temporary directory");
panic!(e);
}
}
}
Err(e) => {
fs::remove_dir_all(dir)
.expect("Should be able to remove our temporary directory");
panic!(e);
}
}
}