ledger-configuration: Add tests for the Configuration decoding methods. [KVL-1002] (#10293)

* ledger-configuration: Add tests for Configuration.

CHANGELOG_BEGIN
CHANGELOG_END

* ledger-configuration: Reject a negative maximum deduplication time.

* ledger-configuration: Remove duplication in Configuration.

* ledger-configuration: Make some Configuration methods private.

Co-authored-by: Miklos <57664299+miklos-da@users.noreply.github.com>

* ledger-configuration: Remove some extra braces.

* ledger-configuration: Use tables to simplify rejection tests.

* ledger-configuration: Add a test for an unknown version.

Co-authored-by: Miklos <57664299+miklos-da@users.noreply.github.com>
This commit is contained in:
Samir Talwar 2021-07-15 18:15:08 +02:00 committed by GitHub
parent cb29f34d4b
commit 88886beb8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 294 additions and 73 deletions

View File

@ -42,11 +42,13 @@ da_scala_test_suite(
"@maven//:org_scalatest_scalatest",
"@maven//:org_scalaz_scalaz_core",
"@maven//:org_scala_lang_modules_scala_collection_compat",
"@maven//:org_scala_lang_modules_scala_java8_compat",
],
deps = [
":ledger-configuration",
"//daml-lf/data",
"//daml-lf/transaction",
"//ledger-api/grpc-definitions:ledger_api_proto_scala",
"//ledger/ledger-configuration/protobuf:ledger_configuration_proto_scala",
],
)

View File

