@@ -88,4 +88,7 @@ abstract class AbstractHead : Head {
|
|||||||
return head.get()
|
return head.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCurrentHeight(): Long? {
|
||||||
|
return getCurrent()?.height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -28,4 +28,8 @@ class EmptyHead : Head {
|
|||||||
|
|
||||||
override fun onBeforeBlock(handler: Runnable) {
|
override fun onBeforeBlock(handler: Runnable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCurrentHeight(): Long? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -36,4 +36,6 @@ interface Head {
|
|||||||
* @see getFlux
|
* @see getFlux
|
||||||
*/
|
*/
|
||||||
fun onBeforeBlock(handler: Runnable)
|
fun onBeforeBlock(handler: Runnable)
|
||||||
|
|
||||||
|
fun getCurrentHeight(): Long?
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ open class EthereumMultistream(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getRoutedApi(matcher: Selector.Matcher): Mono<Reader<JsonRpcRequest, JsonRpcResponse>> {
|
override fun getRoutedApi(matcher: Selector.Matcher): Mono<Reader<JsonRpcRequest, JsonRpcResponse>> {
|
||||||
return Mono.just(NativeCallRouter(reader, getMethods()))
|
return Mono.just(NativeCallRouter(reader, getMethods(), getHead()))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -115,14 +115,14 @@ open class EthereumReader(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun blocksByIdAsCont(): Reader<BlockId, BlockContainer> {
|
open fun blocksByIdAsCont(): Reader<BlockId, BlockContainer> {
|
||||||
return TransformingReader(
|
return TransformingReader(
|
||||||
blocksById(),
|
blocksById(),
|
||||||
blockAsContainer
|
blockAsContainer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun blocksByHeightAsCont(): Reader<Long, BlockContainer> {
|
open fun blocksByHeightAsCont(): Reader<Long, BlockContainer> {
|
||||||
return CompoundReader(
|
return CompoundReader(
|
||||||
caches.getBlocksByHeight(),
|
caches.getBlocksByHeight(),
|
||||||
directReader.blockByHeightReader
|
directReader.blockByHeightReader
|
||||||
@@ -139,7 +139,7 @@ open class EthereumReader(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun txByHashAsCont(): Reader<TxId, TxContainer> {
|
open fun txByHashAsCont(): Reader<TxId, TxContainer> {
|
||||||
return CompoundReader(
|
return CompoundReader(
|
||||||
caches.getTxByHash(),
|
caches.getTxByHash(),
|
||||||
RekeyingReader(idToTxHash, directReader.txReader)
|
RekeyingReader(idToTxHash, directReader.txReader)
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
|
||||||
import io.emeraldpay.dshackle.Global
|
|
||||||
import io.emeraldpay.dshackle.data.BlockId
|
import io.emeraldpay.dshackle.data.BlockId
|
||||||
import io.emeraldpay.dshackle.data.TxId
|
import io.emeraldpay.dshackle.data.TxId
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
import io.emeraldpay.dshackle.upstream.calls.CallMethods
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
@@ -32,7 +31,8 @@ import java.math.BigInteger
|
|||||||
|
|
||||||
class NativeCallRouter(
|
class NativeCallRouter(
|
||||||
private val reader: EthereumReader,
|
private val reader: EthereumReader,
|
||||||
private val methods: CallMethods
|
private val methods: CallMethods,
|
||||||
|
private val head: Head
|
||||||
) : Reader<JsonRpcRequest, JsonRpcResponse> {
|
) : Reader<JsonRpcRequest, JsonRpcResponse> {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -98,12 +98,22 @@ class NativeCallRouter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
method == "eth_getBlockByNumber" -> {
|
method == "eth_getBlockByNumber" -> {
|
||||||
if (params.size != 2) {
|
getBlockByNumber(params)
|
||||||
throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Must provide 2 parameters")
|
}
|
||||||
}
|
else -> null
|
||||||
val number: Long
|
}
|
||||||
try {
|
}
|
||||||
val quantity = HexQuantity.from(params[0].toString()) ?: throw IllegalArgumentException()
|
|
||||||
|
fun getBlockByNumber(params: List<Any>): Mono<ByteArray>? {
|
||||||
|
if (params.size != 2) {
|
||||||
|
throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Must provide 2 parameters")
|
||||||
|
}
|
||||||
|
val number: Long
|
||||||
|
try {
|
||||||
|
val blockRef = params[0].toString()
|
||||||
|
when {
|
||||||
|
blockRef.startsWith("0x") -> {
|
||||||
|
val quantity = HexQuantity.from(blockRef) ?: throw IllegalArgumentException()
|
||||||
number = quantity.value.let {
|
number = quantity.value.let {
|
||||||
if (it < BigInteger.valueOf(Long.MAX_VALUE) && it >= BigInteger.ZERO) {
|
if (it < BigInteger.valueOf(Long.MAX_VALUE) && it >= BigInteger.ZERO) {
|
||||||
it.toLong()
|
it.toLong()
|
||||||
@@ -111,18 +121,29 @@ class NativeCallRouter(
|
|||||||
throw IllegalArgumentException()
|
throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "[0] must be block number")
|
|
||||||
}
|
}
|
||||||
val withTx = params[1].toString().toBoolean()
|
blockRef == "latest" -> {
|
||||||
if (withTx) {
|
number = head.getCurrentHeight() ?: return null
|
||||||
log.warn("Block by number is not implemented")
|
}
|
||||||
null
|
blockRef == "earliest" -> {
|
||||||
} else {
|
number = 0
|
||||||
reader.blocksByHeightAsCont().read(number).map { it.json!! }
|
}
|
||||||
|
blockRef == "pending" -> {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Block number is invalid")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> null
|
} catch (e: IllegalArgumentException) {
|
||||||
|
throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "[0] must be block number")
|
||||||
|
}
|
||||||
|
val withTx = params[1].toString().toBoolean()
|
||||||
|
return if (withTx) {
|
||||||
|
log.warn("Block by number is not implemented")
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
reader.blocksByHeightAsCont().read(number).map { it.json!! }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,4 +62,9 @@ class EthereumHeadMock implements Head {
|
|||||||
void onBeforeBlock(@NotNull Runnable handler) {
|
void onBeforeBlock(@NotNull Runnable handler) {
|
||||||
handlers.add(handler)
|
handlers.add(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Long getCurrentHeight() {
|
||||||
|
return latest?.height
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import io.emeraldpay.dshackle.FileResolver
|
|||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
import io.emeraldpay.dshackle.cache.CachesFactory
|
import io.emeraldpay.dshackle.cache.CachesFactory
|
||||||
import io.emeraldpay.dshackle.config.CacheConfig
|
import io.emeraldpay.dshackle.config.CacheConfig
|
||||||
|
import io.emeraldpay.dshackle.data.BlockContainer
|
||||||
|
import io.emeraldpay.dshackle.data.BlockId
|
||||||
import io.emeraldpay.dshackle.reader.EmptyReader
|
import io.emeraldpay.dshackle.reader.EmptyReader
|
||||||
import io.emeraldpay.dshackle.reader.Reader
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
import io.emeraldpay.dshackle.upstream.Multistream
|
import io.emeraldpay.dshackle.upstream.Multistream
|
||||||
@@ -30,12 +32,17 @@ import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
|
|||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import io.infinitape.etherjar.domain.BlockHash
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
|
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
class TestingCommons {
|
class TestingCommons {
|
||||||
|
|
||||||
static EthereumApiMock api() {
|
static EthereumApiMock api() {
|
||||||
return new EthereumApiMock()
|
return new EthereumApiMock()
|
||||||
}
|
}
|
||||||
|
|
||||||
static EthereumUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
static EthereumUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
|
||||||
return new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
return new EthereumUpstreamMock(Chain.ETHEREUM, api)
|
||||||
}
|
}
|
||||||
@@ -69,4 +76,15 @@ class TestingCommons {
|
|||||||
static FileResolver fileResolver() {
|
static FileResolver fileResolver() {
|
||||||
return new FileResolver(new File("src/test/resources"))
|
return new FileResolver(new File("src/test/resources"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BlockContainer blockForEthereum(Long height) {
|
||||||
|
BlockJson block = new BlockJson().tap {
|
||||||
|
setNumber(height)
|
||||||
|
setHash(BlockHash.from("0xc4b01774e426325b50f0c709753ec7cf1f1774439d587dfb91f2a4eeb8179cde"))
|
||||||
|
setTotalDifficulty(BigInteger.ONE)
|
||||||
|
setTimestamp(Instant.now())
|
||||||
|
}
|
||||||
|
return BlockContainer.from(block)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
package io.emeraldpay.dshackle.upstream.ethereum
|
package io.emeraldpay.dshackle.upstream.ethereum
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Global
|
||||||
import io.emeraldpay.dshackle.cache.Caches
|
import io.emeraldpay.dshackle.cache.Caches
|
||||||
|
import io.emeraldpay.dshackle.reader.EmptyReader
|
||||||
import io.emeraldpay.dshackle.test.TestingCommons
|
import io.emeraldpay.dshackle.test.TestingCommons
|
||||||
|
import io.emeraldpay.dshackle.upstream.EmptyHead
|
||||||
|
import io.emeraldpay.dshackle.upstream.Head
|
||||||
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
import io.emeraldpay.dshackle.upstream.calls.DefaultEthereumMethods
|
||||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||||
|
import io.emeraldpay.dshackle.reader.Reader
|
||||||
import io.emeraldpay.grpc.Chain
|
import io.emeraldpay.grpc.Chain
|
||||||
|
import io.infinitape.etherjar.rpc.json.BlockJson
|
||||||
import org.apache.commons.collections4.functors.ConstantFactory
|
import org.apache.commons.collections4.functors.ConstantFactory
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
import spock.lang.Specification
|
import spock.lang.Specification
|
||||||
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@@ -21,11 +28,92 @@ class NativeCallRouterSpec extends Specification {
|
|||||||
Caches.default(),
|
Caches.default(),
|
||||||
ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
|
ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
|
||||||
),
|
),
|
||||||
methods
|
methods,
|
||||||
|
new EmptyHead()
|
||||||
)
|
)
|
||||||
when:
|
when:
|
||||||
def act = router.read(new JsonRpcRequest("eth_coinbase", [])).block(Duration.ofSeconds(1))
|
def act = router.read(new JsonRpcRequest("eth_coinbase", [])).block(Duration.ofSeconds(1))
|
||||||
then:
|
then:
|
||||||
act.resultAsProcessedString == "0x0000000000000000000000000000000000000000"
|
act.resultAsProcessedString == "0x0000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def "getBlockByNumber with latest uses latest id"() {
|
||||||
|
setup:
|
||||||
|
def head = Mock(Head) {
|
||||||
|
1 * getCurrentHeight() >> 101L
|
||||||
|
}
|
||||||
|
def reader = Mock(EthereumReader) {
|
||||||
|
_ * blocksByIdAsCont() >> new EmptyReader<>()
|
||||||
|
_ * txByHashAsCont() >> new EmptyReader<>()
|
||||||
|
1 * blocksByHeightAsCont() >> Mock(Reader) {
|
||||||
|
1 * read(101L) >> Mono.just(TestingCommons.blockForEthereum(101L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def methods = new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||||
|
def router = new NativeCallRouter(reader, methods, head)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = router.getBlockByNumber(["latest", false])
|
||||||
|
|
||||||
|
then:
|
||||||
|
act != null
|
||||||
|
with(act.block()) {
|
||||||
|
it.length > 0
|
||||||
|
with(Global.objectMapper.readValue(it, BlockJson)) {
|
||||||
|
number == 101
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "getBlockByNumber with earliest uses 0 block"() {
|
||||||
|
setup:
|
||||||
|
def head = Stub(Head) {}
|
||||||
|
def reader = Mock(EthereumReader) {
|
||||||
|
_ * blocksByIdAsCont() >> new EmptyReader<>()
|
||||||
|
_ * txByHashAsCont() >> new EmptyReader<>()
|
||||||
|
1 * blocksByHeightAsCont() >> Mock(Reader) {
|
||||||
|
1 * read(0L) >> Mono.just(TestingCommons.blockForEthereum(0L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def methods = new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||||
|
def router = new NativeCallRouter(reader, methods, head)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = router.getBlockByNumber(["earliest", false])
|
||||||
|
|
||||||
|
then:
|
||||||
|
act != null
|
||||||
|
with(act.block()) {
|
||||||
|
it.length > 0
|
||||||
|
with(Global.objectMapper.readValue(it, BlockJson)) {
|
||||||
|
number == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def "getBlockByNumber fetches the block"() {
|
||||||
|
setup:
|
||||||
|
def head = Stub(Head) {}
|
||||||
|
def reader = Mock(EthereumReader) {
|
||||||
|
_ * blocksByIdAsCont() >> new EmptyReader<>()
|
||||||
|
_ * txByHashAsCont() >> new EmptyReader<>()
|
||||||
|
1 * blocksByHeightAsCont() >> Mock(Reader) {
|
||||||
|
1 * read(74735L) >> Mono.just(TestingCommons.blockForEthereum(74735L))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
def methods = new DefaultEthereumMethods(Chain.ETHEREUM)
|
||||||
|
def router = new NativeCallRouter(reader, methods, head)
|
||||||
|
|
||||||
|
when:
|
||||||
|
def act = router.getBlockByNumber(["0x123ef", false])
|
||||||
|
|
||||||
|
then:
|
||||||
|
act != null
|
||||||
|
with(act.block()) {
|
||||||
|
it.length > 0
|
||||||
|
with(Global.objectMapper.readValue(it, BlockJson)) {
|
||||||
|
number == 74735
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user