Support beacon chain (#436)
This commit is contained in:
@@ -11,15 +11,15 @@ import org.junit.jupiter.params.provider.MethodSource
|
||||
import org.mockito.kotlin.mock
|
||||
import org.springframework.cloud.sleuth.Tracer
|
||||
|
||||
class RpcReaderFactoryTest {
|
||||
private val defaultFactory = RpcReaderFactory.Default()
|
||||
class RequestReaderFactoryTest {
|
||||
private val defaultFactory = RequestReaderFactory.Default()
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
fun `create BroadcastReader for MaximumValueQuorum and BroadcastQuorum`(
|
||||
rpcReaderData: RpcReaderFactory.RpcReaderData,
|
||||
readerData: RequestReaderFactory.ReaderData,
|
||||
) {
|
||||
val reader = defaultFactory.create(rpcReaderData)
|
||||
val reader = defaultFactory.create(readerData)
|
||||
|
||||
assertTrue(reader is BroadcastReader)
|
||||
}
|
||||
@@ -32,9 +32,8 @@ class RpcReaderFactoryTest {
|
||||
fun data(): List<Arguments> {
|
||||
return listOf(
|
||||
Arguments.of(
|
||||
RpcReaderFactory.RpcReaderData(
|
||||
RequestReaderFactory.ReaderData(
|
||||
ms,
|
||||
"method",
|
||||
Selector.empty,
|
||||
MaximumValueQuorum(),
|
||||
null,
|
||||
@@ -42,9 +41,8 @@ class RpcReaderFactoryTest {
|
||||
),
|
||||
),
|
||||
Arguments.of(
|
||||
RpcReaderFactory.RpcReaderData(
|
||||
RequestReaderFactory.ReaderData(
|
||||
ms,
|
||||
"method",
|
||||
Selector.empty,
|
||||
BroadcastQuorum(),
|
||||
null,
|
||||
@@ -5,8 +5,8 @@ import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallReplyItem
|
||||
import io.emeraldpay.api.proto.BlockchainOuterClass.NativeCallRequest
|
||||
import io.emeraldpay.dshackle.config.MainConfig
|
||||
import io.emeraldpay.dshackle.upstream.MultistreamHolder
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.stream.Chunk
|
||||
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
|
||||
import io.emeraldpay.dshackle.upstream.stream.Chunk
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package io.emeraldpay.dshackle.upstream
|
||||
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||
import io.emeraldpay.dshackle.reader.ChainReader
|
||||
import io.emeraldpay.dshackle.upstream.ethereum.EthereumLowerBoundBlockDetector
|
||||
import io.emeraldpay.dshackle.upstream.polkadot.PolkadotLowerBoundBlockDetector
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
@@ -23,7 +21,7 @@ class RecursiveLowerBoundBlockDetectorTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("detectors")
|
||||
fun `find lower block closer to the height`(
|
||||
reader: JsonRpcReader,
|
||||
reader: ChainReader,
|
||||
detectorClass: Class<LowerBoundBlockDetector>,
|
||||
) {
|
||||
val head = mock<Head> {
|
||||
@@ -49,7 +47,7 @@ class RecursiveLowerBoundBlockDetectorTest {
|
||||
@ParameterizedTest
|
||||
@MethodSource("detectorsFirstBlock")
|
||||
fun `lower block is 0x1`(
|
||||
reader: JsonRpcReader,
|
||||
reader: ChainReader,
|
||||
detectorClass: Class<LowerBoundBlockDetector>,
|
||||
) {
|
||||
val head = mock<Head> {
|
||||
@@ -84,15 +82,15 @@ class RecursiveLowerBoundBlockDetectorTest {
|
||||
@JvmStatic
|
||||
fun detectors(): List<Arguments> = listOf(
|
||||
Arguments.of(
|
||||
mock<JsonRpcReader> {
|
||||
mock<ChainReader> {
|
||||
blocks.forEach {
|
||||
if (it == 17964844L) {
|
||||
on {
|
||||
read(JsonRpcRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex())))
|
||||
} doReturn Mono.just(JsonRpcResponse(ByteArray(0), null))
|
||||
read(ChainRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex())))
|
||||
} doReturn Mono.just(ChainResponse(ByteArray(0), null))
|
||||
} else {
|
||||
on {
|
||||
read(JsonRpcRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex())))
|
||||
read(ChainRequest("eth_getBalance", ListParams("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0", it.toHex())))
|
||||
} doReturn Mono.error(RuntimeException("missing trie node"))
|
||||
}
|
||||
}
|
||||
@@ -100,21 +98,21 @@ class RecursiveLowerBoundBlockDetectorTest {
|
||||
EthereumLowerBoundBlockDetector::class.java,
|
||||
),
|
||||
Arguments.of(
|
||||
mock<JsonRpcReader> {
|
||||
mock<ChainReader> {
|
||||
blocks.forEach {
|
||||
if (it == 17964844L) {
|
||||
on {
|
||||
read(JsonRpcRequest("chain_getBlockHash", ListParams(it.toHex())))
|
||||
} doReturn Mono.just(JsonRpcResponse("\"$hash1\"".toByteArray(), null))
|
||||
read(ChainRequest("chain_getBlockHash", ListParams(it.toHex())))
|
||||
} doReturn Mono.just(ChainResponse("\"$hash1\"".toByteArray(), null))
|
||||
on {
|
||||
read(JsonRpcRequest("state_getMetadata", ListParams(hash1)))
|
||||
} doReturn Mono.just(JsonRpcResponse(ByteArray(0), null))
|
||||
read(ChainRequest("state_getMetadata", ListParams(hash1)))
|
||||
} doReturn Mono.just(ChainResponse(ByteArray(0), null))
|
||||
} else {
|
||||
on {
|
||||
read(JsonRpcRequest("chain_getBlockHash", ListParams(it.toHex())))
|
||||
} doReturn Mono.just(JsonRpcResponse("\"$hash2\"".toByteArray(), null))
|
||||
read(ChainRequest("chain_getBlockHash", ListParams(it.toHex())))
|
||||
} doReturn Mono.just(ChainResponse("\"$hash2\"".toByteArray(), null))
|
||||
on {
|
||||
read(JsonRpcRequest("state_getMetadata", ListParams(hash2)))
|
||||
read(ChainRequest("state_getMetadata", ListParams(hash2)))
|
||||
} doReturn Mono.error(RuntimeException("State already discarded for"))
|
||||
}
|
||||
}
|
||||
@@ -126,16 +124,16 @@ class RecursiveLowerBoundBlockDetectorTest {
|
||||
@JvmStatic
|
||||
fun detectorsFirstBlock(): List<Arguments> = listOf(
|
||||
Arguments.of(
|
||||
mock<JsonRpcReader> {
|
||||
mock<ChainReader> {
|
||||
on {
|
||||
read(any())
|
||||
} doReturn Mono.just(JsonRpcResponse("\"0x1\"".toByteArray(), null))
|
||||
} doReturn Mono.just(ChainResponse("\"0x1\"".toByteArray(), null))
|
||||
},
|
||||
PolkadotLowerBoundBlockDetector::class.java,
|
||||
),
|
||||
Arguments.of(
|
||||
mock<JsonRpcReader> {
|
||||
on { read(any()) } doReturn Mono.just(JsonRpcResponse(ByteArray(0), null))
|
||||
mock<ChainReader> {
|
||||
on { read(any()) } doReturn Mono.just(ChainResponse(ByteArray(0), null))
|
||||
},
|
||||
EthereumLowerBoundBlockDetector::class.java,
|
||||
),
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.emeraldpay.dshackle.upstream.beaconchain
|
||||
|
||||
import io.emeraldpay.dshackle.data.BlockContainer
|
||||
import io.emeraldpay.dshackle.data.BlockId
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigInteger
|
||||
import java.time.Instant
|
||||
|
||||
class BeaconChainSpecificTest {
|
||||
|
||||
@Test
|
||||
fun `test getting latest block`() {
|
||||
val header = """
|
||||
{
|
||||
"execution_optimistic": false,
|
||||
"finalized": false,
|
||||
"data": {
|
||||
"header": {
|
||||
"message": {
|
||||
"slot": "8639623",
|
||||
"proposer_index": "830020",
|
||||
"parent_root": "0x3306b3f2d5f4de709a9b13c51ca7bbeab41ee93f44c6873baf6a59495620dbbf",
|
||||
"state_root": "0x1445392736bd2485e5afaaea62a642bea69f75379653e7a1f0846806af8d6d75",
|
||||
"body_root": "0x0a02f2980fda874a050fc2f6fde63ebbeb749318f23325673f1fc4af2b252e84"
|
||||
},
|
||||
"signature": "0x961b9c71530f2a55630e3c943aa054f6a863c86fd53b6644c1680bdb2d70a65a0082e5282fb3bf54a0167ba706fe78961289a652da13594a00d63c4ef8d60f53f515ce5ca1ced41b25a7689fd6e6773ec6b771e4e1f67c37eea2b8ef26c5eb4f"
|
||||
},
|
||||
"root": "0x3ae4855df015bf9906f59aa0c612396105fa7c11495b5596fa946672c3ce5674",
|
||||
"canonical": true
|
||||
}
|
||||
}
|
||||
""".trimIndent().toByteArray()
|
||||
val expected = BlockContainer(
|
||||
height = 8639623,
|
||||
hash = BlockId.from("0x3ae4855df015bf9906f59aa0c612396105fa7c11495b5596fa946672c3ce5674"),
|
||||
difficulty = BigInteger.ZERO,
|
||||
timestamp = Instant.EPOCH,
|
||||
full = false,
|
||||
json = header,
|
||||
transactions = emptyList(),
|
||||
upstreamId = "upId",
|
||||
parentHash = BlockId.from("0x3306b3f2d5f4de709a9b13c51ca7bbeab41ee93f44c6873baf6a59495620dbbf"),
|
||||
parsed = null,
|
||||
)
|
||||
|
||||
val block = BeaconChainSpecific.parseBlock(header, "upId")
|
||||
assertEquals(expected, block)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package io.emeraldpay.dshackle.upstream.beaconchain
|
||||
|
||||
import io.emeraldpay.dshackle.foundation.ChainOptions
|
||||
import io.emeraldpay.dshackle.reader.ChainReader
|
||||
import io.emeraldpay.dshackle.upstream.ChainCallError
|
||||
import io.emeraldpay.dshackle.upstream.ChainRequest
|
||||
import io.emeraldpay.dshackle.upstream.ChainResponse
|
||||
import io.emeraldpay.dshackle.upstream.Head
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.UpstreamAvailability
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.RestParams
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.mock
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
class BeaconChainValidatorTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
fun `validation beacon chain`(
|
||||
respHealth: Mono<ChainResponse>,
|
||||
respSync: Mono<ChainResponse>,
|
||||
respPeers: Mono<ChainResponse>,
|
||||
expected: UpstreamAvailability,
|
||||
) {
|
||||
val reader = mock<ChainReader> {
|
||||
on { read(ChainRequest("GET#/eth/v1/node/health", RestParams.emptyParams())) } doReturn
|
||||
respHealth
|
||||
on { read(ChainRequest("GET#/eth/v1/node/syncing", RestParams.emptyParams())) } doReturn
|
||||
respSync
|
||||
on { read(ChainRequest("GET#/eth/v1/node/peer_count", RestParams.emptyParams())) } doReturn
|
||||
respPeers
|
||||
}
|
||||
val upstream = mock<Upstream> {
|
||||
on { getIngressReader() } doReturn reader
|
||||
on { getHead() } doReturn mock<Head>()
|
||||
}
|
||||
val validator = BeaconChainValidator(upstream, ChainOptions.PartialOptions.getDefaults().buildOptions())
|
||||
|
||||
val result = validator.validate().block()
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun data(): List<Arguments> {
|
||||
return listOf(
|
||||
Arguments.of(
|
||||
Mono.just(ChainResponse(ByteArray(0), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)),
|
||||
UpstreamAvailability.OK,
|
||||
),
|
||||
Arguments.of(
|
||||
Mono.error<ChainResponse>(RuntimeException("error")),
|
||||
Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)),
|
||||
UpstreamAvailability.UNAVAILABLE,
|
||||
),
|
||||
Arguments.of(
|
||||
Mono.just(ChainResponse(ByteArray(0), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"is_syncing": true}}""".toByteArray(), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)),
|
||||
UpstreamAvailability.SYNCING,
|
||||
),
|
||||
Arguments.of(
|
||||
Mono.just(ChainResponse(ByteArray(0), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"connected": "0"}}""".toByteArray(), null)),
|
||||
UpstreamAvailability.IMMATURE,
|
||||
),
|
||||
Arguments.of(
|
||||
Mono.just(ChainResponse(ByteArray(0), null)),
|
||||
Mono.error<ChainResponse>(RuntimeException("error")),
|
||||
Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)),
|
||||
UpstreamAvailability.UNAVAILABLE,
|
||||
),
|
||||
Arguments.of(
|
||||
Mono.just(ChainResponse(ByteArray(0), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)),
|
||||
Mono.error<ChainResponse>(RuntimeException("error")),
|
||||
UpstreamAvailability.UNAVAILABLE,
|
||||
),
|
||||
Arguments.of(
|
||||
Mono.just(ChainResponse(null, ChainCallError(1, "Error"))),
|
||||
Mono.just(ChainResponse("""{"data": {"is_syncing": false}}""".toByteArray(), null)),
|
||||
Mono.just(ChainResponse("""{"data": {"connected": "20"}}""".toByteArray(), null)),
|
||||
UpstreamAvailability.UNAVAILABLE,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class GenericConnectorFactoryCreatorTest {
|
||||
|
||||
mockConstruction(GenericRpcHead::class.java) { _, ctx -> args = ctx.arguments() }
|
||||
.use {
|
||||
factory.createConnectorFactoryCreator(
|
||||
factory.createConnectorFactory(
|
||||
"id",
|
||||
UpstreamsConfig.RpcConnection(
|
||||
UpstreamsConfig.HttpEndpoint(URI("http://localhost")),
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.emeraldpay.dshackle.upstream.restclient
|
||||
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.Arguments
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
class RestRequestParserTest {
|
||||
|
||||
@Test
|
||||
fun `transform query params into string`() {
|
||||
val queryParams = mapOf(
|
||||
"first" to "coolParam",
|
||||
"second" to "moreCoolParam",
|
||||
)
|
||||
|
||||
val result = RestRequestParser.transformQueryParams(queryParams)
|
||||
|
||||
assertEquals("?first=coolParam&second=moreCoolParam", result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `transform query params into empty string`() {
|
||||
val result = RestRequestParser.transformQueryParams(emptyMap())
|
||||
|
||||
assertEquals("", result)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("data")
|
||||
fun `transform path params into path with these params`(path: String, params: List<String>, expected: String) {
|
||||
val result = RestRequestParser.transformPathParams(path, params)
|
||||
|
||||
assertEquals(expected, result)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun data(): List<Arguments> {
|
||||
return listOf(
|
||||
Arguments.of("/eth/v1/blocks/*/state/*/root", listOf("123", "678"), "/eth/v1/blocks/123/state/678/root"),
|
||||
Arguments.of("/eth/v1/blocks/*/state/*", listOf("123", "678"), "/eth/v1/blocks/123/state/678"),
|
||||
Arguments.of("/eth/v1/blocks", emptyList<String>(), "/eth/v1/blocks"),
|
||||
Arguments.of("/eth/v1/blocks/*", listOf("123", "678"), "/eth/v1/blocks/123"),
|
||||
Arguments.of("/eth/v1/blocks", listOf("123", "678"), "/eth/v1/blocks"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
package io.emeraldpay.dshackle.upstream.rpcclient.stream
|
||||
|
||||
import io.emeraldpay.dshackle.upstream.stream.AggregateResponse
|
||||
import io.emeraldpay.dshackle.upstream.stream.Chunk
|
||||
import io.emeraldpay.dshackle.upstream.stream.SingleResponse
|
||||
import io.emeraldpay.dshackle.upstream.stream.StreamResponse
|
||||
import org.junit.jupiter.api.Assertions.assertArrayEquals
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNotNull
|
||||
|
||||
@@ -2,10 +2,10 @@ package io.emeraldpay.dshackle.upstream.solana
|
||||
|
||||
import io.emeraldpay.dshackle.Chain
|
||||
import io.emeraldpay.dshackle.Global
|
||||
import io.emeraldpay.dshackle.reader.JsonRpcReader
|
||||
import io.emeraldpay.dshackle.reader.ChainReader
|
||||
import io.emeraldpay.dshackle.upstream.ChainRequest
|
||||
import io.emeraldpay.dshackle.upstream.ChainResponse
|
||||
import io.emeraldpay.dshackle.upstream.Upstream
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcRequest
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.JsonRpcResponse
|
||||
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -19,12 +19,12 @@ class SolanaLowerBoundBlockDetectorTest {
|
||||
|
||||
@Test
|
||||
fun `get solana lower block and slot`() {
|
||||
val reader = mock<JsonRpcReader> {
|
||||
on { read(JsonRpcRequest("getFirstAvailableBlock", ListParams())) } doReturn
|
||||
Mono.just(JsonRpcResponse("25000000".toByteArray(), null))
|
||||
val reader = mock<ChainReader> {
|
||||
on { read(ChainRequest("getFirstAvailableBlock", ListParams())) } doReturn
|
||||
Mono.just(ChainResponse("25000000".toByteArray(), null))
|
||||
on {
|
||||
read(
|
||||
JsonRpcRequest(
|
||||
ChainRequest(
|
||||
"getBlock",
|
||||
ListParams(
|
||||
25000000L,
|
||||
@@ -37,7 +37,7 @@ class SolanaLowerBoundBlockDetectorTest {
|
||||
),
|
||||
)
|
||||
} doReturn Mono.just(
|
||||
JsonRpcResponse(
|
||||
ChainResponse(
|
||||
Global.objectMapper.writeValueAsBytes(
|
||||
mapOf(
|
||||
"blockHeight" to 21000000,
|
||||
|
||||
Reference in New Issue
Block a user