nimbus-eth1/nimbus/rpc/filters.nim

169 lines
4.8 KiB
Nim

# Nimbus
# Copyright (c) 2022-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/options,
eth/common/[eth_types, eth_types_rlp],
eth/bloom as bFilter,
stint,
../beacon/web3_eth_conv,
./rpc_types
export rpc_types
type
BlockHeader = eth_types.BlockHeader
{.push raises: [].}
proc topicToDigest(t: seq[eth_types.Topic]): seq[Web3Topic] =
var resSeq: seq[Web3Topic] = @[]
for top in t:
let ht = Web3Topic(top)
resSeq.add(ht)
return resSeq
proc deriveLogs*(header: BlockHeader, transactions: seq[Transaction], receipts: seq[Receipt]): seq[FilterLog] =
## Derive log fields, does not deal with pending log, only the logs with
## full data set
doAssert(len(transactions) == len(receipts))
var resLogs: seq[FilterLog] = @[]
var logIndex = 0
for i, receipt in receipts:
for log in receipt.logs:
let filterLog = FilterLog(
# TODO investigate how to handle this field
# - in nimbus info about log removel would need to be kept at synchronization
# level, to keep track about potential re-orgs
# - in fluffy there is no concept of re-org
removed: false,
logIndex: some(w3Qty(logIndex)),
transactionIndex: some(w3Qty(i)),
transactionHash: some(w3Hash transactions[i].rlpHash),
blockHash: some(w3Hash header.blockHash),
blockNumber: some(w3BlockNumber(header.blockNumber)),
address: w3Addr log.address,
data: log.data,
# TODO topics should probably be kept as Hash256 in receipts
topics: topicToDigest(log.topics)
)
inc logIndex
resLogs.add(filterLog)
return resLogs
func participateInFilter(x: AddressOrList): bool =
if x.kind == slkNull:
return false
if x.kind == slkList:
if x.list.len == 0:
return false
true
proc bloomFilter*(
bloom: eth_types.BloomFilter,
addresses: AddressOrList,
topics: seq[TopicOrList]): bool =
let bloomFilter = bFilter.BloomFilter(value: StUint[2048].fromBytesBE(bloom))
if addresses.participateInFilter():
var addrIncluded: bool = false
if addresses.kind == slkSingle:
addrIncluded = bloomFilter.contains(addresses.single.bytes)
elif addresses.kind == slkList:
for address in addresses.list:
if bloomFilter.contains(address.bytes):
addrIncluded = true
break
if not addrIncluded:
return false
for sub in topics:
if sub.kind == slkNull:
# catch all wildcard
continue
var topicIncluded = false
if sub.kind == slkSingle:
if bloomFilter.contains(sub.single.bytes):
topicIncluded = true
else:
topicIncluded = sub.list.len == 0
for topic in sub.list:
# This is is quite not obvious, but passing topic as MDigest256 fails, as
# it does not use internal keccak256 hashing. To achieve desired semantics,
# we need use digest bare bytes so that they will be properly kec256 hashes
if bloomFilter.contains(topic.bytes):
topicIncluded = true
break
if not topicIncluded:
return false
return true
proc headerBloomFilter*(
header: BlockHeader,
addresses: AddressOrList,
topics: seq[TopicOrList]): bool =
return bloomFilter(header.bloom, addresses, topics)
proc matchTopics(log: FilterLog, topics: seq[TopicOrList]): bool =
for i, sub in topics:
if sub.kind == slkNull:
# null subtopic i.e it matches all possible move to nex
continue
var match = false
if sub.kind == slkSingle:
match = log.topics[i] == sub.single
else:
# treat empty as wildcard, although caller should rather use none kind of
# option to indicate that. If nim would have NonEmptySeq type that would be
# use case for it.
match = sub.list.len == 0
for topic in sub.list:
if log.topics[i] == topic:
match = true
break
if not match:
return false
return true
proc filterLogs*(
logs: openArray[FilterLog],
addresses: AddressOrList,
topics: seq[TopicOrList]): seq[FilterLog] =
var filteredLogs: seq[FilterLog] = newSeq[FilterLog]()
for log in logs:
if addresses.kind == slkSingle and (addresses.single != log.address):
continue
if addresses.kind == slkList and
addresses.list.len > 0 and
(not addresses.list.contains(log.address)):
continue
if len(topics) > len(log.topics):
continue
if not matchTopics(log, topics):
continue
filteredLogs.add(log)
return filteredLogs