diff --git a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt index ae19a8ab..1c7de1b2 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReader.kt @@ -114,9 +114,13 @@ class EthereumDirectReader( return readWithQuorum(request) .timeout(Defaults.timeoutInternal, Mono.error(TimeoutException("Block not read $id"))) .retryWhen(Retry.backoff(3, Duration.ofSeconds(1))) - .map { blockbytes -> - val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson - BlockContainer.from(block, blockbytes) + .flatMap { blockbytes -> + val block = objectMapper.readValue(blockbytes, BlockJson::class.java) as BlockJson? + if (block == null) { + Mono.empty() + } else { + Mono.just(BlockContainer.from(block, blockbytes)) + } } .doOnNext { block -> caches.cache(Caches.Tag.REQUESTED, block) diff --git a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy index a8a235f6..bc8036bb 100644 --- a/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy +++ b/src/test/groovy/io/emeraldpay/dshackle/upstream/ethereum/EthereumDirectReaderSpec.groovy @@ -69,6 +69,34 @@ class EthereumDirectReaderSpec extends Specification { .verify(Duration.ofSeconds(1)) } + def "Produce empty result on non-existing block"() { + setup: + def up = Mock(Multistream) { + 1 * getApiSource(_) >> Stub(ApiSource) + } + def calls = Mock(Factory) { + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM) + } + EthereumDirectReader reader = new EthereumDirectReader( + up, Caches.default(), new CurrentBlockCache(), calls + ) + reader.quorumReaderFactory = Mock(QuorumReaderFactory) { + 1 * create(_, _) >> Mock(Reader) { + 1 * read(new JsonRpcRequest("eth_getBlockByHash", [hash1, false])) >> Mono.just( + new QuorumRpcReader.Result( + Global.objectMapper.writeValueAsBytes(null), 1 + ) + ) + } + } + when: + def act = reader.blockReader.read(BlockHash.from(hash1)) + then: + StepVerifier.create(act) + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + def "Reads block by height"() { setup: def json = new BlockJson().tap { @@ -143,6 +171,34 @@ class EthereumDirectReaderSpec extends Specification { .verify(Duration.ofSeconds(1)) } + def "Produce empty on non-existing tx"() { + setup: + def up = Mock(Multistream) { + 1 * getApiSource(_) >> Stub(ApiSource) + } + def calls = Mock(Factory) { + 1 * create() >> new DefaultEthereumMethods(Chain.ETHEREUM) + } + EthereumDirectReader reader = new EthereumDirectReader( + up, Caches.default(), new CurrentBlockCache(), calls + ) + reader.quorumReaderFactory = Mock(QuorumReaderFactory) { + 1 * create(_, _) >> Mock(Reader) { + 1 * read(new JsonRpcRequest("eth_getTransactionByHash", [hash1])) >> Mono.just( + new QuorumRpcReader.Result( + Global.objectMapper.writeValueAsBytes(null), 1 + ) + ) + } + } + when: + def act = reader.txReader.read(TransactionId.from(hash1)) + then: + StepVerifier.create(act) + .expectComplete() + .verify(Duration.ofSeconds(1)) + } + def "Reads balance - height is unknown"() { setup: def up = Mock(Multistream) { diff --git a/testing/trial/src/test/groovy/io/emeraldpay/dshackle/testing/trial/proxy/StandardCallsSpec.groovy b/testing/trial/src/test/groovy/io/emeraldpay/dshackle/testing/trial/proxy/StandardCallsSpec.groovy index 65818107..66e45ebd 100644 --- a/testing/trial/src/test/groovy/io/emeraldpay/dshackle/testing/trial/proxy/StandardCallsSpec.groovy +++ b/testing/trial/src/test/groovy/io/emeraldpay/dshackle/testing/trial/proxy/StandardCallsSpec.groovy @@ -31,6 +31,33 @@ class StandardCallsSpec extends Specification { act.error == null } + def "get non-existing block"() { + when: + def act = client.execute("eth_getBlockByNumber", ["0x200001", false]) + then: + act.result == null + act.error == null + } + + def "get tx"() { + when: + def act = client.execute("eth_getTransactionByHash", ["0x01c5a8461d06c2c195035c148af0f871c7679841d86ae5bb98676bb2d8e68dfa"]) + then: + act.result != null + with(act.result) { + blockHash == "0x9a834c53bbee9c2665a5a84789a1d1ad73750b2d77b50de44f457f411d02e52e" + } + act.error == null + } + + def "get non-existing tx"() { + when: + def act = client.execute("eth_getTransactionByHash", ["0x000000461d06c2c195035c148af0f871c7679841d86ae5bb98676bb2d8e68dfa"]) + then: + act.result == null + act.error == null + } + def "get block with txes"() { when: def act = client.execute("eth_getBlockByNumber", ["0x100001", true])