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> {
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") {
return blockTagSelector(params, 2, head)
return blockTagSelector(params, 2, null, head)
} else if (method in DefaultEthereumMethods.withFilterIdMethods) {
return sameUpstreamMatcher(params)
} 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()
}
@@ -87,36 +89,52 @@ class EthereumCallSelector(
val nodeId = hashHex.toInt(16)
return Mono.just(Selector.SameNodeMatcher(nodeId.toByte()))
}
private fun blockTagSelector(params: String, pos: Int, head: Head): Mono<Selector.Matcher> {
private fun blockTagSelector(params: String, pos: Int, paramName: String?, head: Head): Mono<Selector.Matcher> {
val list = objectMapper.readerFor(Any::class.java).readValues<Any>(params).readAll()
if (list.size < pos + 1) {
log.debug("Tag is not specified. Ignoring")
return Mono.empty()
}
// 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()
// for earliest it doesn't nothing, we expect to have 0 block
"earliest" -> 0L
"earliest" -> 0L // for earliest it doesn't nothing, we expect to have 0 block
else -> if (tag.startsWith("0x")) {
return if (tag.length == 66) { // 32-byte hash is represented as 0x + 64 characters
blockByHash(tag, head)
} else {
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 {
log.debug("Invalid tag: $tag")
null
@@ -128,8 +146,7 @@ class EthereumCallSelector(
Mono.empty()
}
}
private fun blockMethodSelector(method: String, params: String): Mono<Selector.Matcher> {
private fun blockMethodSelector(method: String, params: String, head: Head): Mono<Selector.Matcher> {
val list = objectMapper.readerFor(Any::class.java).readValues<Any>(params).readAll()
if (list.isEmpty()) {
return Mono.empty()
@@ -138,7 +155,7 @@ class EthereumCallSelector(
return when (method) {
"eth_getTransactionByBlockHashAndIndex", "eth_getBlockByHash" -> blockByHashFromCache(hashOrNumber)
"eth_getTransactionByBlockNumberAndIndex", "eth_getBlockByNumber" -> blockByHeight(hashOrNumber)
"eth_getTransactionByBlockNumberAndIndex", "eth_getBlockByNumber" -> blockSelectorByTag(hashOrNumber, head)
else -> Mono.empty()
}
}

View File

@@ -264,22 +264,29 @@ class EthereumCallSelectorSpec extends Specification {
setup:
def cache = Stub(Caches)
def callSelector = new EthereumCallSelector(Stub(Reader), cache)
def head = Stub(Head)
def head = Mock(Head) {
_ * getCurrentHeight() >> 17654321L
}
when:
def act = callSelector.getMatcher(
method, '["0xfbfe3b", false]',
head, false
method, params, head, false
)
then:
StepVerifier.create(act)
.expectNext(new Selector.HeightMatcher(16514619L))
.expectNext(new Selector.HeightMatcher(height))
.expectComplete()
.verify(Duration.ofSeconds(1))
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"() {
@@ -307,4 +314,28 @@ class EthereumCallSelectorSpec extends Specification {
where:
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
}
}