Ton chain id validator (#589)
This commit is contained in:
@@ -0,0 +1,82 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.ton
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
import io.emeraldpay.dshackle.Global
|
||||||
|
import io.emeraldpay.dshackle.upstream.ChainRequest
|
||||||
|
import io.emeraldpay.dshackle.upstream.ChainResponse
|
||||||
|
import io.emeraldpay.dshackle.upstream.SingleValidator
|
||||||
|
import io.emeraldpay.dshackle.upstream.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.RestParams
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
|
||||||
|
class TonChainIdValidator(
|
||||||
|
private val upstream: Upstream,
|
||||||
|
private val chain: Chain,
|
||||||
|
) : SingleValidator<ValidateUpstreamSettingsResult> {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val log: Logger = LoggerFactory.getLogger(TonChainIdValidator::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun validate(onError: ValidateUpstreamSettingsResult): Mono<ValidateUpstreamSettingsResult> {
|
||||||
|
return upstream.getIngressReader()
|
||||||
|
.read(TonHttpSpecific.latestBlockRequest())
|
||||||
|
.flatMap(ChainResponse::requireResult)
|
||||||
|
.flatMap {
|
||||||
|
val tonMasterchainInfo = Global.objectMapper.readValue<TonMasterchainInfo>(it)
|
||||||
|
|
||||||
|
upstream.getIngressReader()
|
||||||
|
.read(
|
||||||
|
ChainRequest(
|
||||||
|
"GET#/getBlockHeader",
|
||||||
|
RestParams(
|
||||||
|
emptyList(),
|
||||||
|
listOf(
|
||||||
|
"workchain" to tonMasterchainInfo.result.last.workchain.toString(),
|
||||||
|
"shard" to tonMasterchainInfo.result.last.shard,
|
||||||
|
"seqno" to tonMasterchainInfo.result.last.seqno.toString(),
|
||||||
|
),
|
||||||
|
emptyList(),
|
||||||
|
ByteArray(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.flatMap(ChainResponse::requireResult)
|
||||||
|
.flatMap { headerResponse ->
|
||||||
|
val blockHeader = Global.objectMapper.readValue<JsonNode>(headerResponse)
|
||||||
|
|
||||||
|
val globalId = blockHeader.get("result")?.get("global_id")?.asText()
|
||||||
|
if (globalId == null) {
|
||||||
|
log.warn(
|
||||||
|
"Couldn't receive global_id from the block header of upstream {} for workchain {}, shard {}, seqno {}",
|
||||||
|
upstream.getId(),
|
||||||
|
tonMasterchainInfo.result.last.workchain,
|
||||||
|
tonMasterchainInfo.result.last.shard,
|
||||||
|
tonMasterchainInfo.result.last.seqno,
|
||||||
|
)
|
||||||
|
Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR)
|
||||||
|
} else {
|
||||||
|
if (globalId == chain.chainId) {
|
||||||
|
Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_VALID)
|
||||||
|
} else {
|
||||||
|
log.warn(
|
||||||
|
"Wrong chainId {} for {}, expected {}",
|
||||||
|
globalId,
|
||||||
|
chain,
|
||||||
|
chain.chainId,
|
||||||
|
)
|
||||||
|
Mono.just(ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onErrorResume {
|
||||||
|
log.error("Error during chain validation of upstream {}, reason - {}", upstream.getId(), it.message)
|
||||||
|
Mono.just(onError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,7 +87,20 @@ object TonHttpSpecific : AbstractPollChainSpecific() {
|
|||||||
config: ChainConfig,
|
config: ChainConfig,
|
||||||
): List<SingleValidator<ValidateUpstreamSettingsResult>> {
|
): List<SingleValidator<ValidateUpstreamSettingsResult>> {
|
||||||
// add check generic block
|
// add check generic block
|
||||||
return emptyList()
|
return listOf(
|
||||||
|
TonChainIdValidator(upstream, chain),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chainSettingsValidator(
|
||||||
|
chain: Chain,
|
||||||
|
upstream: Upstream,
|
||||||
|
reader: ChainReader?,
|
||||||
|
): SingleValidator<ValidateUpstreamSettingsResult>? {
|
||||||
|
if (upstream.getOptions().disableUpstreamValidation) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return TonChainIdValidator(upstream, chain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package io.emeraldpay.dshackle.upstream.ton
|
||||||
|
|
||||||
|
import io.emeraldpay.dshackle.Chain
|
||||||
|
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.Upstream
|
||||||
|
import io.emeraldpay.dshackle.upstream.ValidateUpstreamSettingsResult
|
||||||
|
import io.emeraldpay.dshackle.upstream.rpcclient.RestParams
|
||||||
|
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
|
||||||
|
import org.mockito.kotlin.argThat
|
||||||
|
import org.mockito.kotlin.doReturn
|
||||||
|
import org.mockito.kotlin.mock
|
||||||
|
import org.mockito.kotlin.never
|
||||||
|
import org.mockito.kotlin.verify
|
||||||
|
import reactor.core.publisher.Mono
|
||||||
|
import reactor.test.StepVerifier
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
class TonChainIdValidatorTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("data")
|
||||||
|
fun `validate ton chainId`(
|
||||||
|
blockFilePath: String,
|
||||||
|
expectedResult: ValidateUpstreamSettingsResult,
|
||||||
|
) {
|
||||||
|
val masterchainInfoBytes = this::class.java.getResource("/blocks/ton/masterchain-info.json")!!.readBytes()
|
||||||
|
val block = this::class.java.getResource(blockFilePath)!!.readBytes()
|
||||||
|
val blockReq = ChainRequest(
|
||||||
|
"GET#/getBlockHeader",
|
||||||
|
RestParams(
|
||||||
|
emptyList(),
|
||||||
|
listOf(
|
||||||
|
"workchain" to "-1",
|
||||||
|
"shard" to "-9223372036854775808",
|
||||||
|
"seqno" to "41689287",
|
||||||
|
),
|
||||||
|
emptyList(),
|
||||||
|
ByteArray(0),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val reader = mock<ChainReader> {
|
||||||
|
on { read(ChainRequest("GET#/getMasterchainInfo", RestParams.emptyParams())) } doReturn
|
||||||
|
Mono.just(ChainResponse(masterchainInfoBytes, null))
|
||||||
|
on { read(blockReq) } doReturn Mono.just(ChainResponse(block, null))
|
||||||
|
}
|
||||||
|
val upstream = mock<Upstream> {
|
||||||
|
on { getIngressReader() } doReturn reader
|
||||||
|
}
|
||||||
|
val validator = TonChainIdValidator(upstream, Chain.TON__MAINNET)
|
||||||
|
|
||||||
|
StepVerifier.create(validator.validate(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR))
|
||||||
|
.expectSubscription()
|
||||||
|
.expectNext(expectedResult)
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(1))
|
||||||
|
|
||||||
|
verify(reader).read(ChainRequest("GET#/getMasterchainInfo", RestParams.emptyParams()))
|
||||||
|
verify(reader).read(blockReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `setting error if couldn't read from upstream getMasterchainInfo`() {
|
||||||
|
val reader = mock<ChainReader> {
|
||||||
|
on { read(ChainRequest("GET#/getMasterchainInfo", RestParams.emptyParams())) } doReturn
|
||||||
|
Mono.just(ChainResponse(null, ChainCallError(1, "Big error")))
|
||||||
|
}
|
||||||
|
val upstream = mock<Upstream> {
|
||||||
|
on { getIngressReader() } doReturn reader
|
||||||
|
}
|
||||||
|
val validator = TonChainIdValidator(upstream, Chain.TON__MAINNET)
|
||||||
|
|
||||||
|
StepVerifier.create(validator.validate(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR))
|
||||||
|
.expectSubscription()
|
||||||
|
.expectNext(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR)
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(1))
|
||||||
|
|
||||||
|
verify(reader).read(ChainRequest("GET#/getMasterchainInfo", RestParams.emptyParams()))
|
||||||
|
verify(reader, never()).read(argThat { method == "GET#/getBlockHeader" })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `setting error if couldn't read from upstream getBlockHeader`() {
|
||||||
|
val masterchainInfoBytes = this::class.java.getResource("/blocks/ton/masterchain-info.json")!!.readBytes()
|
||||||
|
val blockReq = ChainRequest(
|
||||||
|
"GET#/getBlockHeader",
|
||||||
|
RestParams(
|
||||||
|
emptyList(),
|
||||||
|
listOf(
|
||||||
|
"workchain" to "-1",
|
||||||
|
"shard" to "-9223372036854775808",
|
||||||
|
"seqno" to "41689287",
|
||||||
|
),
|
||||||
|
emptyList(),
|
||||||
|
ByteArray(0),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val reader = mock<ChainReader> {
|
||||||
|
on { read(ChainRequest("GET#/getMasterchainInfo", RestParams.emptyParams())) } doReturn
|
||||||
|
Mono.just(ChainResponse(masterchainInfoBytes, null))
|
||||||
|
on { read(blockReq) } doReturn Mono.just(ChainResponse(null, ChainCallError(1, "Super error")))
|
||||||
|
}
|
||||||
|
val upstream = mock<Upstream> {
|
||||||
|
on { getIngressReader() } doReturn reader
|
||||||
|
}
|
||||||
|
val validator = TonChainIdValidator(upstream, Chain.TON__MAINNET)
|
||||||
|
|
||||||
|
StepVerifier.create(validator.validate(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR))
|
||||||
|
.expectSubscription()
|
||||||
|
.expectNext(ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR)
|
||||||
|
.expectComplete()
|
||||||
|
.verify(Duration.ofSeconds(1))
|
||||||
|
|
||||||
|
verify(reader).read(ChainRequest("GET#/getMasterchainInfo", RestParams.emptyParams()))
|
||||||
|
verify(reader).read(blockReq)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun data(): List<Arguments> {
|
||||||
|
return listOf(
|
||||||
|
Arguments.of(
|
||||||
|
"/blocks/ton/ton-block.json",
|
||||||
|
ValidateUpstreamSettingsResult.UPSTREAM_VALID,
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
"/blocks/ton/ton-block-wrong.json",
|
||||||
|
ValidateUpstreamSettingsResult.UPSTREAM_FATAL_SETTINGS_ERROR,
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
"/blocks/ton/ton-block-missed-global-id.json",
|
||||||
|
ValidateUpstreamSettingsResult.UPSTREAM_SETTINGS_ERROR,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/test/resources/blocks/ton/masterchain-info.json
Normal file
24
src/test/resources/blocks/ton/masterchain-info.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"result": {
|
||||||
|
"@type": "blocks.masterchainInfo",
|
||||||
|
"last": {
|
||||||
|
"@type": "ton.blockIdExt",
|
||||||
|
"workchain": -1,
|
||||||
|
"shard": "-9223372036854775808",
|
||||||
|
"seqno": 41689287,
|
||||||
|
"root_hash": "kOdzFs2iOVrpBVpD5Wt1o9Z+eTJIjGXxMurjuKiIJgg=",
|
||||||
|
"file_hash": "2gGZ8/7b8ztvtzPaDbzsWzxvCSvwMoct1v2l9jtV2G0="
|
||||||
|
},
|
||||||
|
"state_root_hash": "0mgSkvXs8QIO94ol3bxKxe0Jcu34eaB+/anKRgBkuQI=",
|
||||||
|
"init": {
|
||||||
|
"@type": "ton.blockIdExt",
|
||||||
|
"workchain": -1,
|
||||||
|
"shard": "0",
|
||||||
|
"seqno": 0,
|
||||||
|
"root_hash": "F6OpKZKqvqeFp6CQmFomXNMfMj2EnaUSOXN+Mh+wVWk=",
|
||||||
|
"file_hash": "XplPz01CXAps5qeSWUtxcyBfdAo5zVb1N979KLSKD24="
|
||||||
|
},
|
||||||
|
"@extra": "1730733199.581394:6:0.46521772491020763"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"ok": false
|
||||||
|
}
|
||||||
15
src/test/resources/blocks/ton/ton-block-wrong.json
Normal file
15
src/test/resources/blocks/ton/ton-block-wrong.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"result": {
|
||||||
|
"@type": "blocks.header",
|
||||||
|
"id": {
|
||||||
|
"@type": "ton.blockIdExt",
|
||||||
|
"workchain": -1,
|
||||||
|
"shard": "-9223372036854775808",
|
||||||
|
"seqno": 41689287,
|
||||||
|
"root_hash": "kOdzFs2iOVrpBVpD5Wt1o9Z+eTJIjGXxMurjuKiIJgg=",
|
||||||
|
"file_hash": "2gGZ8/7b8ztvtzPaDbzsWzxvCSvwMoct1v2l9jtV2G0="
|
||||||
|
},
|
||||||
|
"global_id": -3345
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/test/resources/blocks/ton/ton-block.json
Normal file
15
src/test/resources/blocks/ton/ton-block.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"result": {
|
||||||
|
"@type": "blocks.header",
|
||||||
|
"id": {
|
||||||
|
"@type": "ton.blockIdExt",
|
||||||
|
"workchain": -1,
|
||||||
|
"shard": "-9223372036854775808",
|
||||||
|
"seqno": 41689287,
|
||||||
|
"root_hash": "kOdzFs2iOVrpBVpD5Wt1o9Z+eTJIjGXxMurjuKiIJgg=",
|
||||||
|
"file_hash": "2gGZ8/7b8ztvtzPaDbzsWzxvCSvwMoct1v2l9jtV2G0="
|
||||||
|
},
|
||||||
|
"global_id": -239
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user