Skip to content

Commit

Permalink
problem: eth_getBlockByNumber("latest", ...) fails with error
Browse files Browse the repository at this point in the history
fix: #20
  • Loading branch information
splix committed Jul 3, 2020
1 parent 4c8ee34 commit 3934531
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,7 @@ abstract class AbstractHead : Head {
return head.get()
}

override fun getCurrentHeight(): Long? {
return getCurrent()?.height
}
}
4 changes: 4 additions & 0 deletions src/main/kotlin/io/emeraldpay/dshackle/upstream/EmptyHead.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ class EmptyHead : Head {

override fun onBeforeBlock(handler: Runnable) {
}

override fun getCurrentHeight(): Long? {
return null
}
}
2 changes: 2 additions & 0 deletions src/main/kotlin/io/emeraldpay/dshackle/upstream/Head.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ interface Head {
* @see getFlux
*/
fun onBeforeBlock(handler: Runnable)

fun getCurrentHeight(): Long?
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ open class EthereumMultistream(
}

override fun getRoutedApi(matcher: Selector.Matcher): Mono<Reader<JsonRpcRequest, JsonRpcResponse>> {
return Mono.just(NativeCallRouter(reader, getMethods()))
return Mono.just(NativeCallRouter(reader, getMethods(), getHead()))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -115,14 +115,14 @@ open class EthereumReader(
)
}

fun blocksByIdAsCont(): Reader<BlockId, BlockContainer> {
open fun blocksByIdAsCont(): Reader<BlockId, BlockContainer> {
return TransformingReader(
blocksById(),
blockAsContainer
)
}

fun blocksByHeightAsCont(): Reader<Long, BlockContainer> {
open fun blocksByHeightAsCont(): Reader<Long, BlockContainer> {
return CompoundReader(
caches.getBlocksByHeight(),
directReader.blockByHeightReader
Expand All @@ -139,7 +139,7 @@ open class EthereumReader(
)
}

fun txByHashAsCont(): Reader<TxId, TxContainer> {
open fun txByHashAsCont(): Reader<TxId, TxContainer> {
return CompoundReader(
caches.getTxByHash(),
RekeyingReader(idToTxHash, directReader.txReader)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
*/
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.TxId
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.dshackle.upstream.Head
import io.emeraldpay.dshackle.upstream.calls.CallMethods
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
Expand All @@ -32,7 +31,8 @@ import java.math.BigInteger

class NativeCallRouter(
private val reader: EthereumReader,
private val methods: CallMethods
private val methods: CallMethods,
private val head: Head
) : Reader<JsonRpcRequest, JsonRpcResponse> {

companion object {
Expand Down Expand Up @@ -98,31 +98,52 @@ class NativeCallRouter(
}
}
method == "eth_getBlockByNumber" -> {
if (params.size != 2) {
throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "Must provide 2 parameters")
}
val number: Long
try {
val quantity = HexQuantity.from(params[0].toString()) ?: throw IllegalArgumentException()
getBlockByNumber(params)
}
else -> null
}
}

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 {
if (it < BigInteger.valueOf(Long.MAX_VALUE) && it >= BigInteger.ZERO) {
it.toLong()
} else {
throw IllegalArgumentException()
}
}
} catch (e: IllegalArgumentException) {
throw RpcException(RpcResponseError.CODE_INVALID_METHOD_PARAMS, "[0] must be block number")
}
val withTx = params[1].toString().toBoolean()
if (withTx) {
log.warn("Block by number is not implemented")
null
} else {
reader.blocksByHeightAsCont().read(number).map { it.json!! }
blockRef == "latest" -> {
number = head.getCurrentHeight() ?: return null
}
blockRef == "earliest" -> {
number = 0
}
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!! }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,9 @@ class EthereumHeadMock implements Head {
void onBeforeBlock(@NotNull Runnable handler) {
handlers.add(handler)
}

@Override
Long getCurrentHeight() {
return latest?.height
}
}
18 changes: 18 additions & 0 deletions src/test/groovy/io/emeraldpay/dshackle/test/TestingCommons.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import io.emeraldpay.dshackle.FileResolver
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.cache.CachesFactory
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.Reader
import io.emeraldpay.dshackle.upstream.Multistream
Expand All @@ -30,12 +32,17 @@ import io.emeraldpay.dshackle.upstream.ethereum.EthereumUpstream
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.domain.BlockHash
import io.infinitape.etherjar.rpc.json.BlockJson

import java.time.Instant

class TestingCommons {

static EthereumApiMock api() {
return new EthereumApiMock()
}

static EthereumUpstreamMock upstream(Reader<JsonRpcRequest, JsonRpcResponse> api) {
return new EthereumUpstreamMock(Chain.ETHEREUM, api)
}
Expand Down Expand Up @@ -69,4 +76,15 @@ class TestingCommons {
static FileResolver fileResolver() {
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)
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package io.emeraldpay.dshackle.upstream.ethereum

import io.emeraldpay.dshackle.Global
import io.emeraldpay.dshackle.cache.Caches
import io.emeraldpay.dshackle.reader.EmptyReader
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.rpcclient.JsonRpcRequest
import io.emeraldpay.dshackle.reader.Reader
import io.emeraldpay.grpc.Chain
import io.infinitape.etherjar.rpc.json.BlockJson
import org.apache.commons.collections4.functors.ConstantFactory
import reactor.core.publisher.Mono
import spock.lang.Specification

import java.time.Duration
Expand All @@ -21,11 +28,92 @@ class NativeCallRouterSpec extends Specification {
Caches.default(),
ConstantFactory.constantFactory(new DefaultEthereumMethods(Chain.ETHEREUM))
),
methods
methods,
new EmptyHead()
)
when:
def act = router.read(new JsonRpcRequest("eth_coinbase", [])).block(Duration.ofSeconds(1))
then:
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
}
}
}
}

0 comments on commit 3934531

Please sign in to comment.