problem: doesn't select upstream by available block hash, see EIP-1898

rel: #81
This commit is contained in:
Igor Artamonov
2021-03-20 22:34:22 -04:00
parent c3c1a51a52
commit 50f6d7b2fe
13 changed files with 614 additions and 47 deletions

View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) 2021 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.cache
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.data.BlockId
import reactor.core.publisher.Mono
import spock.lang.Specification
import io.emeraldpay.dshackle.reader.Reader
import java.time.Instant
class HeightByHashAddingSpec extends Specification {
def block = new BlockContainer(
12079192L, BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32"),
BigInteger.ONE, Instant.now(), false, "".bytes, null, []
)
def "use memory if available"() {
setup:
def mem = new HeightByHashMemCache()
def upstream = Mock(Reader)
def reader = new HeightByHashAdding(
mem, null, upstream
)
when:
mem.add(block)
def act = reader.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")).block()
then:
act == 12079192L
0 * upstream.read(_)
}
def "call remote if not in memory and no redis"() {
setup:
def mem = new HeightByHashMemCache()
def upstream = Mock(Reader)
def reader = new HeightByHashAdding(
mem, null, upstream
)
when:
def act = reader.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")).block()
then:
act == 12079192L
1 * upstream.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.just(block)
}
def "get from redis if not in memory"() {
setup:
def mem = new HeightByHashMemCache()
def upstream = Mock(Reader)
def redis = Mock(HeightByHashCache)
def reader = new HeightByHashAdding(
mem, redis, upstream
)
when:
def act = reader.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")).block()
then:
act == 12079192L
0 * upstream.read(_)
1 * redis.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.just(12079192L)
}
def "call remote if not in memory and not in redis, add to redis"() {
setup:
def mem = new HeightByHashMemCache()
def upstream = Mock(Reader)
def redis = Mock(HeightByHashCache)
def reader = new HeightByHashAdding(
mem, redis, upstream
)
when:
def act = reader.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")).block()
then:
act == 12079192L
1 * redis.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.empty()
1 * redis.add(block) >> Mono.just(true).then()
1 * upstream.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.just(block)
}
def "empty is nowhere found, without redis"() {
setup:
def mem = new HeightByHashMemCache()
def upstream = Mock(Reader)
def reader = new HeightByHashAdding(
mem, null, upstream
)
when:
def act = reader.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")).block()
then:
act == null
1 * upstream.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.empty()
}
def "empty is nowhere found, with redis"() {
setup:
def mem = new HeightByHashMemCache()
def upstream = Mock(Reader)
def redis = Mock(HeightByHashCache)
def reader = new HeightByHashAdding(
mem, redis, upstream
)
when:
def act = reader.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")).block()
then:
act == null
1 * redis.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.empty()
1 * upstream.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.empty()
}
}

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2021 EmeraldPay, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.emeraldpay.dshackle.cache
import io.emeraldpay.dshackle.data.BlockContainer
import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.test.IntegrationTestingCommons
import io.emeraldpay.grpc.Chain
import io.lettuce.core.api.StatefulRedisConnection
import spock.lang.IgnoreIf
import spock.lang.Specification
import java.time.Instant
@IgnoreIf({ IntegrationTestingCommons.isDisabled("redis") })
class HeightByHashRedisCacheSpec extends Specification {
def block1 = new BlockContainer(
12079192L, BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32"),
BigInteger.ONE, Instant.now(), false, "".bytes, null, []
)
def block2 = new BlockContainer(
12079193L, BlockId.from("0xd27944b460632699768fbfec3e5d454db590cae43d470b5f42fc4d091e372c25"),
BigInteger.ONE, Instant.now(), false, "".bytes, null, []
)
StatefulRedisConnection<String, byte[]> redis
HeightByHashRedisCache cache
def setup() {
redis = IntegrationTestingCommons.redisConnection()
redis.sync().flushdb()
cache = new HeightByHashRedisCache(
redis.reactive(), Chain.ETHEREUM
)
}
def "Add and read"() {
when:
cache.add(block1).subscribe()
def act = cache.read(block1.hash).block()
then:
act == 12079192L
}
def "Add and read multiple"() {
when:
cache.add(block1).subscribe()
cache.add(block2).subscribe()
def act = cache.read(block1.hash).block()
then:
act == 12079192L
def act2 = cache.read(block2.hash).block()
then:
act2 == 12079193L
}
}

View File

