| /* |
| * 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(); |
| } |
| } |