@ -21,6 +21,7 @@ final case class Configuration(
)
object Configuration {
import com.daml.ledger.participant.state.protobuf
/** Version history:
@ -29,73 +30,6 @@ object Configuration {
*/
val protobufVersion: Long = 2L
def decode(bytes: Array[Byte]): Either[String, Configuration] =
Try(protobuf.LedgerConfiguration.parseFrom(bytes)).toEither.left
.map(_.getMessage)
.flatMap(decode)
def decode(config: protobuf.LedgerConfiguration): Either[String, Configuration] =
config.getVersion match {
case 1 => DecodeV1.decode(config)
case 2 => DecodeV2.decode(config)
case v => Left(s"Unknown version: $v")
}
private object DecodeV1 {
def decode(config: protobuf.LedgerConfiguration): Either[String, Configuration] =
for {
tm <-
if (config.hasTimeModel)
decodeTimeModel(config.getTimeModel)
else
Left("Missing time model")
} yield {
Configuration(
generation = config.getGeneration,
timeModel = tm,
maxDeduplicationTime = Duration.ofDays(1),
)
}
def decodeTimeModel(tm: protobuf.LedgerTimeModel): Either[String, LedgerTimeModel] =
LedgerTimeModel(
avgTransactionLatency = parseDuration(tm.getAvgTransactionLatency),
minSkew = parseDuration(tm.getMinSkew),
maxSkew = parseDuration(tm.getMaxSkew),
).toEither.left.map(e => s"decodeTimeModel: ${e.getMessage}")
}
private object DecodeV2 {
def decode(config: protobuf.LedgerConfiguration): Either[String, Configuration] =
for {
tm <-
if (config.hasTimeModel)
decodeTimeModel(config.getTimeModel)
else
Left("Missing time model")
maxDeduplicationTime <-
if (config.hasMaxDeduplicationTime)
Right(parseDuration(config.getMaxDeduplicationTime))
else
Left("Missing maximum command time to live")
} yield {
Configuration(
generation = config.getGeneration,
timeModel = tm,
maxDeduplicationTime = maxDeduplicationTime,
)
}
def decodeTimeModel(tm: protobuf.LedgerTimeModel): Either[String, LedgerTimeModel] =
LedgerTimeModel(
avgTransactionLatency = parseDuration(tm.getAvgTransactionLatency),
minSkew = parseDuration(tm.getMinSkew),
maxSkew = parseDuration(tm.getMaxSkew),
).toEither.left.map(e => s"decodeTimeModel: ${e.getMessage}")
}
def encode(config: Configuration): protobuf.LedgerConfiguration = {
val tm = config.timeModel
protobuf.LedgerConfiguration.newBuilder
@ -111,15 +45,74 @@ object Configuration {
.build
}
private def parseDuration(dur: com.google.protobuf.Duration): Duration = {
Duration.ofSeconds(dur.getSeconds, dur.getNanos.toLong)
}
def decode(bytes: Array[Byte]): Either[String, Configuration] =
Try(protobuf.LedgerConfiguration.parseFrom(bytes)).toEither.left
.map(_.getMessage)
.flatMap(decode)
private def buildDuration(dur: Duration): com.google.protobuf.Duration = {
def decode(config: protobuf.LedgerConfiguration): Either[String, Configuration] =
config.getVersion match {
case 1 => decodeV1(config)
case 2 => decodeV2(config)
case v => Left(s"Unknown version: $v")
}
private def decodeV1(config: protobuf.LedgerConfiguration): Either[String, Configuration] =
for {
tm <-
if (config.hasTimeModel) {
decodeTimeModel(config.getTimeModel)
} else {
Left("Missing time model")
}
} yield {
Configuration(
generation = config.getGeneration,
timeModel = tm,
maxDeduplicationTime = Duration.ofDays(1),
)
}
private def decodeV2(config: protobuf.LedgerConfiguration): Either[String, Configuration] =
for {
tm <-
if (config.hasTimeModel) {
decodeTimeModel(config.getTimeModel)
} else {
Left("Missing time model")
}
maxDeduplicationTime <-
if (config.hasMaxDeduplicationTime) {
val duration = parseDuration(config.getMaxDeduplicationTime)
if (duration.isNegative) {
Left("requirement failed: Negative maximum command time to live")
} else {
Right(duration)
}
} else {
Left("Missing maximum command time to live")
}
} yield {
Configuration(
generation = config.getGeneration,
timeModel = tm,
maxDeduplicationTime = maxDeduplicationTime,
)
}
private def decodeTimeModel(tm: protobuf.LedgerTimeModel): Either[String, LedgerTimeModel] =
LedgerTimeModel(
avgTransactionLatency = parseDuration(tm.getAvgTransactionLatency),
minSkew = parseDuration(tm.getMinSkew),
maxSkew = parseDuration(tm.getMaxSkew),
).toEither.left.map(e => s"decodeTimeModel: ${e.getMessage}")
private def parseDuration(dur: com.google.protobuf.Duration): Duration =
Duration.ofSeconds(dur.getSeconds, dur.getNanos.toLong)
private def buildDuration(dur: Duration): com.google.protobuf.Duration =
com.google.protobuf.Duration.newBuilder
.setSeconds(dur.getSeconds)
.setNanos(dur.getNano)
.build
}
}

View File

@ -0,0 +1,226 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.configuration
import com.daml.ledger.configuration.ConfigurationSpec._
import com.daml.ledger.participant.state.protobuf.{ledger_configuration => protobuf}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.compat.java8.DurationConverters._
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import org.scalatest.prop.TableDrivenPropertyChecks._
class ConfigurationSpec extends AnyWordSpec with Matchers {
"a ledger configuration" when {
"decoding a v1 protobuf" should {
"decode a valid protobuf" in {
val configurationBytes = protobuf.LedgerConfiguration
.of(
version = 1,
generation = 7,
timeModel = Some(
protobuf.LedgerTimeModel.of(
avgTransactionLatency = Some(1.minute.toProtobuf),
minSkew = Some(30.seconds.toProtobuf),
maxSkew = Some(2.minutes.toProtobuf),
)
),
maxDeduplicationTime = None,
)
.toByteArray
val configuration = Configuration.decode(configurationBytes)
configuration should be(
Right(
Configuration(
generation = 7,
timeModel = LedgerTimeModel(
avgTransactionLatency = 1.minute.toJava,
minSkew = 30.seconds.toJava,
maxSkew = 2.minutes.toJava,
).get,
maxDeduplicationTime = 1.day.toJava,
)
)
)
}
"reject a missing time model" in {
val configurationBytes = protobuf.LedgerConfiguration
.of(
version = 1,
generation = 2,
timeModel = None,
maxDeduplicationTime = None,
)
.toByteArray
val configuration = Configuration.decode(configurationBytes)
configuration should be(Left("Missing time model"))
}
}
"decoding a v2 protobuf" should {
"decode a valid protobuf" in {
val configurationBytes = protobuf.LedgerConfiguration
.of(
version = 2,
generation = 3,
timeModel = Some(
protobuf.LedgerTimeModel.of(
avgTransactionLatency = Some(30.seconds.toProtobuf),
minSkew = Some(20.seconds.toProtobuf),
maxSkew = Some(5.minutes.toProtobuf),
)
),
maxDeduplicationTime = Some(6.hours.toProtobuf),
)
.toByteArray
val configuration = Configuration.decode(configurationBytes)
configuration should be(
Right(
Configuration(
generation = 3,
timeModel = LedgerTimeModel(
avgTransactionLatency = 30.seconds.toJava,
minSkew = 20.seconds.toJava,
maxSkew = 5.minutes.toJava,
).get,
maxDeduplicationTime = 6.hours.toJava,
)
)
)
}
val rejections = Table(
("error message", "protobuf"),
(
"Missing time model",
protobuf.LedgerConfiguration.of(
version = 2,
generation = 4,
timeModel = None,
maxDeduplicationTime = Some(1.day.toProtobuf),
),
),
(
"Missing maximum command time to live",
protobuf.LedgerConfiguration.of(
version = 2,
generation = 1,
timeModel = Some(
protobuf.LedgerTimeModel.of(
avgTransactionLatency = Some(com.google.protobuf.duration.Duration.defaultInstance),
minSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
maxSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
)
),
maxDeduplicationTime = None,
),
),
(
"decodeTimeModel: requirement failed: Negative average transaction latency",
protobuf.LedgerConfiguration.of(
version = 2,
generation = 1,
timeModel = Some(
protobuf.LedgerTimeModel.of(
avgTransactionLatency = Some((-5).seconds.toProtobuf),
minSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
maxSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
)
),
maxDeduplicationTime = Some(com.google.protobuf.duration.Duration.defaultInstance),
),
),
(
"decodeTimeModel: requirement failed: Negative min skew",
protobuf.LedgerConfiguration.of(
version = 2,
generation = 1,
timeModel = Some(
protobuf.LedgerTimeModel.of(
avgTransactionLatency = Some(com.google.protobuf.duration.Duration.defaultInstance),
minSkew = Some((-30).seconds.toProtobuf),
maxSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
)
),
maxDeduplicationTime = Some(com.google.protobuf.duration.Duration.defaultInstance),
),
),
(
"decodeTimeModel: requirement failed: Negative max skew",
protobuf.LedgerConfiguration.of(
version = 2,
generation = 1,
timeModel = Some(
protobuf.LedgerTimeModel.of(
avgTransactionLatency = Some(com.google.protobuf.duration.Duration.defaultInstance),
minSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
maxSkew = Some((-10).seconds.toProtobuf),
)
),
maxDeduplicationTime = Some(com.google.protobuf.duration.Duration.defaultInstance),
),
),
(
"requirement failed: Negative maximum command time to live",
protobuf.LedgerConfiguration.of(
version = 2,
generation = 1,
timeModel = Some(
protobuf.LedgerTimeModel.of(
avgTransactionLatency = Some(com.google.protobuf.duration.Duration.defaultInstance),
minSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
maxSkew = Some(com.google.protobuf.duration.Duration.defaultInstance),
)
),
maxDeduplicationTime = Some((-1).day.toProtobuf),
),
),
)
"reject an invalid protobuf" in {
forAll(rejections) { (errorMessage, protobuf) =>
val configurationBytes = protobuf.toByteArray
val configuration = Configuration.decode(configurationBytes)
configuration should be(Left(errorMessage))
}
}
}
"decoding a protobuf with an invalid version" should {
"reject the protobuf" in {
val configurationBytes = protobuf.LedgerConfiguration
.of(
version = 3,
generation = 0,
timeModel = None,
maxDeduplicationTime = None,
)
.toByteArray
val configuration = Configuration.decode(configurationBytes)
configuration should be(Left("Unknown version: 3"))
}
}
}
}
object ConfigurationSpec {
implicit class Converter(duration: FiniteDuration) {
def toProtobuf: com.google.protobuf.duration.Duration = {
val javaDuration = duration.toJava
new com.google.protobuf.duration.Duration(javaDuration.getSeconds, javaDuration.getNano)
}
}
}