Short nodeId (#541)

This commit is contained in:
KirillPamPam
2024-08-06 13:46:17 +04:00
committed by GitHub
parent 642e3851f8
commit 0f143c905e
20 changed files with 108 additions and 54 deletions

View File

@@ -252,8 +252,8 @@ class UpstreamsConfigReader(
return false return false
} }
return upstream.nodeId?.let { return upstream.nodeId?.let {
if (it !in 1..255) { if (it !in 1..65535) {
log.warn("Invalid node-id: $it. Must be in range [1, 255].") log.warn("Invalid node-id: $it. Must be in range [1, 65535].")
false false
} else if (!knownNodeIds.add(it)) { } else if (!knownNodeIds.add(it)) {
log.warn("Duplicated node-id: $it. Must be in unique.") log.warn("Duplicated node-id: $it. Must be in unique.")

View File

@@ -587,13 +587,23 @@ open class NativeCall(
val bytes = result.value val bytes = result.value
if (bytes.last() == quoteCode && result.resolvedUpstreamData.isNotEmpty()) { if (bytes.last() == quoteCode && result.resolvedUpstreamData.isNotEmpty()) {
val suffix = result.resolvedUpstreamData[0].nodeId val suffix = result.resolvedUpstreamData[0].nodeId
.toUByte() .toUShort()
.toString(16).padStart(2, padChar = '0').toByteArray() .toString(16).padStart(4, padChar = '0').toByteArray()
bytes[bytes.lastIndex] = suffix.first() return resultArray(bytes, suffix)
return bytes + suffix.last() + quoteCode
} }
return bytes return bytes
} }
private fun resultArray(bytes: ByteArray, suffix: ByteArray): ByteArray {
val newBytes = ByteArray(bytes.size + suffix.size)
newBytes[newBytes.size - 1] = quoteCode
var index = bytes.size - 1
System.arraycopy(bytes, 0, newBytes, 0, bytes.size - 1)
for (byteVal in suffix) newBytes[index++] = byteVal
return newBytes
}
} }
interface RequestDecorator { interface RequestDecorator {
@@ -608,7 +618,7 @@ open class NativeCall(
override fun processRequest(request: CallParams): CallParams { override fun processRequest(request: CallParams): CallParams {
if (request is ListParams) { if (request is ListParams) {
val filterId = request.list.first().toString() val filterId = request.list.first().toString()
val sanitized = filterId.substring(0, filterId.lastIndex - 1) val sanitized = filterId.substring(0, filterId.lastIndex - 3)
return ListParams(listOf(sanitized)) return ListParams(listOf(sanitized))
} }
return request return request

View File

@@ -24,7 +24,7 @@ open class GenericUpstreamCreator(
private val connectorFactoryCreatorResolver: ConnectorFactoryCreatorResolver, private val connectorFactoryCreatorResolver: ConnectorFactoryCreatorResolver,
private val versionRules: Supplier<CompatibleVersionsRules?>, private val versionRules: Supplier<CompatibleVersionsRules?>,
) : UpstreamCreator(chainsConfig, indexConfig, callTargets) { ) : UpstreamCreator(chainsConfig, indexConfig, callTargets) {
private val hashes: MutableMap<Byte, Boolean> = HashMap() private val hashes = HashSet<Short>()
override fun createUpstream( override fun createUpstream(
upstreamsConfig: UpstreamsConfig.Upstream<*>, upstreamsConfig: UpstreamsConfig.Upstream<*>,

View File

@@ -22,26 +22,28 @@ abstract class UpstreamCreator(
protected val log: Logger = LoggerFactory.getLogger(this::class.java) protected val log: Logger = LoggerFactory.getLogger(this::class.java)
companion object { companion object {
fun getHash(nodeId: Int?, obj: Any, hashes: MutableMap<Byte, Boolean>): Byte = fun getHash(nodeId: Int?, obj: Any, hashes: MutableSet<Short>): Short {
nodeId?.toByte() ?: (obj.hashCode() % 255).let { val hash = nodeId?.toShort()
if (it == 0) 1 else it ?: run {
}.let { nonZeroHash -> (obj.hashCode() % 65535)
listOf<Function<Int, Int>>( .let { if (it == 0) 1 else it }
Function { i -> i }, .let { nonZeroHash ->
Function { i -> (-i) }, listOf<Function<Int, Int>>(
Function { i -> 127 - abs(i) }, Function { i -> i },
Function { i -> abs(i) - 128 }, Function { i -> (-i) },
).map { Function { i -> 32767 - abs(i) },
it.apply(nonZeroHash).toByte() Function { i -> abs(i) - 32768 },
}.firstOrNull { )
hashes[it] != true .map { it.apply(nonZeroHash).toShort() }
}?.let { .firstOrNull { !hashes.contains(it) }
hashes[it] = true }
it ?: (Short.MIN_VALUE..Short.MAX_VALUE).first {
} ?: (Byte.MIN_VALUE..Byte.MAX_VALUE).first { it != 0 && !hashes.contains(it.toShort())
it != 0 && hashes[it.toByte()] != true }.toShort()
}.toByte() }
}
return hash.also { hashes.add(it) }
}
} }
fun createUpstream( fun createUpstream(

View File

@@ -33,7 +33,7 @@ import java.util.concurrent.atomic.AtomicReference
abstract class DefaultUpstream( abstract class DefaultUpstream(
private val id: String, private val id: String,
private val hash: Byte, private val hash: Short,
defaultLag: Long?, defaultLag: Long?,
defaultAvail: UpstreamAvailability, defaultAvail: UpstreamAvailability,
private val options: ChainOptions.Options, private val options: ChainOptions.Options,
@@ -46,7 +46,7 @@ abstract class DefaultUpstream(
constructor( constructor(
id: String, id: String,
hash: Byte, hash: Short,
options: ChainOptions.Options, options: ChainOptions.Options,
role: UpstreamsConfig.UpstreamRole, role: UpstreamsConfig.UpstreamRole,
targets: CallMethods?, targets: CallMethods?,
@@ -153,7 +153,7 @@ abstract class DefaultUpstream(
sendUpstreamStateEvent(UpstreamChangeEvent.ChangeType.UPDATED) sendUpstreamStateEvent(UpstreamChangeEvent.ChangeType.UPDATED)
} }
override fun nodeId(): Byte = hash override fun nodeId(): Short = hash
open fun getQuorumByLabel(): QuorumForLabels { open fun getQuorumByLabel(): QuorumForLabels {
return node?.let { QuorumForLabels(it.copy(labels = fromMap(it.labels))) } return node?.let { QuorumForLabels(it.copy(labels = fromMap(it.labels))) }

View File

@@ -82,7 +82,7 @@ sealed class MatchesResponse {
) : MatchesResponse() ) : MatchesResponse()
data class SameNodeResponse( data class SameNodeResponse(
val upstreamHash: Byte, val upstreamHash: Short,
) : MatchesResponse() ) : MatchesResponse()
object AvailabilityResponse : MatchesResponse() object AvailabilityResponse : MatchesResponse()

View File

@@ -389,7 +389,7 @@ abstract class Multistream(
return false return false
} }
override fun nodeId(): Byte = 0 override fun nodeId(): Short = 0
override fun updateLowerBound(lowerBound: Long, type: LowerBoundType) { override fun updateLowerBound(lowerBound: Long, type: LowerBoundType) {
// NOOP // NOOP

View File

@@ -612,7 +612,7 @@ class Selector {
} }
} }
class SameNodeMatcher(private val upstreamHash: Byte) : Matcher() { class SameNodeMatcher(private val upstreamHash: Short) : Matcher() {
override fun matchesWithCause(up: Upstream): MatchesResponse = override fun matchesWithCause(up: Upstream): MatchesResponse =
if (up.nodeId() == upstreamHash) { if (up.nodeId() == upstreamHash) {

View File

@@ -60,10 +60,10 @@ interface Upstream : Lifecycle {
fun <T : Upstream> cast(selfType: Class<T>): T fun <T : Upstream> cast(selfType: Class<T>): T
fun nodeId(): Byte fun nodeId(): Short
data class UpstreamSettingsData( data class UpstreamSettingsData(
val nodeId: Byte, val nodeId: Short,
val id: String, val id: String,
val nodeVersion: String, val nodeVersion: String,
) { ) {

View File

@@ -33,7 +33,7 @@ abstract class BitcoinUpstream(
node: QuorumForLabels.QuorumItem, node: QuorumForLabels.QuorumItem,
val esploraClient: EsploraClient? = null, val esploraClient: EsploraClient? = null,
chainConfig: ChainsConfig.ChainConfig, chainConfig: ChainsConfig.ChainConfig,
) : DefaultUpstream(id, 0.toByte(), options, role, callMethods, node, chainConfig, chain) { ) : DefaultUpstream(id, 0.toShort(), options, role, callMethods, node, chainConfig, chain) {
constructor( constructor(
id: String, id: String,

View File

@@ -97,11 +97,11 @@ class EthereumCallSelector(
} }
val filterId = list[0].toString() val filterId = list[0].toString()
if (filterId.length < 4) { if (filterId.length < 4) {
return Mono.just(Selector.SameNodeMatcher(0.toByte())) return Mono.just(Selector.SameNodeMatcher(0.toShort()))
} }
val hashHex = filterId.substring(filterId.length - 2) val hashHex = filterId.substring(filterId.length - 4)
val nodeId = hashHex.toInt(16) val nodeId = hashHex.toInt(16)
return Mono.just(Selector.SameNodeMatcher(nodeId.toByte())) return Mono.just(Selector.SameNodeMatcher(nodeId.toShort()))
} }
private fun blockTagSelector(params: String, pos: Int, paramName: String?, head: Head): Mono<Selector.Matcher> { private fun blockTagSelector(params: String, pos: Int, paramName: String?, head: Head): Mono<Selector.Matcher> {

View File

@@ -41,7 +41,7 @@ import java.util.function.Supplier
open class GenericUpstream( open class GenericUpstream(
id: String, id: String,
chain: Chain, chain: Chain,
hash: Byte, hash: Short,
options: ChainOptions.Options, options: ChainOptions.Options,
role: UpstreamsConfig.UpstreamRole, role: UpstreamsConfig.UpstreamRole,
targets: CallMethods?, targets: CallMethods?,
@@ -58,7 +58,7 @@ open class GenericUpstream(
constructor( constructor(
config: UpstreamsConfig.Upstream<*>, config: UpstreamsConfig.Upstream<*>,
chain: Chain, chain: Chain,
hash: Byte, hash: Short,
options: ChainOptions.Options, options: ChainOptions.Options,
node: QuorumForLabels.QuorumItem?, node: QuorumForLabels.QuorumItem?,
chainConfig: ChainsConfig.ChainConfig, chainConfig: ChainsConfig.ChainConfig,

View File

@@ -56,7 +56,7 @@ import java.util.function.Function
open class GenericGrpcUpstream( open class GenericGrpcUpstream(
parentId: String, parentId: String,
hash: Byte, hash: Short,
role: UpstreamsConfig.UpstreamRole, role: UpstreamsConfig.UpstreamRole,
chain: Chain, chain: Chain,
private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub, private val remote: ReactorBlockchainGrpc.ReactorBlockchainStub,

View File

@@ -32,7 +32,7 @@ class GrpcUpstreamCreator(
@Value("\${spring.application.max-metadata-size}") @Value("\${spring.application.max-metadata-size}")
private var maxMetadataSize: Int = Defaults.maxMetadataSize private var maxMetadataSize: Int = Defaults.maxMetadataSize
private val hashes: MutableMap<Byte, Boolean> = HashMap() private val hashes = HashSet<Short>()
companion object { companion object {
val grpcUpstreamsScheduler: Scheduler = Schedulers.fromExecutorService( val grpcUpstreamsScheduler: Scheduler = Schedulers.fromExecutorService(

View File

@@ -69,7 +69,7 @@ import kotlin.concurrent.withLock
class GrpcUpstreams( class GrpcUpstreams(
private val id: String, private val id: String,
private val hash: Byte, private val hash: Short,
private val role: UpstreamsConfig.UpstreamRole, private val role: UpstreamsConfig.UpstreamRole,
private val host: String, private val host: String,
private val port: Int, private val port: Int,

View File

@@ -581,7 +581,7 @@ class NativeCallSpec extends Specification {
setup: setup:
def nativeCall = nativeCall() def nativeCall = nativeCall()
def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), new Selector.UpstreamFilter(Selector.empty), new AlwaysQuorum(), def ctx = new NativeCall.ValidCallContext(1, null, Stub(Multistream), new Selector.UpstreamFilter(Selector.empty), new AlwaysQuorum(),
new NativeCall.ParsedCallDetails("eth_getFilterUpdates", new ListParams("0xabcd")), new NativeCall.ParsedCallDetails("eth_getFilterUpdates", new ListParams("0xabcdcd")),
new NativeCall.WithFilterIdDecorator(), new NativeCall.NoneResultDecorator(), null, false, "reqId", 1) new NativeCall.WithFilterIdDecorator(), new NativeCall.NoneResultDecorator(), null, false, "reqId", 1)
when: when:
def act = nativeCall.parseParams(ctx) def act = nativeCall.parseParams(ctx)
@@ -609,7 +609,7 @@ class NativeCallSpec extends Specification {
def nativeCall = nativeCall(multistreamHolder) def nativeCall = nativeCall(multistreamHolder)
nativeCall.requestReaderFactory = Mock(RequestReaderFactory) { nativeCall.requestReaderFactory = Mock(RequestReaderFactory) {
1 * create(_) >> Mock(RequestReader) { 1 * create(_) >> Mock(RequestReader) {
1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, List.of(new Upstream.UpstreamSettingsData((byte) 255, "", "")), null)) 1 * read(_) >> Mono.just(new RequestReader.Result("\"0xab\"".bytes, null, 1, List.of(new Upstream.UpstreamSettingsData((short) 65535, "", "")), null))
} }
} }
def call = new NativeCall.ValidCallContext(1, 10, multistream, new Selector.UpstreamFilter(Selector.empty), quorum, def call = new NativeCall.ValidCallContext(1, 10, multistream, new Selector.UpstreamFilter(Selector.empty), quorum,
@@ -620,7 +620,7 @@ class NativeCallSpec extends Specification {
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1)) def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1))
def act = objectMapper.readValue(resp.result, Object) def act = objectMapper.readValue(resp.result, Object)
then: then:
act == "0xabff" act == "0xabffff"
resp.nonce == 10 resp.nonce == 10
} }
@@ -653,7 +653,7 @@ class NativeCallSpec extends Specification {
def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1)) def resp = nativeCall.executeOnRemote(call).block(Duration.ofSeconds(1))
def act = objectMapper.readValue(resp.result, Object) def act = objectMapper.readValue(resp.result, Object)
then: then:
act == "0xab01" act == "0xab0001"
resp.nonce == 10 resp.nonce == 10
} }

View File

@@ -194,12 +194,12 @@ class EthereumCallSelectorSpec extends Specification {
expect: expect:
callSelector.getMatcher("eth_getFilterChanges", param, head, true).block() callSelector.getMatcher("eth_getFilterChanges", param, head, true).block()
== new Selector.SameNodeMatcher((byte)hash) == new Selector.SameNodeMatcher((short)hash)
where: where:
param | hash param | hash
'["0xff09"]' | 9 '["0x0009"]' | 9
'["0xff"]' | 255 '["0x00ff"]' | 255
'[""]' | 0 '[""]' | 0
'["0x0"]' | 0 '["0x0"]' | 0
} }

View File

@@ -0,0 +1,42 @@
package io.emeraldpay.dshackle.startup.configure
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
class UpstreamCreatorTest {
@ParameterizedTest
@MethodSource("data")
fun `test getHash`(
inputHash: Int?,
obj: Any,
answer: Short,
) {
val hash = UpstreamCreator.getHash(inputHash, obj, hashes)
assertThat(hash).isEqualTo(answer)
println(hashes)
}
companion object {
private val hashes = HashSet<Short>()
@JvmStatic
fun data(): List<Arguments> =
listOf(
Arguments.of(49, 1, 49.toShort()),
Arguments.of(4000, 1, 4000.toShort()),
Arguments.of(24000, 1, 24000.toShort()),
Arguments.of(null, 49, (-49).toShort()),
Arguments.of(null, 49, (32718).toShort()),
Arguments.of(null, 49, (-32719).toShort()),
Arguments.of(null, 49, (-32768).toShort()),
Arguments.of(null, 49, (-32767).toShort()),
Arguments.of(null, 32718, (-32718).toShort()),
Arguments.of(null, 32718, (-50).toShort()),
)
}
}

View File

@@ -22,7 +22,7 @@ import reactor.core.scheduler.Schedulers
class GenericGrpcUpstreamTest { class GenericGrpcUpstreamTest {
private val parentId = "testParent" private val parentId = "testParent"
private val hash: Byte = 0x01 private val hash: Short = 0x01
private val role = UpstreamsConfig.UpstreamRole.PRIMARY private val role = UpstreamsConfig.UpstreamRole.PRIMARY
private val headSink = Sinks.many().multicast().directBestEffort<BlockchainOuterClass.ChainHead>() private val headSink = Sinks.many().multicast().directBestEffort<BlockchainOuterClass.ChainHead>()
private val remote = private val remote =

View File

@@ -26,7 +26,7 @@ upstreams:
ws: ws:
url: "ws://localhost:9546" url: "ws://localhost:9546"
- id: invalid_node_id - id: invalid_node_id
node-id: 256 node-id: 100000
chain: ethereum chain: ethereum
connection: connection:
grpc: grpc: