Detect and send node version in api (#445)

This commit is contained in:
KirillPamPam
2024-03-29 19:25:22 +04:00
committed by GitHub
parent 5fedc28d24
commit 7e9709bb6f
39 changed files with 459 additions and 151 deletions

View File

@@ -20,6 +20,7 @@ import io.emeraldpay.dshackle.monitoring.accesslog.AccessHandlerHttp
import io.emeraldpay.dshackle.rpc.NativeCall
import io.emeraldpay.dshackle.rpc.NativeSubscribe
import io.emeraldpay.dshackle.upstream.Selector
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.ethereum.json.RequestJson
import io.micrometer.core.instrument.Counter
import reactor.core.publisher.Flux
@@ -85,7 +86,7 @@ class WebsocketHandlerSpec extends Specification {
def "Respond to a single call"() {
setup:
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, "test", null, null)
def response = new NativeCall.CallResult(0, null, '{"foo": 1}'.bytes, null, null, new Upstream.UpstreamSettingsData("test"), null, null)
def nativeCall = Mock(NativeCall) {
1 * it.nativeCallResult(_) >> Flux.fromIterable([response])

View File

@@ -120,14 +120,10 @@ class NativeCallSpec extends Specification {
setup:
def quorum = new AlwaysQuorum()
def ups = Mock(Upstream) {
_ * nodeId() >> (byte) 1
}
def nativeCall = nativeCall()
nativeCall.requestReaderFactory = Mock(RequestReaderFactory) {
1 * create(_) >> Mock(RequestReader) {
1 * read(_) >> Mono.just(new RequestReader.Result("\"foo\"".bytes, null, 1, ups, null))
1 * read(_) >> Mono.just(new RequestReader.Result("\"foo\"".bytes, null, 1, new Upstream.UpstreamSettingsData((byte)1, "test", "v"), null))
}
}
def call = new NativeCall.ValidCallContext(1, 10, TestingCommons.multistream(TestingCommons.api()), Selector.empty, quorum,
@@ -250,7 +246,7 @@ class NativeCallSpec extends Specification {
when:
def resp = nativeCall.buildResponse(
new NativeCall.CallResult(1561, 10, objectMapper.writeValueAsBytes(json), null, new ResponseSigner.Signature("sig1".bytes, "test", 100), "test", null, null)
new NativeCall.CallResult(1561, 10, objectMapper.writeValueAsBytes(json), null, new ResponseSigner.Signature("sig1".bytes, "test", 100), new Upstream.UpstreamSettingsData("test"), null, null)
)
then:
resp.id == 1561
@@ -597,9 +593,6 @@ class NativeCallSpec extends Specification {
def "Decorate eth_newFilter result"() {
setup:
def ups = Mock(Upstream) {
_ * nodeId() >> (byte)255
}
def quorum = new AlwaysQuorum()
def methods = new ManagedCallMethods(
new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false),
@@ -616,7 +609,7 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall(multistreamHolder)
nativeCall.requestReaderFactory = Mock(RequestReaderFactory) {
1 * create(_) >> Mock(RequestReader) {
1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, ups, null))
1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, new Upstream.UpstreamSettingsData((byte) 255, "", ""), null))
}
}
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,
@@ -633,9 +626,6 @@ class NativeCallSpec extends Specification {
def "Decorate eth_newFilter result with short nodeId"() {
setup:
def ups = Mock(Upstream) {
_ * nodeId() >> (byte)1
}
def quorum = new AlwaysQuorum()
def methods = new ManagedCallMethods(
new DefaultEthereumMethods(Chain.ETHEREUM__MAINNET, false),
@@ -652,7 +642,7 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall(multistreamHolder)
nativeCall.requestReaderFactory = Mock(RequestReaderFactory) {
1 * create(_) >> Mock(RequestReader) {
1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, ups, null))
1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, new Upstream.UpstreamSettingsData((byte) 1, "", ""), null))
}
}
def call = new NativeCall.ValidCallContext(1, 10, multistream, Selector.empty, quorum,

View File

@@ -74,7 +74,7 @@ class GenericUpstreamMock extends GenericUpstream {
ChainConfig.default(),
new ConnectorFactoryMock(api, new EthereumHeadMock()),
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.&validator,
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.&labelDetector,
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.&upstreamSettingsDetector,
io.emeraldpay.dshackle.upstream.starknet.StarknetChainSpecific.INSTANCE.&lowerBoundBlockDetector,
)
this.ethereumHeadMock = this.getHead() as EthereumHeadMock

View File

@@ -76,7 +76,7 @@ class FilteredApisSpec extends Specification {
ChainsConfig.ChainConfig.default(),
connectorFactory,
cs.&validator,
cs.&labelDetector,
cs.&upstreamSettingsDetector,
cs.&lowerBoundBlockDetector
)
}

View File

@@ -32,7 +32,7 @@ class EthereumDirectReaderSpec extends Specification {
String hash1 = "0x40d15edaff9acdabd2a1c96fd5f683b3300aad34e7015f34def3c56ba8a7ffb5"
String address1 = "0xe0aadb0a012dbcdc529c4c743d3e0385a0b54d3d"
Upstream resolver = TestingCommons.upstream()
Upstream.UpstreamSettingsData data = new Upstream.UpstreamSettingsData("test")
def "Reads block by hash"() {
setup:
@@ -54,7 +54,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null)
Global.objectMapper.writeValueAsBytes(json), null, 1, data, null)
)
}
}
@@ -81,7 +81,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getBlockByHash", new ListParams([hash1, false]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes(null), null, 1, data, null
)
)
}
@@ -114,7 +114,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getBlockByNumber", new ListParams(["0x64", false]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes(json), null, 1, data, null
)
)
}
@@ -146,7 +146,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getLogs", new ListParams([Map.of("blockHash", hash1)]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes([json]), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes([json]), null, 1, data, null
)
)
}
@@ -179,7 +179,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getTransactionByHash", new ListParams([hash1]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes(json), null, 1, data, null
)
)
}
@@ -212,7 +212,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getTransactionReceipt", new ListParams([hash1]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes(json), null, 1, data, null
)
)
}
@@ -246,7 +246,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getTransactionReceipt", new ListParams([hash1]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes(json), null, 1, data, null
)
)
}
@@ -271,7 +271,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getTransactionByHash", new ListParams([hash1]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(null), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes(null), null, 1, data, null
)
)
}
@@ -301,7 +301,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getBalance", new ListParams([address1, "latest"]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, data, null
)
)
}
@@ -332,7 +332,7 @@ class EthereumDirectReaderSpec extends Specification {
1 * create(_) >> Mock(RequestReader) {
1 * read(new ChainRequest("eth_getBalance", new ListParams([address1, "0xa8c9bb"]))) >> Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, resolver, null
Global.objectMapper.writeValueAsBytes("0x100"), null, 1, data, null
)
)
}
@@ -361,7 +361,7 @@ class EthereumDirectReaderSpec extends Specification {
}
def result = Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null)
Global.objectMapper.writeValueAsBytes(json), null, 1, data, null)
)
EthereumDirectReader ethereumDirectReader = new EthereumDirectReader(
Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()
@@ -401,7 +401,7 @@ class EthereumDirectReaderSpec extends Specification {
}
def result = Mono.just(
new RequestReader.Result(
Global.objectMapper.writeValueAsBytes(json), null, 1, resolver, null)
Global.objectMapper.writeValueAsBytes(json), null, 1, data, null)
)
EthereumDirectReader ethereumDirectReader = new EthereumDirectReader(
Stub(Multistream), Caches.default(), new CurrentBlockCache(), calls, TestingCommons.tracerMock()

View File

@@ -15,7 +15,7 @@ import spock.lang.Specification
import java.time.Duration
class EthereumLabelsDetectorSpec extends Specification {
class EthereumUpstreamSettingsDetectorSpec extends Specification {
def "Detect labels"() {
setup:
@@ -27,7 +27,7 @@ class EthereumLabelsDetectorSpec extends Specification {
answer("eth_getBalance", ["0x0000000000000000000000000000000000000000", "0x2710"], "")
}
)
def detector = new EthereumLabelsDetector(up.getIngressReader(), Chain.ETHEREUM__MAINNET)
def detector = new EthereumUpstreamSettingsDetector(up, Chain.ETHEREUM__MAINNET)
when:
def act = detector.detectLabels()
@@ -51,7 +51,7 @@ class EthereumLabelsDetectorSpec extends Specification {
def "No any label"() {
setup:
def up = Mock(DefaultUpstream) {
1 * getIngressReader() >> Mock(Reader) {
4 * getIngressReader() >> Mock(Reader) {
1 * read(new ChainRequest("web3_clientVersion", new ListParams())) >>
Mono.just(new ChainResponse('no/v1.19.3+e8ac1da4/linux-x64/dotnet7.0.8'.getBytes(), null))
1 * read(new ChainRequest("eth_blockNumber", new ListParams())) >>
@@ -62,7 +62,7 @@ class EthereumLabelsDetectorSpec extends Specification {
Mono.just(new ChainResponse("".getBytes(), null))
}
}
def detector = new EthereumLabelsDetector(up.getIngressReader(), Chain.ETHEREUM__MAINNET)
def detector = new EthereumUpstreamSettingsDetector(up, Chain.ETHEREUM__MAINNET)
when:
def act = detector.detectLabels()
then:
@@ -70,4 +70,22 @@ class EthereumLabelsDetectorSpec extends Specification {
.expectComplete()
.verify(Duration.ofSeconds(1))
}
def "Detect client version"() {
setup:
def up = Mock(DefaultUpstream) {
2 * getIngressReader() >> Mock(Reader) {
1 * read(new ChainRequest("web3_clientVersion", new ListParams())) >>
Mono.just(new ChainResponse('"Erigon/v1.12.0-stable-e501b3b0/linux-amd64/go1.20.3"'.getBytes(), null))
}
}
def detector = new EthereumUpstreamSettingsDetector(up, Chain.ETHEREUM__MAINNET)
when:
def act = detector.detectClientVersion()
then:
StepVerifier.create(act)
.expectNext("Erigon/v1.12.0-stable-e501b3b0/linux-amd64/go1.20.3")
.expectComplete()
.verify(Duration.ofSeconds(1))
}
}

View File

@@ -5,6 +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.UNKNOWN_CLIENT_VERSION
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.signature.ResponseSigner
import io.emeraldpay.dshackle.upstream.stream.Chunk
import org.junit.jupiter.api.Test
@@ -31,7 +33,7 @@ class NativeCallTest {
),
) {
on { nativeCallResult(request) } doReturn Flux.just(
NativeCall.CallResult.ok(1, null, "0x1".toByteArray(), null, "id", null),
NativeCall.CallResult.ok(1, null, "0x1".toByteArray(), null, Upstream.UpstreamSettingsData("id"), null),
)
}
@@ -39,6 +41,7 @@ class NativeCallTest {
.expectNext(
NativeCallReplyItem.newBuilder()
.setUpstreamId("id")
.setUpstreamNodeVersion(UNKNOWN_CLIENT_VERSION)
.setId(1)
.setSucceed(true)
.setPayload(ByteString.copyFrom("0x1".toByteArray()))
@@ -60,7 +63,7 @@ class NativeCallTest {
),
) {
on { nativeCallResult(request) } doReturn Flux.just(
NativeCall.CallResult(1, null, null, NativeCall.CallError(50001, "message", null, null, "upId"), null, null),
NativeCall.CallResult(1, null, null, NativeCall.CallError(50001, "message", null, null, Upstream.UpstreamSettingsData("upId")), null, null),
)
}
@@ -69,6 +72,7 @@ class NativeCallTest {
NativeCallReplyItem.newBuilder()
.setUpstreamId("upId")
.setId(1)
.setUpstreamNodeVersion(UNKNOWN_CLIENT_VERSION)
.setSucceed(false)
.setErrorMessage("message")
.setItemErrorCode(50001)
@@ -95,7 +99,7 @@ class NativeCallTest {
),
) {
on { nativeCallResult(request) } doReturn Flux.just(
NativeCall.CallResult.ok(1, null, "".toByteArray(), null, "upId", null, chunks),
NativeCall.CallResult.ok(1, null, "".toByteArray(), null, Upstream.UpstreamSettingsData("upId"), null, chunks),
)
}
@@ -104,6 +108,7 @@ class NativeCallTest {
NativeCallReplyItem.newBuilder()
.setUpstreamId("upId")
.setId(1)
.setUpstreamNodeVersion(UNKNOWN_CLIENT_VERSION)
.setChunked(true)
.setSucceed(true)
.setPayload(ByteString.copyFrom("0x1".toByteArray()))

View File

@@ -0,0 +1,46 @@
package io.emeraldpay.dshackle.upstream.near
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.ListParams
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
class NearUpstreamSettingsDetectorTest {
@Test
fun `detect client version`() {
val reader = mock<ChainReader> {
on { read(ChainRequest("status", ListParams())) } doReturn
Mono.just(
ChainResponse(
"""
{
"version": {
"build": "1.38.1",
"rustc_version": "1.75.0",
"version": "1.38.1"
}
}
""".trimIndent().toByteArray(),
null,
),
)
}
val up = mock<Upstream> {
on { getIngressReader() } doReturn reader
}
val detector = NearUpstreamSettingsDetector(up)
StepVerifier.create(detector.detectClientVersion())
.expectNext("1.38.1")
.expectComplete()
.verify()
}
}

View File

@@ -0,0 +1,71 @@
package io.emeraldpay.dshackle.upstream.solana
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.UNKNOWN_CLIENT_VERSION
import io.emeraldpay.dshackle.upstream.Upstream
import io.emeraldpay.dshackle.upstream.rpcclient.ListParams
import org.junit.jupiter.api.Test
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import reactor.core.publisher.Mono
import reactor.test.StepVerifier
class SolanaUpstreamSettingsDetectorTest {
@Test
fun `detect client version`() {
val reader = mock<ChainReader> {
on { read(ChainRequest("getVersion", ListParams())) } doReturn
Mono.just(
ChainResponse(
"""
{
"feature-set": 2891131721,
"solana-core": "1.16.7"
}
""".trimIndent().toByteArray(),
null,
),
)
}
val up = mock<Upstream> {
on { getIngressReader() } doReturn reader
}
val detector = SolanaUpstreamSettingsDetector(up)
StepVerifier.create(detector.detectClientVersion())
.expectNext("1.16.7")
.expectComplete()
.verify()
}
@Test
fun `unknown client if there is an error`() {
val reader = mock<ChainReader> {
on { read(ChainRequest("getVersion", ListParams())) } doReturn
Mono.just(
ChainResponse(
null,
ChainCallError(
1,
"message",
),
),
)
}
val up = mock<Upstream> {
on { getIngressReader() } doReturn reader
}
val detector = SolanaUpstreamSettingsDetector(up)
StepVerifier.create(detector.detectClientVersion())
.expectNext(UNKNOWN_CLIENT_VERSION)
.expectComplete()
.verify()
}
}