Skip to content

Commit 10ec822

Browse files
committed
problem: upstream options are not always correctly merged when defined in few places
1 parent dec4280 commit 10ec822

File tree

14 files changed

+239
-76
lines changed

14 files changed

+239
-76
lines changed

src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfig.kt

Lines changed: 55 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package io.emeraldpay.dshackle.config
1818

1919
import io.emeraldpay.dshackle.Defaults
20+
import org.apache.commons.lang3.ObjectUtils
2021
import java.net.URI
22+
import java.time.Duration
2123
import java.util.Arrays
2224
import java.util.Locale
2325

@@ -29,73 +31,100 @@ open class UpstreamsConfig {
2931
private const val MIN_PRIORITY = 0
3032
private const val MAX_PRIORITY = 1_000_000
3133
private const val DEFAULT_PRIORITY = 10
34+
private const val DEFAULT_VALIDATION_INTERVAL = 30
3235
}
3336

34-
open class Options {
37+
data class Options(
38+
var disableValidation: Boolean,
39+
var validationInterval: Duration,
40+
var timeout: Duration,
41+
var providesBalance: Boolean,
42+
var priority: Int,
43+
var validatePeers: Boolean,
44+
var minPeers: Int,
45+
var validateSyncing: Boolean
46+
)
47+
48+
open class PartialOptions {
49+
3550
var disableValidation: Boolean? = null
36-
var validationInterval: Int = 30
51+
var validationInterval: Int? = null
3752
set(value) {
38-
require(value > 0) {
53+
require(value == null || value > 0) {
3954
"validation-interval must be a positive number: $value"
4055
}
4156
field = value
4257
}
43-
var timeout = Defaults.timeout
58+
59+
var timeout: Int? = null
4460
var providesBalance: Boolean? = null
45-
var priority: Int = DEFAULT_PRIORITY
61+
var priority: Int? = null
4662
set(value) {
47-
require(value in MIN_PRIORITY..MAX_PRIORITY) {
63+
require(value == null || value in MIN_PRIORITY..MAX_PRIORITY) {
4864
"Upstream priority must be in $MIN_PRIORITY..$MAX_PRIORITY. Configured: $value"
4965
}
5066
field = value
5167
}
52-
var validatePeers: Boolean = true
53-
var minPeers: Int? = 1
68+
var validatePeers: Boolean? = null
69+
var minPeers: Int? = null
5470
set(value) {
55-
require(value != null && value >= 0) {
71+
require(value == null || value >= 0) {
5672
"min-peers must be a positive number: $value"
5773
}
5874
field = value
5975
}
60-
var validateSyncing: Boolean = true
76+
var validateSyncing: Boolean? = null
6177

62-
fun merge(overwrites: Options?): Options {
78+
fun merge(overwrites: PartialOptions?): PartialOptions {
6379
if (overwrites == null) {
6480
return this
6581
}
66-
val copy = Options()
67-
copy.priority = this.priority.coerceAtLeast(overwrites.priority)
68-
copy.validatePeers = this.validatePeers && overwrites.validatePeers
69-
copy.minPeers = if (this.minPeers != null) this.minPeers else overwrites.minPeers
70-
copy.disableValidation =
71-
if (this.disableValidation != null) this.disableValidation else overwrites.disableValidation
72-
copy.validationInterval = overwrites.validationInterval
73-
copy.providesBalance =
74-
if (this.providesBalance != null) this.providesBalance else overwrites.providesBalance
75-
copy.validateSyncing = this.validateSyncing && overwrites.validateSyncing
82+
val copy = PartialOptions()
83+
copy.disableValidation = ObjectUtils.firstNonNull(overwrites.disableValidation, this.disableValidation)
84+
copy.validationInterval = ObjectUtils.firstNonNull(overwrites.validationInterval, this.validationInterval)
85+
copy.timeout = ObjectUtils.firstNonNull(overwrites.timeout, this.timeout)
86+
copy.providesBalance = ObjectUtils.firstNonNull(overwrites.providesBalance, this.providesBalance)
87+
copy.priority = ObjectUtils.firstNonNull(overwrites.priority, this.priority)
88+
copy.validatePeers = ObjectUtils.firstNonNull(overwrites.validatePeers, this.validatePeers)
89+
copy.minPeers = ObjectUtils.firstNonNull(overwrites.minPeers, this.minPeers)
90+
copy.validateSyncing = ObjectUtils.firstNonNull(overwrites.validateSyncing, this.validateSyncing)
7691
return copy
7792
}
7893

94+
fun build(): Options {
95+
return Options(
96+
disableValidation = ObjectUtils.firstNonNull(this.disableValidation, false)!!,
97+
validationInterval = ObjectUtils.firstNonNull(this.validationInterval, DEFAULT_VALIDATION_INTERVAL)!!
98+
.toLong().let(Duration::ofSeconds),
99+
timeout = ObjectUtils.firstNonNull(this.timeout?.toLong()?.let(Duration::ofSeconds), Defaults.timeout)!!,
100+
providesBalance = ObjectUtils.firstNonNull(this.providesBalance, false)!!,
101+
priority = ObjectUtils.firstNonNull(this.priority, DEFAULT_PRIORITY)!!,
102+
validatePeers = ObjectUtils.firstNonNull(this.validatePeers, true)!!,
103+
minPeers = ObjectUtils.firstNonNull(this.minPeers, 1)!!,
104+
validateSyncing = ObjectUtils.firstNonNull(this.validateSyncing, true)!!,
105+
)
106+
}
107+
79108
companion object {
80109
@JvmStatic
81-
fun getDefaults(): Options {
82-
val options = Options()
110+
fun getDefaults(): PartialOptions {
111+
val options = PartialOptions()
83112
options.minPeers = 1
84113
options.disableValidation = false
85114
return options
86115
}
87116
}
88117
}
89118

90-
class DefaultOptions : Options() {
119+
class DefaultOptions : PartialOptions() {
91120
var chains: List<String>? = null
92-
var options: Options? = null
121+
var options: PartialOptions? = null
93122
}
94123

95124
class Upstream<T : UpstreamConnection> {
96125
var id: String? = null
97126
var chain: String? = null
98-
var options: Options? = null
127+
var options: PartialOptions? = null
99128
var isEnabled = true
100129
var connection: T? = null
101130
val labels = Labels()

src/main/kotlin/io/emeraldpay/dshackle/config/UpstreamsConfigReader.kt

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import org.yaml.snakeyaml.nodes.ScalarNode
2424
import reactor.util.function.Tuples
2525
import java.io.InputStream
2626
import java.net.URI
27-
import java.time.Duration
2827
import java.util.Locale
2928

3029
class UpstreamsConfigReader(
@@ -217,7 +216,7 @@ class UpstreamsConfigReader(
217216
upstream.methods = tryReadMethods(upNode)
218217
getValueAsInt(upNode, "priority")?.let {
219218
if (upstream.options == null) {
220-
upstream.options = UpstreamsConfig.Options.getDefaults()
219+
upstream.options = UpstreamsConfig.PartialOptions.getDefaults()
221220
}
222221
upstream.options!!.priority = it
223222
}
@@ -270,7 +269,7 @@ class UpstreamsConfigReader(
270269
}
271270
}
272271

273-
internal fun tryReadOptions(upNode: MappingNode): UpstreamsConfig.Options? {
272+
internal fun tryReadOptions(upNode: MappingNode): UpstreamsConfig.PartialOptions? {
274273
return if (hasAny(upNode, "options")) {
275274
return getMapping(upNode, "options")?.let { values ->
276275
readOptions(values)
@@ -305,8 +304,8 @@ class UpstreamsConfigReader(
305304
}
306305
}
307306

308-
internal fun readOptions(values: MappingNode): UpstreamsConfig.Options {
309-
val options = UpstreamsConfig.Options()
307+
internal fun readOptions(values: MappingNode): UpstreamsConfig.PartialOptions {
308+
val options = UpstreamsConfig.PartialOptions()
310309
getValueAsBool(values, "validate-peers")?.let {
311310
options.validatePeers = it
312311
}
@@ -317,7 +316,7 @@ class UpstreamsConfigReader(
317316
options.minPeers = it
318317
}
319318
getValueAsInt(values, "timeout")?.let {
320-
options.timeout = Duration.ofSeconds(it.toLong())
319+
options.timeout = it
321320
}
322321
getValueAsBool(values, "disable-validation")?.let {
323322
options.disableValidation = it

src/main/kotlin/io/emeraldpay/dshackle/startup/ConfiguredUpstreams.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -76,22 +76,22 @@ open class ConfiguredUpstreams(
7676
}
7777
log.debug("Start upstream ${up.id}")
7878
if (up.connection is UpstreamsConfig.GrpcConnection) {
79-
val options = up.options ?: UpstreamsConfig.Options()
80-
buildGrpcUpstream(up.cast(UpstreamsConfig.GrpcConnection::class.java), options)
79+
val options = up.options ?: UpstreamsConfig.PartialOptions()
80+
buildGrpcUpstream(up.cast(UpstreamsConfig.GrpcConnection::class.java), options.build())
8181
} else {
8282
val chain = Global.chainById(up.chain)
8383
if (chain == Chain.UNSPECIFIED) {
8484
log.error("Chain is unknown: ${up.chain}")
8585
return@forEach
8686
}
87-
val options = (defaultOptions[chain] ?: UpstreamsConfig.Options.getDefaults())
88-
.merge(up.options ?: UpstreamsConfig.Options())
87+
val options = (defaultOptions[chain] ?: UpstreamsConfig.PartialOptions.getDefaults())
88+
.merge(up.options ?: UpstreamsConfig.PartialOptions())
8989
when (BlockchainType.from(chain)) {
9090
BlockchainType.ETHEREUM -> {
91-
buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options)
91+
buildEthereumUpstream(up.cast(UpstreamsConfig.EthereumConnection::class.java), chain, options.build())
9292
}
9393
BlockchainType.BITCOIN -> {
94-
buildBitcoinUpstream(up.cast(UpstreamsConfig.BitcoinConnection::class.java), chain, options)
94+
buildBitcoinUpstream(up.cast(UpstreamsConfig.BitcoinConnection::class.java), chain, options.build())
9595
}
9696
else -> {
9797
log.error("Chain is unsupported: ${up.chain}")
@@ -102,8 +102,8 @@ open class ConfiguredUpstreams(
102102
}
103103
}
104104

105-
private fun buildDefaultOptions(config: UpstreamsConfig): HashMap<Chain, UpstreamsConfig.Options> {
106-
val defaultOptions = HashMap<Chain, UpstreamsConfig.Options>()
105+
private fun buildDefaultOptions(config: UpstreamsConfig): HashMap<Chain, UpstreamsConfig.PartialOptions> {
106+
val defaultOptions = HashMap<Chain, UpstreamsConfig.PartialOptions>()
107107
config.defaultOptions.forEach { defaultsConfig ->
108108
defaultsConfig.chains?.forEach { chainName ->
109109
Global.chainById(chainName).let { chain ->

src/main/kotlin/io/emeraldpay/dshackle/upstream/Multistream.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ abstract class Multistream(
207207

208208
// TODO options for multistream are useless
209209
override fun getOptions(): UpstreamsConfig.Options {
210-
return UpstreamsConfig.Options()
210+
throw RuntimeException("No options for multistream")
211211
}
212212

213213
// TODO roles for multistream are useless

src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumRpcUpstream.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ open class EthereumRpcUpstream(
4040
constructor(id: String, chain: Chain, forkWatch: ForkWatch, api: Reader<JsonRpcRequest, JsonRpcResponse>) :
4141
this(
4242
id, chain, forkWatch, api, null,
43-
UpstreamsConfig.Options.getDefaults(), UpstreamsConfig.UpstreamRole.PRIMARY,
43+
UpstreamsConfig.PartialOptions.getDefaults().build(), UpstreamsConfig.UpstreamRole.PRIMARY,
4444
QuorumForLabels.QuorumItem(1, UpstreamsConfig.Labels()),
4545
DirectCallMethods()
4646
)
@@ -62,7 +62,7 @@ open class EthereumRpcUpstream(
6262
override fun start() {
6363
log.info("Configured for ${chain.chainName}")
6464
super.start()
65-
if (getOptions().disableValidation != null && getOptions().disableValidation!!) {
65+
if (getOptions().disableValidation) {
6666
log.warn("Disable validation for upstream ${this.getId()}")
6767
this.setLag(0)
6868
this.setStatus(UpstreamAvailability.OK)

src/main/kotlin/io/emeraldpay/dshackle/upstream/ethereum/EthereumUpstreamValidator.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import reactor.core.publisher.Flux
3030
import reactor.core.publisher.Mono
3131
import reactor.core.scheduler.Schedulers
3232
import reactor.util.function.Tuple2
33-
import java.time.Duration
3433
import java.util.concurrent.Executors
3534
import java.util.concurrent.TimeoutException
3635

@@ -110,7 +109,7 @@ open class EthereumUpstreamValidator(
110109
}
111110

112111
fun start(): Flux<UpstreamAvailability> {
113-
return Flux.interval(Duration.ofSeconds(options.validationInterval.toLong()))
112+
return Flux.interval(options.validationInterval)
114113
.subscribeOn(scheduler)
115114
.flatMap {
116115
validate()

src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/BitcoinGrpcUpstream.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ class BitcoinGrpcUpstream(
6868
}
6969

7070
constructor(parentId: String, role: UpstreamsConfig.UpstreamRole, chain: Chain, remote: ReactorBlockchainStub, client: JsonRpcGrpcClient) :
71-
this(parentId, ForkWatch.Never(), role, chain, UpstreamsConfig.Options.getDefaults(), remote, client)
71+
this(parentId, ForkWatch.Never(), role, chain, UpstreamsConfig.PartialOptions.getDefaults().build(), remote, client)
7272

7373
private val extractBlock = ExtractBlock()
7474
private val defaultReader: Reader<JsonRpcRequest, JsonRpcResponse> = client.forSelector(Selector.empty)

src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/EthereumGrpcUpstream.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ open class EthereumGrpcUpstream(
6868
Lifecycle {
6969

7070
constructor(parentId: String, role: UpstreamsConfig.UpstreamRole, chain: Chain, remote: ReactorBlockchainStub, client: JsonRpcGrpcClient) :
71-
this(parentId, ForkWatch.Never(), role, chain, UpstreamsConfig.Options.getDefaults(), remote, client)
71+
this(parentId, ForkWatch.Never(), role, chain, UpstreamsConfig.PartialOptions.getDefaults().build(), remote, client)
7272

7373
private val blockConverter: Function<BlockchainOuterClass.ChainHead, BlockContainer> = Function { value ->
7474
val block = BlockContainer(

src/main/kotlin/io/emeraldpay/dshackle/upstream/grpc/GrpcUpstreams.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class GrpcUpstreams(
6161
) {
6262
private val log = LoggerFactory.getLogger(GrpcUpstreams::class.java)
6363

64-
var options = UpstreamsConfig.Options.getDefaults()
64+
var options = UpstreamsConfig.PartialOptions.getDefaults().build()
6565

6666
private var client: ReactorBlockchainGrpc.ReactorBlockchainStub? = null
6767
private val known = HashMap<Chain, DefaultUpstream>()

0 commit comments

Comments
 (0)