implement provider for client
This commit is contained in:
parent
3c7533831e
commit
abf39fde0a
@ -68,6 +68,7 @@ use factory::Factories;
|
|||||||
use rlp::{decode, View, UntrustedRlp};
|
use rlp::{decode, View, UntrustedRlp};
|
||||||
use state_db::StateDB;
|
use state_db::StateDB;
|
||||||
use rand::OsRng;
|
use rand::OsRng;
|
||||||
|
use light::{self, request};
|
||||||
|
|
||||||
// re-export
|
// re-export
|
||||||
pub use types::blockchain_info::BlockChainInfo;
|
pub use types::blockchain_info::BlockChainInfo;
|
||||||
@ -1317,32 +1318,155 @@ impl MayPanic for Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Implementation of a light client data provider for a client.
|
||||||
|
impl light::Provider for Client {
|
||||||
|
fn chain_info(&self) -> BlockChainInfo {
|
||||||
|
BlockChainClient::chain_info(self)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
|
||||||
fn should_not_cache_details_before_commit() {
|
self.tree_route(a, b).map(|route| route.index as u64)
|
||||||
use tests::helpers::*;
|
}
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
|
||||||
|
|
||||||
let client = generate_dummy_client(0);
|
fn earliest_state(&self) -> Option<u64> {
|
||||||
let genesis = client.chain_info().best_block_hash;
|
Some(self.pruning_info().earliest_state)
|
||||||
let (new_hash, new_block) = get_good_dummy_block_hash();
|
}
|
||||||
|
|
||||||
let go = {
|
fn block_headers(&self, req: request::Headers) -> Vec<Bytes> {
|
||||||
// Separate thread uncommited transaction
|
let best_num = self.chain.read().best_block_number();
|
||||||
let go = Arc::new(AtomicBool::new(false));
|
let start_num = req.block_num;
|
||||||
let go_thread = go.clone();
|
|
||||||
let another_client = client.reference().clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone());
|
|
||||||
another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new());
|
|
||||||
go_thread.store(true, Ordering::SeqCst);
|
|
||||||
});
|
|
||||||
go
|
|
||||||
};
|
|
||||||
|
|
||||||
while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); }
|
match self.block_hash(BlockID::Number(req.block_num)) {
|
||||||
|
Some(hash) if hash == req.block_hash => {}
|
||||||
|
_=> {
|
||||||
|
trace!(target: "les_provider", "unknown/non-canonical start block in header request: {:?}", (req.block_num, req.block_hash));
|
||||||
|
return vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
assert!(client.tree_route(&genesis, &new_hash).is_none());
|
(0u64..req.max as u64)
|
||||||
|
.map(|x: u64| x.saturating_mul(req.skip))
|
||||||
|
.take_while(|x| if req.reverse { x < &start_num } else { best_num - start_num < *x })
|
||||||
|
.map(|x| if req.reverse { start_num - x } else { start_num + x })
|
||||||
|
.map(|x| self.block_header(BlockID::Number(x)))
|
||||||
|
.flat_map(|x| x)
|
||||||
|
.fuse() // collect no more beyond the first `None`
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_bodies(&self, req: request::Bodies) -> Vec<Bytes> {
|
||||||
|
use ids::BlockID;
|
||||||
|
|
||||||
|
req.block_hashes.into_iter()
|
||||||
|
.map(|hash| self.block_body(BlockID::Hash(hash)))
|
||||||
|
.map(|body| body.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receipts(&self, req: request::Receipts) -> Vec<Bytes> {
|
||||||
|
req.block_hashes.into_iter()
|
||||||
|
.map(|hash| self.block_receipts(&hash))
|
||||||
|
.map(|receipts| receipts.unwrap_or_else(|| ::rlp::EMPTY_LIST_RLP.to_vec()))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proofs(&self, req: request::StateProofs) -> Vec<Bytes> {
|
||||||
|
use rlp::{EMPTY_LIST_RLP, RlpStream, Stream};
|
||||||
|
|
||||||
|
let mut results = Vec::with_capacity(req.requests.len());
|
||||||
|
|
||||||
|
for request in req.requests {
|
||||||
|
let state = match self.state_at(BlockID::Hash(request.block)) {
|
||||||
|
Some(state) => state,
|
||||||
|
None => {
|
||||||
|
trace!(target: "light_provider", "state for {} not available", request.block);
|
||||||
|
results.push(EMPTY_LIST_RLP.to_vec());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = match request.key2 {
|
||||||
|
Some(storage_key) => state.prove_storage(request.key1, storage_key, request.from_level),
|
||||||
|
None => state.prove_account(request.key1, request.from_level),
|
||||||
|
};
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(records) => {
|
||||||
|
let mut stream = RlpStream::new_list(records.len());
|
||||||
|
for record in records {
|
||||||
|
stream.append_raw(&record, 1);
|
||||||
|
}
|
||||||
|
results.push(stream.out())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(target: "light_provider", "encountered error {} while forming proof of state at {}", e, request.block);
|
||||||
|
results.push(EMPTY_LIST_RLP.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contract_code(&self, req: request::ContractCodes) -> Vec<Bytes> {
|
||||||
|
req.code_requests.into_iter()
|
||||||
|
.map(|req| {
|
||||||
|
self.state_at(BlockID::Hash(req.block_hash))
|
||||||
|
.map(|state| {
|
||||||
|
match state.code_by_address_hash(req.account_key) {
|
||||||
|
Ok(code) => code.unwrap_or_else(Vec::new),
|
||||||
|
Err(e) => {
|
||||||
|
debug!(target: "light_provider", "encountered error {} while fetching code.", e);
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).unwrap_or_else(Vec::new)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes> {
|
||||||
|
req.requests.into_iter().map(|_| ::rlp::EMPTY_LIST_RLP.to_vec()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
||||||
|
BlockChainClient::pending_transactions(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_not_cache_details_before_commit() {
|
||||||
|
use client::BlockChainClient;
|
||||||
|
use tests::helpers::*;
|
||||||
|
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use util::kvdb::DBTransaction;
|
||||||
|
|
||||||
|
let client = generate_dummy_client(0);
|
||||||
|
let genesis = client.chain_info().best_block_hash;
|
||||||
|
let (new_hash, new_block) = get_good_dummy_block_hash();
|
||||||
|
|
||||||
|
let go = {
|
||||||
|
// Separate thread uncommited transaction
|
||||||
|
let go = Arc::new(AtomicBool::new(false));
|
||||||
|
let go_thread = go.clone();
|
||||||
|
let another_client = client.reference().clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut batch = DBTransaction::new(&*another_client.chain.read().db().clone());
|
||||||
|
another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new());
|
||||||
|
go_thread.store(true, Ordering::SeqCst);
|
||||||
|
});
|
||||||
|
go
|
||||||
|
};
|
||||||
|
|
||||||
|
while !go.load(Ordering::SeqCst) { thread::park_timeout(Duration::from_millis(5)); }
|
||||||
|
|
||||||
|
assert!(client.tree_route(&genesis, &new_hash).is_none());
|
||||||
|
}
|
||||||
|
}
|
@ -101,7 +101,7 @@ impl Provider for Client {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn code(&self, _req: request::ContractCodes) -> Vec<Bytes> {
|
fn contract_code(&self, _req: request::ContractCodes) -> Vec<Bytes> {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,4 +35,5 @@ pub mod client;
|
|||||||
pub mod net;
|
pub mod net;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
|
|
||||||
|
pub use self::provider::Provider;
|
||||||
pub use types::les_request as request;
|
pub use types::les_request as request;
|
@ -17,11 +17,9 @@
|
|||||||
//! A provider for the LES protocol. This is typically a full node, who can
|
//! A provider for the LES protocol. This is typically a full node, who can
|
||||||
//! give as much data as necessary to its peers.
|
//! give as much data as necessary to its peers.
|
||||||
|
|
||||||
use client::BlockChainClient;
|
|
||||||
use transaction::SignedTransaction;
|
use transaction::SignedTransaction;
|
||||||
use blockchain_info::BlockChainInfo;
|
use blockchain_info::BlockChainInfo;
|
||||||
|
|
||||||
use rlp::EMPTY_LIST_RLP;
|
|
||||||
use util::{Bytes, H256};
|
use util::{Bytes, H256};
|
||||||
|
|
||||||
use light::request;
|
use light::request;
|
||||||
@ -65,65 +63,11 @@ pub trait Provider: Send + Sync {
|
|||||||
fn proofs(&self, req: request::StateProofs) -> Vec<Bytes>;
|
fn proofs(&self, req: request::StateProofs) -> Vec<Bytes>;
|
||||||
|
|
||||||
/// Provide contract code for the specified (block_hash, account_hash) pairs.
|
/// Provide contract code for the specified (block_hash, account_hash) pairs.
|
||||||
fn code(&self, req: request::ContractCodes) -> Vec<Bytes>;
|
fn contract_code(&self, req: request::ContractCodes) -> Vec<Bytes>;
|
||||||
|
|
||||||
/// Provide header proofs from the Canonical Hash Tries.
|
/// Provide header proofs from the Canonical Hash Tries.
|
||||||
fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes>;
|
fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes>;
|
||||||
|
|
||||||
/// Provide pending transactions.
|
/// Provide pending transactions.
|
||||||
fn pending_transactions(&self) -> Vec<SignedTransaction>;
|
fn pending_transactions(&self) -> Vec<SignedTransaction>;
|
||||||
}
|
|
||||||
|
|
||||||
// TODO [rob] move into trait definition file after ethcore crate
|
|
||||||
// is split up. ideally `ethcore-light` will be between `ethcore-blockchain`
|
|
||||||
// and `ethcore-client`
|
|
||||||
impl<T: BlockChainClient + ?Sized> Provider for T {
|
|
||||||
fn chain_info(&self) -> BlockChainInfo {
|
|
||||||
BlockChainClient::chain_info(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reorg_depth(&self, a: &H256, b: &H256) -> Option<u64> {
|
|
||||||
self.tree_route(a, b).map(|route| route.index as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn earliest_state(&self) -> Option<u64> {
|
|
||||||
Some(self.pruning_info().earliest_state)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block_headers(&self, req: request::Headers) -> Vec<Bytes> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block_bodies(&self, req: request::Bodies) -> Vec<Bytes> {
|
|
||||||
use ids::BlockID;
|
|
||||||
|
|
||||||
req.block_hashes.into_iter()
|
|
||||||
.map(|hash| self.block_body(BlockID::Hash(hash)))
|
|
||||||
.map(|body| body.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn receipts(&self, req: request::Receipts) -> Vec<Bytes> {
|
|
||||||
req.block_hashes.into_iter()
|
|
||||||
.map(|hash| self.block_receipts(&hash))
|
|
||||||
.map(|receipts| receipts.unwrap_or_else(|| EMPTY_LIST_RLP.to_vec()))
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn proofs(&self, req: request::StateProofs) -> Vec<Bytes> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn code(&self, req: request::ContractCodes) -> Vec<Bytes> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn header_proofs(&self, req: request::HeaderProofs) -> Vec<Bytes> {
|
|
||||||
// TODO: [rob] implement CHT stuff on `ethcore` side.
|
|
||||||
req.requests.into_iter().map(|_| EMPTY_LIST_RLP.to_vec()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pending_transactions(&self) -> Vec<SignedTransaction> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -437,6 +437,27 @@ impl Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// light client storage proof.
|
||||||
|
impl Account {
|
||||||
|
/// Prove a storage key's existence or nonexistence in the account's storage
|
||||||
|
/// trie.
|
||||||
|
/// `storage_key` is the hash of the desired storage key, meaning
|
||||||
|
/// this will only work correctly under a secure trie.
|
||||||
|
/// Returns a merkle proof of the storage trie node with all nodes before `from_level`
|
||||||
|
/// omitted.
|
||||||
|
pub fn prove_storage(&self, db: &HashDB, storage_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
|
||||||
|
use util::trie::{Trie, TrieDB};
|
||||||
|
use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
|
||||||
|
|
||||||
|
let mut recorder = TrieRecorder::with_depth(from_level);
|
||||||
|
|
||||||
|
let trie = try!(TrieDB::new(db, &self.storage_root));
|
||||||
|
let _ = try!(trie.get_recorded(&storage_key, &mut recorder));
|
||||||
|
|
||||||
|
Ok(recorder.drain().into_iter().map(|r| r.data).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Account {
|
impl fmt::Debug for Account {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{:?}", PodAccount::from_account(self))
|
write!(f, "{:?}", PodAccount::from_account(self))
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
use util::*;
|
|
||||||
use receipt::Receipt;
|
use receipt::Receipt;
|
||||||
use engines::Engine;
|
use engines::Engine;
|
||||||
use env_info::EnvInfo;
|
use env_info::EnvInfo;
|
||||||
@ -30,6 +30,9 @@ use types::state_diff::StateDiff;
|
|||||||
use transaction::SignedTransaction;
|
use transaction::SignedTransaction;
|
||||||
use state_db::StateDB;
|
use state_db::StateDB;
|
||||||
|
|
||||||
|
use util::*;
|
||||||
|
use util::trie::recorder::{Recorder, BasicRecorder as TrieRecorder};
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod substate;
|
mod substate;
|
||||||
|
|
||||||
@ -751,6 +754,53 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LES state proof implementations.
|
||||||
|
impl State {
|
||||||
|
/// Prove an account's existence or nonexistence in the state trie.
|
||||||
|
/// Returns a merkle proof of the account's trie node with all nodes before `from_level`
|
||||||
|
/// omitted or an encountered trie error.
|
||||||
|
/// Requires a secure trie to be used for accurate results.
|
||||||
|
/// `account_key` == sha3(address)
|
||||||
|
pub fn prove_account(&self, account_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
|
||||||
|
let mut recorder = TrieRecorder::with_depth(from_level);
|
||||||
|
let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root));
|
||||||
|
let _ = try!(trie.get_recorded(&account_key, &mut recorder));
|
||||||
|
|
||||||
|
Ok(recorder.drain().into_iter().map(|r| r.data).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prove an account's storage key's existence or nonexistence in the state.
|
||||||
|
/// Returns a merkle proof of the account's storage trie with all nodes before
|
||||||
|
/// `from_level` omitted. Requires a secure trie to be used for correctness.
|
||||||
|
/// `account_key` == sha3(address)
|
||||||
|
/// `storage_key` == sha3(key)
|
||||||
|
pub fn prove_storage(&self, account_key: H256, storage_key: H256, from_level: u32) -> Result<Vec<Bytes>, Box<TrieError>> {
|
||||||
|
// TODO: probably could look into cache somehow but it's keyed by
|
||||||
|
// address, not sha3(address).
|
||||||
|
let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root));
|
||||||
|
let acc = match try!(trie.get(&account_key)) {
|
||||||
|
Some(rlp) => Account::from_rlp(&rlp),
|
||||||
|
None => return Ok(Vec::new()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key);
|
||||||
|
acc.prove_storage(account_db.as_hashdb(), storage_key, from_level)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get code by address hash.
|
||||||
|
/// Only works when backed by a secure trie.
|
||||||
|
pub fn code_by_address_hash(&self, account_key: H256) -> Result<Option<Bytes>, Box<TrieError>> {
|
||||||
|
let trie = try!(TrieDB::new(self.db.as_hashdb(), &self.root));
|
||||||
|
let mut acc = match try!(trie.get(&account_key)) {
|
||||||
|
Some(rlp) => Account::from_rlp(&rlp),
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let account_db = self.factories.accountdb.readonly(self.db.as_hashdb(), account_key);
|
||||||
|
Ok(acc.cache_code(account_db.as_hashdb()).map(|c| (&*c).clone()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for State {
|
impl fmt::Debug for State {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{:?}", self.cache.borrow())
|
write!(f, "{:?}", self.cache.borrow())
|
||||||
|
@ -29,7 +29,7 @@ pub struct Headers {
|
|||||||
/// The maximum amount of headers which can be returned.
|
/// The maximum amount of headers which can be returned.
|
||||||
pub max: usize,
|
pub max: usize,
|
||||||
/// The amount of headers to skip between each response entry.
|
/// The amount of headers to skip between each response entry.
|
||||||
pub skip: usize,
|
pub skip: u64,
|
||||||
/// Whether the headers should proceed in falling number from the initial block.
|
/// Whether the headers should proceed in falling number from the initial block.
|
||||||
pub reverse: bool,
|
pub reverse: bool,
|
||||||
}
|
}
|
||||||
|
@ -97,11 +97,11 @@ pub trait Trie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Query the value of the given key in this trie while recording visited nodes
|
/// Query the value of the given key in this trie while recording visited nodes
|
||||||
/// to the given recorder. If the query fails, the nodes passed to the recorder are unspecified.
|
/// to the given recorder. If the query encounters an error, the nodes passed to the recorder are unspecified.
|
||||||
fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> Result<Option<DBValue>>
|
fn get_recorded<'a, 'b, R: 'b>(&'a self, key: &'b [u8], rec: &'b mut R) -> Result<Option<DBValue>>
|
||||||
where 'a: 'b, R: Recorder;
|
where 'a: 'b, R: Recorder;
|
||||||
|
|
||||||
/// Returns an iterator over elements of trie.
|
/// Returns a depth-first iterator over the elements of the trie.
|
||||||
fn iter<'a>(&'a self) -> Result<Box<Iterator<Item = TrieItem> + 'a>>;
|
fn iter<'a>(&'a self) -> Result<Box<Iterator<Item = TrieItem> + 'a>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,5 +235,5 @@ impl TrieFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true iff the trie DB is a fat DB (allows enumeration of keys).
|
/// Returns true iff the trie DB is a fat DB (allows enumeration of keys).
|
||||||
pub fn is_fat(&self) -> bool { self.spec == TrieSpec::Fat }
|
pub fn is_fat(&self) -> bool { self.spec == TrieSpec::Fat }
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ pub struct Record {
|
|||||||
/// These are used to record which nodes are visited during a trie query.
|
/// These are used to record which nodes are visited during a trie query.
|
||||||
/// Inline nodes are not to be recorded, as they are contained within their parent.
|
/// Inline nodes are not to be recorded, as they are contained within their parent.
|
||||||
pub trait Recorder {
|
pub trait Recorder {
|
||||||
|
|
||||||
/// Record that the given node has been visited.
|
/// Record that the given node has been visited.
|
||||||
///
|
///
|
||||||
/// The depth parameter is the depth of the visited node, with the root node having depth 0.
|
/// The depth parameter is the depth of the visited node, with the root node having depth 0.
|
||||||
@ -58,6 +57,7 @@ impl Recorder for NoOp {
|
|||||||
|
|
||||||
/// A simple recorder. Does nothing fancy but fulfills the `Recorder` interface
|
/// A simple recorder. Does nothing fancy but fulfills the `Recorder` interface
|
||||||
/// properly.
|
/// properly.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct BasicRecorder {
|
pub struct BasicRecorder {
|
||||||
nodes: Vec<Record>,
|
nodes: Vec<Record>,
|
||||||
min_depth: u32,
|
min_depth: u32,
|
||||||
|
Loading…
Reference in New Issue
Block a user