Add height matcher for method (#156)

* add height matcher for method

* add test

* add test for block methods fix
This commit is contained in:
Vadim Vlasov
2023-03-13 17:39:56 +08:00
committed by GitHub
parent 01ea756e33
commit d3e215b940
2 changed files with 76 additions and 28 deletions

View File

@@ -63,13 +63,15 @@ class EthereumCallSelector(
*/ */
fun getMatcher(method: String, params: String, head: Head, passthrough: Boolean): Mono<Selector.Matcher> { fun getMatcher(method: String, params: String, head: Head, passthrough: Boolean): Mono<Selector.Matcher> {
if (!passthrough && Collections.binarySearch(TAG_METHODS, method) >= 0) { if (!passthrough && Collections.binarySearch(TAG_METHODS, method) >= 0) {
return blockTagSelector(params, 1, head) return blockTagSelector(params, 1, null, head)
} else if (!passthrough && method == "eth_getStorageAt") { } else if (!passthrough && method == "eth_getStorageAt") {
return blockTagSelector(params, 2, head) return blockTagSelector(params, 2, null, head)
} else if (method in DefaultEthereumMethods.withFilterIdMethods) { } else if (method in DefaultEthereumMethods.withFilterIdMethods) {
return sameUpstreamMatcher(params) return sameUpstreamMatcher(params)
} else if (method in GET_BY_HASH_OR_NUMBER_METHODS) { } else if (method in GET_BY_HASH_OR_NUMBER_METHODS) {
return blockMethodSelector(method, params) return blockMethodSelector(method, params, head)
} else if (method == "eth_getLogs") {
return blockTagSelector(params, 0, "toBlock", head)
} }
return Mono.empty() return Mono.empty()
} }
@@ -87,36 +89,52 @@ class EthereumCallSelector(
val nodeId = hashHex.toInt(16) val nodeId = hashHex.toInt(16)
return Mono.just(Selector.SameNodeMatcher(nodeId.toByte())) return Mono.just(Selector.SameNodeMatcher(nodeId.toByte()))
} }
private fun blockTagSelector(params: String, pos: Int, paramName: String?, head: Head): Mono<Selector.Matcher> {
private fun blockTagSelector(params: String, pos: Int, head: Head): Mono<Selector.Matcher> {
val list = objectMapper.readerFor(Any::class.java).readValues<Any>(params).readAll() val list = objectMapper.readerFor(Any::class.java).readValues<Any>(params).readAll()
if (list.size < pos + 1) { if (list.size < pos + 1) {
log.debug("Tag is not specified. Ignoring") log.debug("Tag is not specified. Ignoring")
return Mono.empty() return Mono.empty()
} }
// integer block number, a string "latest", "earliest" or "pending", or an object with block reference // integer block number, a string "latest", "earliest" or "pending", or an object with block reference
val minHeight: Long? = when (val tag = Objects.toString(list[pos])) { val blockTag = Objects.toString(list[pos])
return if (blockTag.startsWith("{") && list[pos] is Map<*, *>) {
val obj = list[pos] as Map<*, *>
when {
paramName != null -> {
return if (obj.containsKey(paramName)) {
blockSelectorByTag(obj[paramName].toString(), head)
} else {
Mono.empty()
}
}
obj.containsKey("blockNumber") -> {
return blockSelectorByTag(obj["blockNumber"].toString(), head)
}
obj.containsKey("blockHash") -> {
return blockSelectorByTag(obj["blockHash"].toString(), head)
}
else -> {
log.debug("Tag is not found. Ignoring")
Mono.empty()
}
}
} else {
blockSelectorByTag(blockTag, head)
}
}
private fun blockSelectorByTag(tag: String, head: Head): Mono<Selector.Matcher> {
val minHeight: Long? = when (tag) {
"latest" -> head.getCurrentHeight() "latest" -> head.getCurrentHeight()
// for earliest it doesn't nothing, we expect to have 0 block "earliest" -> 0L // for earliest it doesn't nothing, we expect to have 0 block
"earliest" -> 0L
else -> if (tag.startsWith("0x")) { else -> if (tag.startsWith("0x")) {
return if (tag.length == 66) { // 32-byte hash is represented as 0x + 64 characters return if (tag.length == 66) { // 32-byte hash is represented as 0x + 64 characters
blockByHash(tag, head) blockByHash(tag, head)
} else { } else {
blockByHeight(tag) blockByHeight(tag)
} }
} else if (tag.startsWith("{") && list[pos] is Map<*, *>) {
// see https://eips.ethereum.org/EIPS/eip-1898
val obj = list[pos] as Map<*, *>
when {
obj.containsKey("blockNumber") -> {
return blockByHeight(obj["blockNumber"].toString())
}
obj.containsKey("blockHash") -> {
return blockByHash(obj["blockHash"].toString(), head)
}
else -> null
}
} else { } else {
log.debug("Invalid tag: $tag") log.debug("Invalid tag: $tag")
null null
@@ -128,8 +146,7 @@ class EthereumCallSelector(
Mono.empty() Mono.empty()
} }
} }
private fun blockMethodSelector(method: String, params: String, head: Head): Mono<Selector.Matcher> {
private fun blockMethodSelector(method: String, params: String): Mono<Selector.Matcher> {
val list = objectMapper.readerFor(Any::class.java).readValues<Any>(params).readAll() val list = objectMapper.readerFor(Any::class.java).readValues<Any>(params).readAll()
if (list.isEmpty()) { if (list.isEmpty()) {
return Mono.empty() return Mono.empty()
@@ -138,7 +155,7 @@ class EthereumCallSelector(
return when (method) { return when (method) {
"eth_getTransactionByBlockHashAndIndex", "eth_getBlockByHash" -> blockByHashFromCache(hashOrNumber) "eth_getTransactionByBlockHashAndIndex", "eth_getBlockByHash" -> blockByHashFromCache(hashOrNumber)
"eth_getTransactionByBlockNumberAndIndex", "eth_getBlockByNumber" -> blockByHeight(hashOrNumber) "eth_getTransactionByBlockNumberAndIndex", "eth_getBlockByNumber" -> blockSelectorByTag(hashOrNumber, head)
else -> Mono.empty() else -> Mono.empty()
} }
} }

View File

@@ -264,22 +264,29 @@ class EthereumCallSelectorSpec extends Specification {
setup: setup:
def cache = Stub(Caches) def cache = Stub(Caches)
def callSelector = new EthereumCallSelector(Stub(Reader), cache) def callSelector = new EthereumCallSelector(Stub(Reader), cache)
def head = Stub(Head) def head = Mock(Head) {
_ * getCurrentHeight() >> 17654321L
}
when: when:
def act = callSelector.getMatcher( def act = callSelector.getMatcher(
method, '["0xfbfe3b", false]', method, params, head, false
head, false
) )
then: then:
StepVerifier.create(act) StepVerifier.create(act)
.expectNext(new Selector.HeightMatcher(16514619L)) .expectNext(new Selector.HeightMatcher(height))
.expectComplete() .expectComplete()
.verify(Duration.ofSeconds(1)) .verify(Duration.ofSeconds(1))
where: where:
method << ["eth_getTransactionByBlockNumberAndIndex", "eth_getBlockByNumber"] method | params | height
"eth_getTransactionByBlockNumberAndIndex" | '["0xfbfe3b", false]' | 16514619L
"eth_getTransactionByBlockNumberAndIndex" | '["earliest", false]' | 0L
"eth_getTransactionByBlockNumberAndIndex" | '["latest", false]' | 17654321L
"eth_getBlockByNumber" | '["0xfbfe3b", false]' | 16514619L
"eth_getBlockByNumber" | '["earliest", false]' | 0L
"eth_getBlockByNumber" | '["latest", false]' | 17654321L
} }
def "No height matcher for getByHash method"() { def "No height matcher for getByHash method"() {
@@ -307,4 +314,28 @@ class EthereumCallSelectorSpec extends Specification {
where: where:
resultFromCache << [Mono.empty(), Mono.error(new RuntimeException())] resultFromCache << [Mono.empty(), Mono.error(new RuntimeException())]
} }
def "Get height matcher for getLogs method"() {
setup:
def cache = Stub(Caches)
def callSelector = new EthereumCallSelector(Stub(Reader), cache)
def head = Mock(Head) {
_ * getCurrentHeight() >> 17654321L
}
when:
def act = callSelector.getMatcher(method, param, head, false)
then:
StepVerifier.create(act)
.expectNext(new Selector.HeightMatcher(height))
.expectComplete()
.verify(Duration.ofSeconds(1))
where:
method | param | height
"eth_getLogs" | '[{"toBlock":"0xfbfe3b"}]' | 16514619L
"eth_getLogs" | '[{"toBlock":"latest"}]' | 17654321L
"eth_getLogs" | '[{"toBlock":"earliest"}]' | 0L
}
} }