eth_subscribe multiple topics with OR logic (#682)
* eth_subscribe multiple topics with OR logic * test Filter by topics with OR logic
This commit is contained in:
@@ -134,7 +134,7 @@ open class EthereumEgressSubscription(
|
||||
|
||||
data class LogsRequest(
|
||||
val address: List<Address>,
|
||||
val topics: List<Hex32?>,
|
||||
val topics: List<List<Hex32>?>,
|
||||
)
|
||||
|
||||
fun readLogsRequest(params: Map<String, Any?>): LogsRequest {
|
||||
@@ -160,24 +160,35 @@ open class EthereumEgressSubscription(
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
val topics: List<Hex32?> = if (params.containsKey("topics")) {
|
||||
when (val topics = params["topics"]) {
|
||||
|
||||
val topics: List<List<Hex32>?> = if (params.containsKey("topics")) {
|
||||
when (val rawTopics = params["topics"]) {
|
||||
is String -> try {
|
||||
listOf(Hex32.from(topics))
|
||||
listOf(listOf(Hex32.from(rawTopics)))
|
||||
} catch (t: Throwable) {
|
||||
log.debug("Ignore invalid topic: $topics with error ${t.message}")
|
||||
log.debug("Ignore invalid topic: $rawTopics with error ${t.message}")
|
||||
emptyList()
|
||||
}
|
||||
is Collection<*> -> topics.map { topic ->
|
||||
try {
|
||||
when (topic) {
|
||||
null -> null
|
||||
is Collection<*> -> topic.firstOrNull()?.toString()?.let { Hex32.from(it) }
|
||||
else -> topic?.toString()?.let { Hex32.from(it) }
|
||||
is Collection<*> -> rawTopics.map { topicItem ->
|
||||
when (topicItem) {
|
||||
null -> null
|
||||
is String -> listOfNotNull(
|
||||
try {
|
||||
Hex32.from(topicItem)
|
||||
} catch (t: Throwable) {
|
||||
log.debug("Ignore invalid topic: $topicItem with error ${t.message}")
|
||||
null
|
||||
},
|
||||
)
|
||||
is Collection<*> -> topicItem.mapNotNull { t ->
|
||||
try {
|
||||
t?.toString()?.let { Hex32.from(it) }
|
||||
} catch (t: Throwable) {
|
||||
log.debug("Ignore invalid topic: $t with error ${t.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
log.debug("Ignore invalid topic: $topic with error ${t.message}")
|
||||
throw IllegalArgumentException("Invalid topic: $topic")
|
||||
else -> throw IllegalArgumentException("Invalid topic entry: $topicItem. Must be null, string or list of strings")
|
||||
}
|
||||
}
|
||||
null -> emptyList()
|
||||
|
||||
@@ -30,7 +30,6 @@ open class ConnectLogs(
|
||||
upstream: Multistream,
|
||||
private val connectBlockUpdates: ConnectBlockUpdates,
|
||||
) {
|
||||
|
||||
companion object {
|
||||
private val ADDR_COMPARATOR = HexDataComparator()
|
||||
private val TOPIC_COMPARATOR = HexDataComparator()
|
||||
@@ -44,33 +43,40 @@ open class ConnectLogs(
|
||||
return produceLogs.produce(connectBlockUpdates.connect(matcher))
|
||||
}
|
||||
|
||||
open fun create(addresses: List<Address>, topics: List<Hex32?>): SubscriptionConnect<LogMessage> {
|
||||
open fun create(addresses: List<Address>, topics: List<List<Hex32>?>): SubscriptionConnect<LogMessage> {
|
||||
return object : SubscriptionConnect<LogMessage> {
|
||||
override fun connect(matcher: Selector.Matcher): Flux<LogMessage> {
|
||||
// shortcut to the whole output if we don't have any filters
|
||||
if (addresses.isEmpty() && topics.isEmpty()) {
|
||||
return start(matcher)
|
||||
}
|
||||
// filtered output
|
||||
return start(matcher)
|
||||
.transform(filtered(addresses, topics))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun filtered(addresses: List<Address>, selectedTopics: List<Hex32?>): Function<Flux<LogMessage>, Flux<LogMessage>> {
|
||||
// sort search criteria to use binary search later
|
||||
fun filtered(addresses: List<Address>, selectedTopics: List<List<Hex32>?>): Function<Flux<LogMessage>, Flux<LogMessage>> {
|
||||
val sortedAddresses: List<Address> = addresses.sortedWith(ADDR_COMPARATOR)
|
||||
val topicSets: List<Set<Hex32>?> = selectedTopics.map { topicsOrNull ->
|
||||
topicsOrNull?.toSet()
|
||||
}
|
||||
|
||||
return Function { logs ->
|
||||
logs.filter {
|
||||
val goodAddress =
|
||||
sortedAddresses.isEmpty() || sortedAddresses.binarySearch(it.address, ADDR_COMPARATOR) >= 0
|
||||
val goodTopic = when {
|
||||
selectedTopics.isEmpty() -> true
|
||||
it.topics.size < selectedTopics.size -> false
|
||||
else -> selectedTopics.zip(it.topics).all { (selectedTopic, logTopic) -> selectedTopic == null || selectedTopic == logTopic }
|
||||
logs.filter { log ->
|
||||
val goodAddress = sortedAddresses.isEmpty() ||
|
||||
sortedAddresses.binarySearch(log.address, ADDR_COMPARATOR) >= 0
|
||||
|
||||
val goodTopics = if (topicSets.isEmpty()) {
|
||||
true
|
||||
} else if (log.topics.size < topicSets.size) {
|
||||
false
|
||||
} else {
|
||||
topicSets.zip(log.topics).all { (wantedTopics, logTopic) ->
|
||||
wantedTopics == null || logTopic in wantedTopics
|
||||
}
|
||||
}
|
||||
goodAddress && goodTopic
|
||||
|
||||
goodAddress && goodTopics
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user