diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 420c268521..8d12567ede 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -259,9 +259,6 @@ jobs: - name: Report coverage run: sbt coverageReport - - name: Run static analysis (ScapeGoat) - run: sbt scapegoat - - name: Run Sonar scanner env: SONAR_HOST_URL: https://sonarcloud.io diff --git a/be2-scala/build.sbt b/be2-scala/build.sbt index b92bc017c3..2bf6144cb8 100644 --- a/be2-scala/build.sbt +++ b/be2-scala/build.sbt @@ -3,44 +3,21 @@ import sbtsonar.SonarPlugin.autoImport.sonarProperties name := "pop" -scalaVersion := "2.13.7" +scalaVersion := "3.2.0" // Recommended 2.13 Scala flags (https://nathankleyn.com/2019/05/13/recommended-scalac-flags-for-2-13) slightly adapted for PoP scalacOptions ++= Seq( "-deprecation", // Emit warning and location for usages of deprecated APIs. - "-explaintypes", // Explain type errors in more detail. + "-explain-types", // Explain type errors in more detail. "-feature", // Emit warning and location for usages of features that should be imported explicitly. "-language:existentials", // Existential types (besides wildcard types) can be written and inferred "-language:experimental.macros", // Allow macro definition (besides implementation and application) "-language:higherKinds", // Allow higher-kinded types "-language:implicitConversions", // Allow definition of implicit functions called views "-unchecked", // Enable additional warnings where generated code depends on assumptions. - "-Xcheckinit", // Wrap field accessors to throw an exception on uninitialized access. "-Xfatal-warnings", // Fail the compilation if there are any warnings. - "-Xlint:adapted-args", // Warn if an argument list is modified to match the receiver. - "-Xlint:constant", // Evaluation of a constant arithmetic expression results in an error. - "-Xlint:delayedinit-select", // Selecting member of DelayedInit. - "-Xlint:doc-detached", // A Scaladoc comment appears to be detached from its element. - "-Xlint:inaccessible", // Warn about inaccessible types in method signatures. - "-Xlint:infer-any", // Warn when a type argument is inferred to be `Any`. - "-Xlint:missing-interpolator", // A string literal appears to be missing an interpolator id. - "-Xlint:nullary-unit", // Warn when nullary methods return Unit. - "-Xlint:option-implicit", // Option.apply used implicit view. - "-Xlint:package-object-classes", // Class or object defined in package object. - "-Xlint:poly-implicit-overload", // Parameterized overloaded implicit methods are not visible as view bounds. - "-Xlint:private-shadow", // A private field (or class parameter) shadows a superclass field. - "-Xlint:stars-align", // Pattern sequence wildcard must align with sequence component. - "-Xlint:type-parameter-shadow", // A local type parameter shadows a type already in scope. - "-Ywarn-extra-implicit", // Warn when more than one implicit parameter section is defined. - "-Ywarn-numeric-widen", // Warn when numerics are widened. - "-Ycache-plugin-class-loader:last-modified", // Enables caching of classloaders for compiler plugins - "-Ycache-macro-class-loader:last-modified", // and macro definitions. This can lead to performance improvements. ) -Scapegoat/ scalacOptions -= "-Xfatal-warnings" -// Temporarily report scapegoat errors as warnings, to avoid broken builds -Scapegoat/ scalacOptions += "-P:scapegoat:overrideLevels:all=Warning" - // Reload changes automatically Global / onChangedBuildSource := ReloadOnSourceChanges Global / cancelable := true @@ -89,15 +66,9 @@ copyProtocolTask := { Compile/ run/ mainClass := Some("ch.epfl.pop.Server") Compile/ packageBin/ mainClass := Some("ch.epfl.pop.Server") -lazy val scoverageSettings = Seq( - Compile/ coverageEnabled := true, - Test/ coverageEnabled := true, - packageBin/ coverageEnabled := false, -) - -ThisBuild/ scapegoatVersion := "1.4.11" - -scapegoatReports := Seq("xml", "html") +// https://github.com/ckipp01/dotty/blob/2fc33a36ea19752920d17596b5a6194c815a1873/docs/_docs/usage/coverage.md +Compile/ compile/ scalacOptions += + s"-coverage-out:target/scala-${scalaVersion.value}/scoverage-data" // Configure Sonar sonarProperties := Map( @@ -108,10 +79,9 @@ sonarProperties := Map( "sonar.tests" -> "src/test/scala", "sonar.sourceEncoding" -> "UTF-8", - "sonar.scala.version" -> "2.13.7", + "sonar.scala.version" -> scalaVersion.value, // Paths to the test and coverage reports - "sonar.scala.coverage.reportPaths" -> "./target/scala-2.13/scoverage-report/scoverage.xml", - "sonar.scala.scapegoat.reportPaths" -> "./target/scala-2.13/scapegoat-report/scapegoat.xml" + "sonar.scala.coverage.reportPaths" -> "./target/scala-3/scoverage-report/scoverage.xml", ) assembly/ assemblyMergeStrategy := { @@ -131,7 +101,7 @@ val AkkaHttpVersion = "10.2.9" libraryDependencies ++= Seq( "com.typesafe.akka" %% "akka-stream-typed" % AkkaVersion, // Akka streams (Graph) - "com.typesafe.akka" %% "akka-http" % AkkaHttpVersion, // Akka http (WebSockets) + ("com.typesafe.akka" %% "akka-http" % AkkaHttpVersion).cross(CrossVersion.for3Use2_13), // Akka http (WebSockets) "com.typesafe.akka" %% "akka-cluster-tools" % AkkaVersion, // Akka distributed publish/subscribe cluster "ch.qos.logback" % "logback-classic" % "1.1.3" % Runtime, // Akka logging library diff --git a/be2-scala/project/plugins.sbt b/be2-scala/project/plugins.sbt index baa8a4d6f8..467444fb83 100644 --- a/be2-scala/project/plugins.sbt +++ b/be2-scala/project/plugins.sbt @@ -1,7 +1,5 @@ -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1") -addSbtPlugin("com.sksamuel.scapegoat" % "sbt-scapegoat" % "1.1.1") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0-M4") addSbtPlugin("com.sonar-scala" % "sbt-sonar" % "2.3.0") addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0") addSbtPlugin("com.rallyhealth.sbt" % "sbt-git-versioning" % "1.6.0") addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6") -addSbtPlugin("ch.epfl.scala" % "sbt-scala3-migrate" % "0.5.1") diff --git a/be2-scala/src/main/scala/ch/epfl/pop/Server.scala b/be2-scala/src/main/scala/ch/epfl/pop/Server.scala index bc64272387..bc3aadbb1d 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/Server.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/Server.scala @@ -55,7 +55,7 @@ object Server { val bindingFuture = Http().newServerAt(config.interface, config.port).bindFlow(publishSubscribeRoute) bindingFuture.onComplete { - case Success(_) => println(f"ch.epfl.pop.Server online at ws://${config.interface}:${config.port}/${config.path}") + case Success(_) => println(s"ch.epfl.pop.Server online at ws://${config.interface}:${config.port}/${config.path}") case Failure(_) => logger.error( "ch.epfl.pop.Server failed to start. Terminating actor system" diff --git a/be2-scala/src/main/scala/ch/epfl/pop/model/objects/ElectionChannel.scala b/be2-scala/src/main/scala/ch/epfl/pop/model/objects/ElectionChannel.scala index 2b90addd09..8457f6e68c 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/model/objects/ElectionChannel.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/model/objects/ElectionChannel.scala @@ -23,7 +23,7 @@ object ElectionChannel { */ def extractMessages[T: reflect.ClassTag](dbActor: AskableActorRef = DbActor.getInstance): Future[List[(Message, T)]] = { for { - DbActor.DbActorCatchupAck(messages) <- dbActor ? DbActor.Catchup(channel) + case DbActor.DbActorCatchupAck(messages) <- dbActor ? DbActor.Catchup(channel) result <- Future.traverse(messages.flatMap(message => message.decodedData match { case Some(t: T) => Some((message, t)) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Transaction.scala b/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Transaction.scala index cf6687a4d0..e92bf5f4ca 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Transaction.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Transaction.scala @@ -41,8 +41,8 @@ final case class Transaction( } private def signaturePayload = - Base64Data.encode(inputs.map { txin => f"${txin.txOutHash}${txin.txOutIndex}" }.reduce(_ + _) + - outputs.map { txout => f"${txout.value}${txout.script.`type`}${txout.script.pubkeyHash.base64Data}" }.reduce(_ + _)) + Base64Data.encode(inputs.map { txin => s"${txin.txOutHash}${txin.txOutIndex}" }.reduce(_ + _) + + outputs.map { txout => s"${txout.value}${txout.script.`type`}${txout.script.pubkeyHash.base64Data}" }.reduce(_ + _)) /** This ensures the validity of the signatures, not that the funds are unspent. */ diff --git a/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Uint53.scala b/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Uint53.scala index bbdf21d275..f0d89b5b6b 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Uint53.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/model/objects/Uint53.scala @@ -26,7 +26,9 @@ object Uint53 { */ def safeSum(seq: IterableOnce[Uint53]): Either[ArithmeticOverflowError, Uint53] = { var acc = 0L - for (v <- seq.iterator) { + val it = seq.iterator + while (it.hasNext) { + val v = it.next require(inRange(v), s"value $v out of range for uint53") acc += v if (acc > MaxValue) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ElectionHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ElectionHandler.scala index b6e89b4892..a9170b6b6b 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ElectionHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ElectionHandler.scala @@ -128,7 +128,7 @@ class ElectionHandler(dbRef: => AskableActorRef) extends MessageHandler { castsVotesElections <- electionChannel.getLastVotes(dbActor) setupMessage <- electionChannel.getSetupMessage(dbActor) questionToBallots = setupMessage.questions.map(question => question.id -> question.ballot_options).toMap - DbActorReadElectionDataAck(electionData) <- dbActor ? DbActor.ReadElectionData(setupMessage.id) + case DbActorReadElectionDataAck(electionData) <- dbActor ? DbActor.ReadElectionData(setupMessage.id) } yield { val resultsTable = mutable.HashMap.from(for { (question, ballots) <- questionToBallots diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/LaoHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/LaoHandler.scala index 3a9124d284..e08e4c5560 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/LaoHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/LaoHandler.scala @@ -30,7 +30,7 @@ case object LaoHandler extends MessageHandler { val reactionChannel: Channel = Channel(s"$laoChannel${Channel.REACTIONS_CHANNEL_PREFIX}") // we get access to the canonical address of the server val config = ServerConf(appConf) - val address: Option[String] = Some(f"ws://${config.interface}:${config.port}/${config.path}") + val address: Option[String] = Some(s"ws://${config.interface}:${config.port}/${config.path}") val combined = for { // check whether the lao already exists in db diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/MessageHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/MessageHandler.scala index 42722fdb67..46cfbac075 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/MessageHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/MessageHandler.scala @@ -27,16 +27,17 @@ trait MessageHandler extends AskPatternConstants { * the database answer wrapped in a [[scala.concurrent.Future]] */ def dbAskWrite(rpcRequest: JsonRpcRequest): Future[GraphMessage] = { - val m: Message = rpcRequest.getParamsMessage.getOrElse( - return Future { - Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWrite failed : retrieve empty rpcRequest message", rpcRequest.id)) - } - ) - - val askWrite = dbActor ? DbActor.Write(rpcRequest.getParamsChannel, m) - askWrite.transformWith { - case Success(_) => Future(Left(rpcRequest)) - case _ => Future(Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWrite failed : could not write message $m", rpcRequest.id))) + rpcRequest.getParamsMessage match { + case None => + Future { + Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWrite failed : retrieve empty rpcRequest message", rpcRequest.id)) + } + case Some(m) => + val askWrite = dbActor ? DbActor.Write(rpcRequest.getParamsChannel, m) + askWrite.transformWith { + case Success(_) => Future(Left(rpcRequest)) + case _ => Future(Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWrite failed : could not write message $m", rpcRequest.id))) + } } } @@ -48,16 +49,17 @@ trait MessageHandler extends AskPatternConstants { * the database answer wrapped in a [[scala.concurrent.Future]] */ def dbAskWritePropagate(rpcRequest: JsonRpcRequest): Future[GraphMessage] = { - val m: Message = rpcRequest.getParamsMessage.getOrElse( - return Future { - Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWritePropagate failed : retrieve empty rpcRequest message", rpcRequest.id)) - } - ) - - val askWritePropagate = dbActor ? DbActor.WriteAndPropagate(rpcRequest.getParamsChannel, m) - askWritePropagate.transformWith { - case Success(_) => Future(Left(rpcRequest)) - case _ => Future(Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWritePropagate failed : could not write & propagate message $m", rpcRequest.id))) + rpcRequest.getParamsMessage match { + case None => + Future { + Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWritePropagate failed : retrieve empty rpcRequest message", rpcRequest.id)) + } + case Some(m) => + val askWritePropagate = dbActor ? DbActor.WriteAndPropagate(rpcRequest.getParamsChannel, m) + askWritePropagate.transformWith { + case Success(_) => Future(Left(rpcRequest)) + case _ => Future(Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWritePropagate failed : could not write & propagate message $m", rpcRequest.id))) + } } } @@ -75,23 +77,24 @@ trait MessageHandler extends AskPatternConstants { * the database answer wrapped in a [[scala.concurrent.Future]] */ def dbBroadcast(rpcMessage: JsonRpcRequest, channel: Channel, broadcastData: Base64Data, broadcastChannel: Channel): Future[GraphMessage] = { - val m: Message = rpcMessage.getParamsMessage.getOrElse( - return Future { - Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWritePropagate failed : retrieve empty rpcRequest message", rpcMessage.id)) - } - ) - - val combined = for { - DbActorReadLaoDataAck(laoData) <- dbActor ? DbActor.ReadLaoData(channel) - broadcastSignature: Signature = laoData.privateKey.signData(broadcastData) - broadcastId: Hash = Hash.fromStrings(broadcastData.toString, broadcastSignature.toString) - broadcastMessage: Message = Message(broadcastData, laoData.publicKey, broadcastSignature, broadcastId, List.empty) - _ <- dbActor ? DbActor.WriteAndPropagate(broadcastChannel, broadcastMessage) - } yield () + rpcMessage.getParamsMessage match { + case None => + Future { + Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbAskWritePropagate failed : retrieve empty rpcRequest message", rpcMessage.id)) + } + case Some(m) => + val combined = for { + case DbActorReadLaoDataAck(laoData) <- dbActor ? DbActor.ReadLaoData(channel) + broadcastSignature: Signature = laoData.privateKey.signData(broadcastData) + broadcastId: Hash = Hash.fromStrings(broadcastData.toString, broadcastSignature.toString) + broadcastMessage: Message = Message(broadcastData, laoData.publicKey, broadcastSignature, broadcastId, List.empty) + _ <- dbActor ? DbActor.WriteAndPropagate(broadcastChannel, broadcastMessage) + } yield () - combined.transformWith { - case Success(_) => Future(Left(rpcMessage)) - case _ => Future(Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbBroadcast failed : could not read and broadcast message $m", rpcMessage.id))) + combined.transformWith { + case Success(_) => Future(Left(rpcMessage)) + case _ => Future(Right(PipelineError(ErrorCodes.SERVER_ERROR.id, s"dbBroadcast failed : could not read and broadcast message $m", rpcMessage.id))) + } } } } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/WitnessHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/WitnessHandler.scala index 2b3d69a82a..55c7f5d369 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/WitnessHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/WitnessHandler.scala @@ -35,7 +35,7 @@ class WitnessHandler(dbRef: => AskableActorRef) extends MessageHandler { case Some(_) => val combined = for { // add new witness signature to existing ones - DbActorAddWitnessSignatureAck(witnessMessage) <- dbActor ? DbActor.AddWitnessSignature(channel, messageId, signature) + case DbActorAddWitnessSignatureAck(witnessMessage) <- dbActor ? DbActor.AddWitnessSignature(channel, messageId, signature) // overwrites the message containing now the witness signature in the db _ <- dbActor ? DbActor.WriteAndPropagate(channel, witnessMessage) } yield () diff --git a/be2-scala/src/test/scala/ch/epfl/pop/model/objects/PublicKeySuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/model/objects/PublicKeySuite.scala index 3534e2e950..913f837ab1 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/model/objects/PublicKeySuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/model/objects/PublicKeySuite.scala @@ -17,7 +17,7 @@ class PublicKeySuite extends FunSuite with Matchers { test("pubkey hash matches example data") { for (suffix <- Seq("", "2")) { - val Seq(JsString(pubkeyData), JsString(pubkeyHash)) = data.asJsObject.getFields(s"publicKey$suffix", s"publicKeyHash$suffix") + val Seq(JsString(pubkeyData), JsString(pubkeyHash)) = data.asJsObject.getFields(s"publicKey$suffix", s"publicKeyHash$suffix"): @unchecked val pubkey = PublicKey(Base64Data(pubkeyData)) pubkey.hash.base64Data should equal(Base64Data(pubkeyHash)) } diff --git a/be2-scala/src/test/scala/util/examples/data/TestKeyPairs.scala b/be2-scala/src/test/scala/util/examples/data/TestKeyPairs.scala index 8da668cfba..6ae685ecad 100644 --- a/be2-scala/src/test/scala/util/examples/data/TestKeyPairs.scala +++ b/be2-scala/src/test/scala/util/examples/data/TestKeyPairs.scala @@ -18,7 +18,7 @@ object TestKeyPairs { val keypairs: Vector[KeyPairWithHash] = for (suffix <- Vector("", "2")) yield { val Seq(JsString(privkeyData), JsString(pubkeyData), JsString(pubkeyHash)) = - raw.getFields(s"privateKey$suffix", s"publicKey$suffix", s"publicKeyHash$suffix") + raw.getFields(s"privateKey$suffix", s"publicKey$suffix", s"publicKeyHash$suffix"): @unchecked val fixedPrivkeyData = Base64Data(privkeyData).decode().take(32) KeyPairWithHash(KeyPair(PrivateKey(Base64Data.encode(fixedPrivkeyData)), PublicKey(Base64Data(pubkeyData))), Base64Data(pubkeyHash)) }