@@ -34,6 +34,7 @@ import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.rpc.RpcException
import io.infinitape.etherjar.rpc.RpcResponseError
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
import spock.lang.Ignore
@@ -210,7 +211,9 @@ class NativeCallSpec extends Specification {
def "Returns error for unsupported chain"() {
setup:
def upstreams = Mock(MultistreamHolder)
def upstreams = Mock(MultistreamHolder) {
_ * it.observeChains() >> Flux.empty()
}
def nativeCall = new NativeCall(upstreams)
def req = BlockchainOuterClass.NativeCallRequest.newBuilder()
@@ -234,7 +237,9 @@ class NativeCallSpec extends Specification {
def "Prepare call"() {
setup:
def upstreams = Mock(MultistreamHolder)
def upstreams = Mock(MultistreamHolder) {
_ * it.observeChains() >> Flux.empty()
}
def nativeCall = new NativeCall(upstreams)
def req = BlockchainOuterClass.NativeCallRequest.newBuilder()
@@ -260,7 +265,9 @@ class NativeCallSpec extends Specification {
def "Prepare call without payload"() {
setup:
def upstreams = Mock(MultistreamHolder)
def upstreams = Mock(MultistreamHolder) {
_ * it.observeChains() >> Flux.empty()
}
def nativeCall = new NativeCall(upstreams)
def req = BlockchainOuterClass.NativeCallRequest.newBuilder()

View File

@@ -15,75 +15,115 @@
*/
package io.emeraldpay.dshackle.upstream.calls
import io.emeraldpay.dshackle.data.BlockId
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.Selector
import reactor.core.publisher.Mono
import spock.lang.Specification
class EthereumCallSelectorSpec extends Specification {
EthereumCallSelector callSelector = new EthereumCallSelector()
def "Get height matcher for latest balance"() {
setup:
EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader))
def head = Mock(Head) {
1 * getCurrentHeight() >> 100
}
when:
def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "latest"]', head)
def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "latest"]', head).block()
then:
act == new Selector.HeightMatcher(100)
}
def "Get height matcher for latest call"() {
setup:
EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader))
def head = Mock(Head) {
1 * getCurrentHeight() >> 100
}
when:
def act = callSelector.getMatcher("eth_call", '["0x0000", "latest"]', head)
def act = callSelector.getMatcher("eth_call", '["0x0000", "latest"]', head).block()
then:
act == new Selector.HeightMatcher(100)
}
def "Get height matcher for latest storageAt"() {
setup:
EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader))
def head = Mock(Head) {
1 * getCurrentHeight() >> 100
}
when:
def act = callSelector.getMatcher("eth_getStorageAt", '["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]', head)
def act = callSelector.getMatcher("eth_getStorageAt", '["0x295a70b2de5e3953354a6a8344e616ed314d7251", "0x0", "latest"]', head).block()
then:
act == new Selector.HeightMatcher(100)
}
def "Get height matcher for balance on block"() {
setup:
EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader))
def head = Mock(Head) {
_ * getCurrentHeight() >> 100
}
when:
def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "0x40"]', head)
def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "0x40"]', head).block()
then:
act == new Selector.HeightMatcher(0x40)
}
def "No matcher for pending balance"() {
setup:
EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader))
def head = Mock(Head) {
_ * getCurrentHeight() >> 100
}
when:
def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "pending"]', head)
def act = callSelector.getMatcher("eth_getBalance", '["0x0000", "pending"]', head).block()
then:
act == null
}
def "Get height matcher with EIP-1898"() {
setup:
EthereumCallSelector callSelector = new EthereumCallSelector(Stub(Reader))
def head = Stub(Head)
when:
def act = callSelector.getMatcher("eth_call", '["0x0000", {"blockNumber": "0x100"}]', head)
def act = callSelector.getMatcher("eth_call", '["0x0000", {"blockNumber": "0x100"}]', head).block()
then:
act == new Selector.HeightMatcher(0x100)
}
def "Get hash matcher with EIP-1898"() {
setup:
def heights = Mock(Reader) {
1 * it.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.just(12079192L)
}
EthereumCallSelector callSelector = new EthereumCallSelector(heights)
def head = Stub(Head)
when:
def act = callSelector.getMatcher("eth_call",
'["0x0000", {"blockHash": "0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32"}]', head)
.block()
then:
act == new Selector.HeightMatcher(12079192)
}
def "Match head if hash matcher for unknown hash"() {
setup:
def heights = Mock(Reader) {
1 * it.read(BlockId.from("0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32")) >> Mono.empty()
}
EthereumCallSelector callSelector = new EthereumCallSelector(heights)
def head = Mock(Head) {
1 * it.getCurrentHeight() >> 100
}
when:
def act = callSelector.getMatcher("eth_call",
'["0x0000", {"blockHash": "0xa6af163aab691919c595e2a466f0a7b01f1dff8cfd9631dee811df57064c2d32"}]', head)
.block()
then:
act == new Selector.HeightMatcher(100)
}
}