blob: 47793576d2d1781ce3f5d730d46d4319ee8be3c6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE
* file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file
* to You 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.
*/
package org.apache.tuweni.eth;
import static com.google.common.base.Preconditions.checkArgument;
import static org.apache.tuweni.crypto.Hash.keccak256;
import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.bytes.Bytes32;
import org.apache.tuweni.bytes.MutableBytes;
import org.apache.tuweni.rlp.RLPReader;
import java.util.Collection;
/**
* Bloom filter implementation for storing persistent logs, describes a 2048-bit representation of all log entries of a
* transaction, except data. Sets the bits of the 2048 byte array, where indices are given by: The lower order 11-bits,
* of the first three double-bytes, of the SHA3, of each value. For instance the address
* "0x0F572E5295C57F15886F9B263E2F6D2D6C7B5EC6" results in the KECCAK256 hash
* "bd2b01afcd27800b54d2179edc49e2bffde5078bb6d0b204694169b1643fb108", of which the corresponding double-bytes are:
* bd2b, 01af, cd27, corresponding to the following bits in the bloom filter: 1323, 431, 1319
*/
public final class LogsBloomFilter {
private static final int LEAST_SIGNIFICANT_THREE_BITS = 0x7;
/**
* Creates a bloom filter corresponding to the provide log series.
*
* @param logs the logs to populate the bloom filter with.
* @return the newly created bloom filter populated with the logs from {@code logs}.
*/
public static LogsBloomFilter compute(final Collection<Log> logs) {
final LogsBloomFilter bloom = new LogsBloomFilter();
logs.forEach(bloom::insertLog);
return bloom;
}
/**
* Creates a bloom filter from the given RLP-encoded input.
*
* @param input The input to read from
* @return the input's corresponding bloom filter
*/
public static LogsBloomFilter readFrom(final RLPReader input) {
return new LogsBloomFilter(input.readValue());
}
private final MutableBytes data;
public LogsBloomFilter() {
this.data = MutableBytes.create(256);
}
public LogsBloomFilter(Bytes data) {
checkArgument(
data.size() == 256,
"Invalid size for bloom filter backing array: expected 256 but got %s",
data.size());
this.data = data.mutableCopy();
}
public Bytes toBytes() {
return data;
}
public void insertLog(final Log log) {
setBits(keccak256(log.logger().toBytes()));
for (final Bytes32 topic : log.topics()) {
setBits(keccak256(topic));
}
}
public void digest(final LogsBloomFilter other) {
for (int i = 0; i < data.size(); ++i) {
data.set(i, (byte) ((data.get(i) | other.data.get(i)) & 0xFF));
}
}
/**
* Discover the low order 11-bits, of the first three double-bytes, of the SHA3 hash, of each value and update the
* bloom filter accordingly.
*
* @param hashValue The hash of the log item.
*/
private void setBits(final Bytes hashValue) {
for (int counter = 0; counter < 6; counter += 2) {
final int setBloomBit =
((hashValue.get(counter) & LEAST_SIGNIFICANT_THREE_BITS) << 8) + (hashValue.get(counter + 1) & 0xFF);
setBit(setBloomBit);
}
}
private void setBit(final int index) {
final int byteIndex = 256 - 1 - index / 8;
final int bitIndex = index % 8;
data.set(byteIndex, (byte) (data.get(byteIndex) | (1 << bitIndex)));
}
@Override
public String toString() {
return data.toString();
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof LogsBloomFilter)) {
return false;
}
final LogsBloomFilter other = (LogsBloomFilter) obj;
return data.equals(other.data);
}
@Override
public int hashCode() {
return data.hashCode();
}
}