Support beacon chain (#436)

This commit is contained in:
KirillPamPam
2024-03-15 17:46:29 +04:00
committed by GitHub
parent d5414f947e
commit d44b72ec69
152 changed files with 2498 additions and 1382 deletions

View File

@@ -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,

View File

@@ -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

View File

@@ -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,
),

View File

@@ -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)
}
}

View File

@@ -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,
),
)
}
}
}

View File

@@ -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")),

View File

@@ -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"),
)
}
}
}

View File

@@ -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

View File

@@ -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,