From 0f77b57bd05254d7381e8bebbac599d66216ff60 Mon Sep 17 00:00:00 2001 From: Igor Artamonov Date: Mon, 16 Aug 2021 21:59:39 -0400 Subject: [PATCH] problem: invalid height matcher when block hash is specified --- .../upstream/calls/EthereumCallSelector.kt | 47 +++++++++------- .../calls/EthereumCallSelectorSpec.groovy | 53 +++++++++++++++++++ 2 files changed, 80 insertions(+), 20 deletions(-) diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt index d31f52fc..abb2cebd 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelector.kt @@ -73,34 +73,20 @@ class EthereumCallSelector( // for earliest it doesn't nothing, we expect to have 0 block "earliest" -> 0L else -> if (tag.startsWith("0x")) { - try { - HexQuantity.from(tag).value.toLong() - } catch (t: Throwable) { - log.debug("Invalid tag: $tag. ${t.javaClass}: ${t.message}") - null + 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") -> { - try { - HexQuantity.from(obj["blockNumber"].toString()).value.toLong() - } catch (t: Throwable) { - log.warn("Invalid blockNumber: $tag") - null - } + return blockByHeight(obj["blockNumber"].toString()) } obj.containsKey("blockHash") -> { - try { - val blockId = BlockId.from(obj["blockHash"].toString()) - return heightReader.read(blockId) - .switchIfEmpty(Mono.justOrEmpty(head.getCurrentHeight())) - .map { Selector.HeightMatcher(it) } - } catch (t: Throwable) { - log.warn("Invalid blockHash: $tag") - null - } + return blockByHash(obj["blockHash"].toString(), head) } else -> null } @@ -116,4 +102,25 @@ class EthereumCallSelector( } } + private fun blockByHeight(blockNumber: String): Mono { + return try { + Mono.just(Selector.HeightMatcher(HexQuantity.from(blockNumber).value.longValueExact())) + } catch (t: Throwable) { + log.warn("Invalid blockNumber: $blockNumber") + Mono.empty() + } + } + + private fun blockByHash(blockHash: String, head: Head): Mono { + return try { + val blockId = BlockId.from(blockHash) + heightReader.read(blockId) + .switchIfEmpty(Mono.justOrEmpty(head.getCurrentHeight())) + .map { Selector.HeightMatcher(it) } + } catch (t: Throwable) { + log.warn("Invalid blockHash: $blockHash") + Mono.empty() + } + } + } \ No newline at end of file diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy index f4832bd1..ea5d15cb 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/calls/EthereumCallSelectorSpec.groovy @@ -73,6 +73,59 @@ class EthereumCallSelectorSpec extends Specification { act == new Selector.HeightMatcher(0x40) } + def "Get height matcher for balance on block referred by hash"() { + setup: + def heights = Mock(Reader) { + 1 * it.read(BlockId.from("0xc90f1c8c125a4d5b90742f16947bdb1d10516f173fd7fc51223d10499de2a812")) >> Mono.just(8606722L) + } + EthereumCallSelector callSelector = new EthereumCallSelector(heights) + def head = Mock(Head) { + _ * getCurrentHeight() >> 9128116 + } + when: + def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "0xc90f1c8c125a4d5b90742f16947bdb1d10516f173fd7fc51223d10499de2a812"]', head).block() + then: + act == new Selector.HeightMatcher(8606722) + } + + def "No matcher for invalid height"() { + setup: + EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader)) + def head = Mock(Head) { + _ * getCurrentHeight() >> 100 + } + when: + // 0x10000000000000000 is too large to be a block + def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "0x10000000000000000"]', head).block() + then: + act == null + } + + def "No matcher for negative height"() { + setup: + EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader)) + def head = Mock(Head) { + _ * getCurrentHeight() >> 100 + } + when: + def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "-0x100"]', head).block() + then: + act == null + } + + def "No matcher for negative long"() { + setup: + EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader)) + def head = Mock(Head) { + _ * getCurrentHeight() >> 100 + } + when: + // 0x8000000000000000 becomes -9223372036854775808 in converted to long as is, i.e. high bit is set + def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "0x8000000000000000"]', head).block() + then: + act == null + } + def "No matcher for pending balance"() { setup: EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader))