Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Fetching logs by hash in blockchain database #8463

Merged
merged 12 commits into from
May 2, 2018
54 changes: 47 additions & 7 deletions ethcore/src/blockchain/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ pub trait BlockProvider {
fn blocks_with_bloom(&self, bloom: &Bloom, from_block: BlockNumber, to_block: BlockNumber) -> Vec<BlockNumber>;

/// Returns logs matching given filter.
fn logs<F>(&self, blocks: Vec<BlockNumber>, matches: F, limit: Option<usize>) -> Vec<LocalizedLogEntry>
fn logs<F>(&self, blocks: Vec<H256>, matches: F, limit: Option<usize>) -> Vec<LocalizedLogEntry>
where F: Fn(&LogEntry) -> bool + Send + Sync, Self: Sized;
}

Expand Down Expand Up @@ -332,16 +332,18 @@ impl BlockProvider for BlockChain {
.collect()
}

fn logs<F>(&self, mut blocks: Vec<BlockNumber>, matches: F, limit: Option<usize>) -> Vec<LocalizedLogEntry>
/// Returns logs matching given filter. The order of logs returned will be the same as the order of the blocks
/// provided. And it's the callers responsibility to sort blocks provided in advance.
fn logs<F>(&self, mut blocks: Vec<H256>, matches: F, limit: Option<usize>) -> Vec<LocalizedLogEntry>
where F: Fn(&LogEntry) -> bool + Send + Sync, Self: Sized {
// sort in reverse order
blocks.sort_by(|a, b| b.cmp(a));
blocks.reverse();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment that blocks must be already sorted would be good

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added!


let mut logs = blocks
.chunks(128)
.flat_map(move |blocks_chunk| {
blocks_chunk.into_par_iter()
.filter_map(|number| self.block_hash(*number).map(|hash| (*number, hash)))
.filter_map(|hash| self.block_number(&hash).map(|r| (r, hash)))
.filter_map(|(number, hash)| self.block_receipts(&hash).map(|r| (number, hash, r.receipts)))
.filter_map(|(number, hash, receipts)| self.block_body(&hash).map(|ref b| (number, hash, receipts, b.transaction_hashes())))
.flat_map(|(number, hash, mut receipts, mut hashes)| {
Expand All @@ -368,7 +370,7 @@ impl BlockProvider for BlockChain {
.enumerate()
.map(move |(i, log)| LocalizedLogEntry {
entry: log,
block_hash: hash,
block_hash: *hash,
block_number: number,
transaction_hash: tx_hash,
// iterating in reverse order
Expand Down Expand Up @@ -1933,17 +1935,33 @@ mod tests {
value: 103.into(),
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
}.sign(&secret(), None);
let t4 = Transaction {
nonce: 0.into(),
gas_price: 0.into(),
gas: 100_000.into(),
action: Action::Create,
value: 104.into(),
data: "601080600c6000396000f3006000355415600957005b60203560003555".from_hex().unwrap(),
}.sign(&secret(), None);
let tx_hash1 = t1.hash();
let tx_hash2 = t2.hash();
let tx_hash3 = t3.hash();
let tx_hash4 = t4.hash();

let genesis = BlockBuilder::genesis();
let b1 = genesis.add_block_with_transactions(vec![t1, t2]);
let b2 = b1.add_block_with_transactions(iter::once(t3));
let b3 = genesis.add_block_with(|| BlockOptions {
transactions: vec![t4.clone()],
difficulty: U256::from(9),
..Default::default()
}); // Branch block
let b1_hash = b1.last().hash();
let b1_number = b1.last().number();
let b2_hash = b2.last().hash();
let b2_number = b2.last().number();
let b3_hash = b3.last().hash();
let b3_number = b3.last().number();

let db = new_db();
let bc = new_chain(&genesis.last().encoded(), db.clone());
Expand Down Expand Up @@ -1974,10 +1992,21 @@ mod tests {
],
}
]);
insert_block(&db, &bc, &b3.last().encoded(), vec![
Receipt {
outcome: TransactionOutcome::StateRoot(H256::default()),
gas_used: 10_000.into(),
log_bloom: Default::default(),
logs: vec![
LogEntry { address: Default::default(), topics: vec![], data: vec![5], },
],
}
]);

// when
let logs1 = bc.logs(vec![1, 2], |_| true, None);
let logs2 = bc.logs(vec![1, 2], |_| true, Some(1));
let logs1 = bc.logs(vec![b1_hash, b2_hash], |_| true, None);
let logs2 = bc.logs(vec![b1_hash, b2_hash], |_| true, Some(1));
let logs3 = bc.logs(vec![b3_hash], |_| true, None);

// then
assert_eq!(logs1, vec![
Expand Down Expand Up @@ -2029,6 +2058,17 @@ mod tests {
log_index: 0,
}
]);
assert_eq!(logs3, vec![
LocalizedLogEntry {
entry: LogEntry { address: Default::default(), topics: vec![], data: vec![5] },
block_hash: b3_hash,
block_number: b3_number,
transaction_hash: tx_hash4,
transaction_index: 0,
transaction_log_index: 0,
log_index: 0,
}
]);
}

#[test]
Expand Down
101 changes: 88 additions & 13 deletions ethcore/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1853,21 +1853,96 @@ impl BlockChainClient for Client {
}

fn logs(&self, filter: Filter) -> Vec<LocalizedLogEntry> {
let (from, to) = match (self.block_number_ref(&filter.from_block), self.block_number_ref(&filter.to_block)) {
(Some(from), Some(to)) => (from, to),
_ => return Vec::new(),
};
macro_rules! return_empty_if_none {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be replaced with ? if the whole function would return Option<Vec<_>>

( $v: expr ) => (
match $v {
Some(value) => value,
None => return Vec::new(),
}
)
}

let chain = self.chain.read();
let blocks = filter.bloom_possibilities().iter()
.map(move |bloom| {
chain.blocks_with_bloom(bloom, from, to)
})
.flat_map(|m| m)
// remove duplicate elements
.collect::<HashSet<u64>>()
.into_iter()
.collect::<Vec<u64>>();

// First, check whether `filter.from_block` and `filter.to_block` is on the canon chain. If so, we can use the
// optimized version.
let is_canon = |id| {
match id {
// If it is referred by number, then it is always on the canon chain.
&BlockId::Earliest | &BlockId::Latest | &BlockId::Number(_) => Some(true),
// If it is referred by hash, we see whether a hash -> number -> hash conversion gives us the same
// result.
&BlockId::Hash(hash) =>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I would move is_canon(H256) -> bool to blockhain.rs (it's also used in couple of places inside that file (epochs)).
  2. This would become just chain.is_canon(self.block_hash(id)?)

Some(hash == chain.block_hash(chain.block_number(&hash)?)?),
}
};

let blocks = if return_empty_if_none!(is_canon(&filter.from_block)) && return_empty_if_none!(is_canon(&filter.to_block)) {
// If we are on the canon chain, use bloom filter to fetch required hashes.
let from = return_empty_if_none!(self.block_number_ref(&filter.from_block));
let to = return_empty_if_none!(self.block_number_ref(&filter.to_block));

let mut numbers = filter.bloom_possibilities().iter()
.map(|bloom| {
chain.blocks_with_bloom(bloom, from, to)
})
.flat_map(|m| m)
// remove duplicate elements
.collect::<HashSet<u64>>()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of HashSet + sort we could collect to BTreeSet<u64> and then convert to Vec<H256>

.into_iter()
.collect::<Vec<u64>>();

numbers.sort_by(|a, b| a.cmp(b));
numbers.into_iter()
.filter_map(|n| chain.block_hash(n))
.collect::<Vec<H256>>()

} else {
// Otherwise, we use a slower version that finds a link between from_block and to_block.
let get_hash = |id| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That function already exists as Self::block_hash(chain, id)?

match id {
&BlockId::Earliest | &BlockId::Latest | &BlockId::Number(_) =>
chain.block_hash(self.block_number_ref(id)?),
&BlockId::Hash(hash) => Some(hash),
}
};

let from_hash = return_empty_if_none!(get_hash(&filter.from_block));
let from_number = return_empty_if_none!(chain.block_number(&from_hash));
let to_hash = return_empty_if_none!(get_hash(&filter.to_block));
let to_number = return_empty_if_none!(chain.block_number(&to_hash));

let blooms = filter.bloom_possibilities();
let bloom_match = |header: &encoded::Header| {
blooms.iter().any(|bloom| header.log_bloom().contains_bloom(bloom))
};

let mut blocks = Vec::new();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd wrap this into a block and use loop

let (blocks, last_hash) = {
  let mut blocks = Vec::new();
  let mut current_hash = to_hash;
  loop {
    let header = return_empty_if_none!(chain.block_header_data(&current_hash));
	if bloom_match(&header) {
	  blocks.push(current_hash);
	}
    // Stop if `from` block is reached.
    if header.number() <= from_number {
      break;
    }
    current_hash = header.parent_hash();
  }
  (blocks, current_hash)
};

// check if we've actually reached the expected `from` block.
if last_hash != from_hash || blocks.is_empty() {
  return Vec::new();
}

let mut current_hash = to_hash;
let mut current_number = to_number;

if bloom_match(&return_empty_if_none!(chain.block_header_data(&current_hash))) {
blocks.push(current_hash);
}

while current_number > from_number {
let header = return_empty_if_none!(chain.block_header_data(&current_hash));

if bloom_match(&header) {
blocks.push(current_hash);
}

current_hash = header.parent_hash();
current_number = current_number - 1;
}

if current_hash != from_hash || blocks.is_empty() {
return Vec::new();
}

blocks.reverse();
blocks
};

self.chain.read().logs(blocks, |entry| filter.matches(entry), filter.limit)
}
Expand Down
2 changes: 1 addition & 1 deletion ethcore/src/verification/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ mod tests {
unimplemented!()
}

fn logs<F>(&self, _blocks: Vec<BlockNumber>, _matches: F, _limit: Option<usize>) -> Vec<LocalizedLogEntry>
fn logs<F>(&self, _blocks: Vec<H256>, _matches: F, _limit: Option<usize>) -> Vec<LocalizedLogEntry>
where F: Fn(&LogEntry) -> bool, Self: Sized {
unimplemented!()
}
Expand Down