Skip to content

Commit 53f1ec5

Browse files
Merge pull request #606 from JeneaVranceanu/fix/bloom-filter-update
chore: EthereumBloomFilter improvement
2 parents 08daa88 + e70bea4 commit 53f1ec5

File tree

3 files changed

+152
-110
lines changed

3 files changed

+152
-110
lines changed

Sources/Core/Transaction/BloomFilter.swift

Lines changed: 0 additions & 108 deletions
This file was deleted.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//
2+
// EthereumBloomFilter.swift
3+
// web3swift
4+
//
5+
// Created by Alex Vlasov.
6+
// Copyright © 2018 Alex Vlasov. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import BigInt
11+
import CryptoSwift
12+
13+
/// A wrapper around a set of bytes that represent a Bloom filter.
14+
/// Similar implementation in Go language can be found [here in bloom9.go](https://github.com/ethereum/go-ethereum/blob/master/core/types/bloom9.go).
15+
///
16+
/// Bloom filter is used to reduce the cost in terms of memory consumption during the process of searching
17+
/// Bloom filter can be calculated for any set of data. In case of Ethereum blockchain it could be a set of addresses, event topics etc.
18+
///
19+
/// A definition of [Bloom filter](https://en.wikipedia.org/wiki/Bloom_filter#:~:text=A%20Bloom%20filter%20is%20a,a%20member%20of%20a%20set.).
20+
public struct EthereumBloomFilter {
21+
static let mask = BigUInt(2047)
22+
/// Bloom filter.
23+
public var bytes = Data(repeatElement(UInt8(0), count: 256))
24+
public init?(_ biguint: BigUInt) {
25+
guard let data = biguint.serialize().setLengthLeft(256) else {return nil}
26+
bytes = data
27+
}
28+
public init() {}
29+
public init(_ data: Data) {
30+
let padding = Data(repeatElement(UInt8(0), count: 256 - data.count))
31+
bytes = padding + data
32+
}
33+
public func asBigUInt() -> BigUInt {
34+
BigUInt(bytes)
35+
}
36+
}
37+
38+
extension EthereumBloomFilter {
39+
40+
// MARK: - Bloom filter calculation functions
41+
/// Calculates Bloom filter from Keccak-256 calculated from given `number`.
42+
/// - Parameter number: some number to calculate filter from.
43+
/// - Returns: Bloom filter.
44+
static func bloom9(_ number: BigUInt) -> BigUInt {
45+
bloom9(number.serialize())
46+
}
47+
48+
/// Calculates Bloom filter from Keccak-256 calculated from given `data`.
49+
/// - Parameter data: some data to calculate filter from, e.g. event's topic or address of a smart contract.
50+
/// - Returns: Bloom filter.
51+
static func bloom9(_ data: Data) -> BigUInt {
52+
// TODO: update to match this implementation https://manbytesgnu.com/eth-log-bloom.html
53+
// TODO: it will increase performance.
54+
let b = data.sha3(.keccak256)
55+
var result = BigUInt(1) <<
56+
((BigUInt(b[1]) + (BigUInt(b[0]) << 8)) & EthereumBloomFilter.mask)
57+
var nextPoint = BigUInt(1) <<
58+
((BigUInt(b[3]) + (BigUInt(b[2]) << 8)) & EthereumBloomFilter.mask)
59+
result = result | nextPoint
60+
nextPoint = BigUInt(1) <<
61+
((BigUInt(b[5]) + (BigUInt(b[4]) << 8)) & EthereumBloomFilter.mask)
62+
return result | nextPoint
63+
}
64+
65+
// MARK: - Bloom filter match functions
66+
public static func bloomLookup(_ bloom: EthereumBloomFilter, topic: Data) -> Bool {
67+
bloom.test(topic: topic)
68+
}
69+
70+
public static func bloomLookup(_ bloom: EthereumBloomFilter, topic: BigUInt) -> Bool {
71+
EthereumBloomFilter.bloomLookup(bloom, topic: topic.serialize())
72+
}
73+
74+
/// Check if topic is in the bloom filter.
75+
/// - Parameter topic: topic of an event as bytes;
76+
/// - Returns: `true` if topic is possibly in set, `false` if definitely not in set.
77+
public func test(topic: Data) -> Bool {
78+
let bin = asBigUInt()
79+
let comparison = EthereumBloomFilter.bloom9(topic)
80+
return bin & comparison == comparison
81+
}
82+
83+
/// Check if topic is in the bloom filter.
84+
/// - Parameter topic: topic of an event in `BigUInt`;
85+
/// - Returns: `true` if topic is possibly in set, `false` if definitely not in set.
86+
public func test(topic: BigUInt) -> Bool {
87+
test(topic: topic.serialize())
88+
}
89+
90+
public func lookup(_ topic: Data) -> Bool {
91+
EthereumBloomFilter.bloomLookup(self, topic: topic)
92+
}
93+
94+
// MARK: - Create Bloom filter from a list of logs
95+
/// Creates a bloom filter from ``EventLog/address`` and ``EventLog/topics``.
96+
/// - Parameter logs: event logs to create filter from.
97+
/// - Returns: calculated bloom filter represented as `BigUInt`.
98+
public static func logsToBloom(_ logs: [EventLog]) -> BigUInt {
99+
var bin = BigUInt(0)
100+
for log in logs {
101+
bin = bin | bloom9(log.address.addressData)
102+
for topic in log.topics {
103+
bin = bin | bloom9(topic)
104+
}
105+
}
106+
return bin
107+
}
108+
109+
/// Creates a bloom filter from arrays of logs from each ``TransactionReceipt``.
110+
/// ``TransactionReceipt/logs`` from each entry in `receipts` array are combined to create a bloom filter
111+
/// using ``EthereumBloomFilter/logsToBloom(_:)``.
112+
/// - Parameter receipts: an array of receipts to create bloom filter from.
113+
/// - Returns: bloom filter.
114+
public static func createBloom(_ receipts: [TransactionReceipt]) -> EthereumBloomFilter {
115+
var bin = BigUInt(0)
116+
for receipt in receipts {
117+
bin = bin | logsToBloom(receipt.logs)
118+
}
119+
return EthereumBloomFilter(bin)!
120+
}
121+
122+
// MARK: - Mutating functions
123+
public mutating func add(_ biguint: BigUInt) {
124+
let newBloomFilter = asBigUInt() | EthereumBloomFilter.bloom9(biguint)
125+
setBytes(newBloomFilter.serialize())
126+
}
127+
128+
public mutating func add(_ data: Data) {
129+
let newBloomFilter = asBigUInt() | EthereumBloomFilter.bloom9(data)
130+
setBytes(newBloomFilter.serialize())
131+
}
132+
133+
mutating func setBytes(_ data: Data) {
134+
if bytes.count < data.count {
135+
fatalError("bloom bytes are too big")
136+
}
137+
bytes = bytes[0 ..< data.count] + data
138+
}
139+
}

Tests/web3swiftTests/localTests/UncategorizedTests.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
import XCTest
88
import CryptoSwift
99
import BigInt
10-
import Core
1110

11+
@testable import Core
1212
@testable import web3swift
1313

1414
class UncategorizedTests: XCTestCase {
@@ -130,6 +130,17 @@ class UncategorizedTests: XCTestCase {
130130
print(user!)
131131
print(allMethods)
132132
}
133-
133+
134+
func testBloomFilterPerformance() throws {
135+
var uuids = [Data]()
136+
for _ in 0..<4000 {
137+
uuids.append(UUID().uuidString.data(using: .utf8)!)
138+
}
139+
measure {
140+
for bytes in uuids {
141+
let _ = EthereumBloomFilter.bloom9(bytes)
142+
}
143+
}
144+
}
134145
}
135146

0 commit comments

Comments
 (0)