problem: doesn't select upstream by available block hash, see EIP-1898
rel: #81
This commit is contained in:
130
src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashAddingSpec.groovy
vendored
Normal file
130
src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashAddingSpec.groovy
vendored
Normal 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()
|
||||
}
|
||||
}
|
||||
72
src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashRedisCacheSpec.groovy
vendored
Normal file
72
src/test/groovy/io/emeraldpay/dshackle/cache/HeightByHashRedisCacheSpec.groovy
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user