From f3b8d33e27613d4a1ad1bb1fd47be793ed85b481 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 23 Apr 2024 18:56:39 +0200 Subject: [PATCH 01/22] handler should write new rumors in db + tests --- .../pubsub/graph/handlers/ParamsHandler.scala | 11 ++++ .../graph/handlers/RumorHandlerSuite.scala | 51 +++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index f8a21eb119..45843dcc02 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -11,6 +11,7 @@ import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse} import ch.epfl.pop.model.objects.{Channel, PublicKey} import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} import ch.epfl.pop.pubsub.{AskPatternConstants, ClientActor, PubSubMediator} +import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors, WriteRumor} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{Await, Future} @@ -94,6 +95,16 @@ object ParamsHandler extends AskPatternConstants { val senderPk: PublicKey = rumor.senderPk val rumorId: Int = rumor.rumorId val messages: Map[Channel, List[Message]] = rumor.messages + + // check if rumor already received + val readRumorDb = dbActorRef ? ReadRumors(Map(senderPk.base64Data.data -> List(rumorId))) + Await.result(readRumorDb, duration) match { + // already present + case DbActorReadRumors(foundRumors) => // do nothing + // absent + case failure => + val writeRumor = dbActorRef ? WriteRumor(rumor) + } Right(jsonRpcMessage) case _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "RumorHandler received a non expected jsonRpcRequest", jsonRpcMessage.id)) } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala new file mode 100644 index 0000000000..03e0c672a9 --- /dev/null +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -0,0 +1,51 @@ +package ch.epfl.pop.pubsub.graph.handlers + +import akka.NotUsed +import akka.actor.Status.Failure +import akka.actor.{ActorRef, ActorSystem, Props} +import akka.pattern.AskableActorRef +import akka.testkit.TestKit +import ch.epfl.pop.pubsub.{AskPatternConstants, MessageRegistry, PubSubMediator} +import ch.epfl.pop.storage.{DbActor, InMemoryStorage} +import akka.pattern.ask +import akka.stream.scaladsl.{Flow, Sink, Source} +import ch.epfl.pop.IOHelper.readJsonFromPath +import ch.epfl.pop.model.network.JsonRpcRequest +import ch.epfl.pop.model.network.method.Rumor +import ch.epfl.pop.pubsub.graph.GraphMessage +import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors} +import org.scalatest.BeforeAndAfterAll +import org.scalatest.funsuite.AnyFunSuiteLike +import org.scalatest.matchers.should.Matchers.{a, shouldBe} + +import scala.concurrent.Await + +class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem")) with AnyFunSuiteLike with AskPatternConstants with BeforeAndAfterAll { + + val inMemoryStorage: InMemoryStorage = InMemoryStorage() + val messageRegistry: MessageRegistry = MessageRegistry() + val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props, "PubSubMediator") + val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage)), "DbActor") + + val rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = ParamsHandler.rumorHandler(dbActorRef) + + val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" + + val rumorRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath(pathCorrectRumor)) + val rumor: Rumor = rumorRequest.getParams.asInstanceOf[Rumor] + + override def afterAll(): Unit = { + // Stops the testKit + TestKit.shutdownActorSystem(system) + } + + test("rumor handler should write new rumors in memory") { + val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) + + Await.result(output, duration) + + val readRumor = dbActorRef ? ReadRumors(Map(rumor.senderPk.base64Data.data -> List(rumor.rumorId))) + Await.result(readRumor, duration) shouldBe a[DbActorReadRumors] + } + +} From c824212af942ed5aef75a2d02163e7cce75c6ec8 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Wed, 24 Apr 2024 10:16:25 +0200 Subject: [PATCH 02/22] add handling rumor tests --- .../graph/handlers/RumorHandlerSuite.scala | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 03e0c672a9..11c072b6b4 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -32,6 +32,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" val rumorRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath(pathCorrectRumor)) + val rumor: Rumor = rumorRequest.getParams.asInstanceOf[Rumor] override def afterAll(): Unit = { @@ -48,4 +49,20 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" Await.result(readRumor, duration) shouldBe a[DbActorReadRumors] } + test("rumor handler should handle correct rumors without error") { + val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) + + Await.result(output, duration) shouldBe a[Right[_, _]] + } + + test("rumor handler should fail on processing something else than a rumor") { + val publishRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath("src/test/scala/util/examples/json/election/open_election.json")) + + val output = Source.single(Right(publishRequest)).via(rumorHandler).runWith(Sink.head) + + Await.result(output, duration) shouldBe a[Left[_,_]] + + + } + } From 4b37adbdb4b2a790a00b80f2ddf390404698a8a7 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Thu, 25 Apr 2024 15:24:16 +0200 Subject: [PATCH 03/22] added forward of rumor to random peer and tests --- .../decentralized/ConnectionMediator.scala | 13 ++++++- .../ch/epfl/pop/pubsub/PublishSubscribe.scala | 2 +- .../pubsub/graph/handlers/ParamsHandler.scala | 16 ++++++++- .../graph/handlers/RumorHandlerSuite.scala | 36 ++++++++++++++----- 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala index fde1d05b24..231153360e 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala @@ -4,7 +4,7 @@ import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props} import akka.http.scaladsl.Http import akka.http.scaladsl.model.ws.WebSocketRequest import akka.pattern.AskableActorRef -import ch.epfl.pop.decentralized.ConnectionMediator.{NewServerConnected, ReadPeersClientAddress, ReadPeersClientAddressAck} +import ch.epfl.pop.decentralized.ConnectionMediator.{GetRandomPeerAck, NewServerConnected, ReadPeersClientAddress, ReadPeersClientAddressAck} import ch.epfl.pop.model.network.method.{GreetServer, Heartbeat, ParamsWithMap} import ch.epfl.pop.model.network.{JsonRpcRequest, MethodType} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer @@ -12,6 +12,7 @@ import ch.epfl.pop.pubsub.graph.validators.RpcValidator import ch.epfl.pop.pubsub.{AskPatternConstants, MessageRegistry, PublishSubscribe} import scala.collection.immutable.HashMap +import scala.util.Random final case class ConnectionMediator( monitorRef: ActorRef, mediatorRef: ActorRef, @@ -78,6 +79,13 @@ final case class ConnectionMediator( )) ) ) + + case ConnectionMediator.GetRandomPeer() => + if (serverMap.isEmpty) + sender() ! ConnectionMediator.NoPeer + else + val serverRefs = serverMap.keys.toList + sender() ! ConnectionMediator.GetRandomPeerAck(serverRefs(Random.nextInt(serverRefs.size))) } } @@ -92,7 +100,10 @@ object ConnectionMediator { final case class ServerLeft(serverRef: ActorRef) extends Event final case class Ping() extends Event final case class ReadPeersClientAddress() extends Event + final case class GetRandomPeer() extends Event sealed trait ConnectionMediatorMessage final case class ReadPeersClientAddressAck(list: List[String]) extends ConnectionMediatorMessage + final case class GetRandomPeerAck(serverRef: ActorRef) extends ConnectionMediatorMessage + final case class NoPeer() extends ConnectionMediatorMessage } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala index 82c0913645..791993a3c6 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala @@ -147,7 +147,7 @@ object PublishSubscribe { val heartbeatPartition = builder.add(ParamsWithMapHandler.heartbeatHandler(dbActorRef)) val getMessagesByIdPartition = builder.add(ParamsWithMapHandler.getMessagesByIdHandler(dbActorRef)) val greetServerPartition = builder.add(ParamsHandler.greetServerHandler(clientActorRef)) - val rumorPartition = builder.add(ParamsHandler.rumorHandler(dbActorRef)) + val rumorPartition = builder.add(ParamsHandler.rumorHandler(dbActorRef, connectionMediatorRef)) val merger = builder.add(Merge[GraphMessage](totalPorts)) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index 45843dcc02..3617479ad4 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -4,11 +4,13 @@ import akka.NotUsed import akka.actor.ActorRef import akka.pattern.AskableActorRef import akka.stream.scaladsl.Flow +import ch.epfl.pop.decentralized.ConnectionMediator import ch.epfl.pop.model.network.MethodType import ch.epfl.pop.model.network.method.message.Message import ch.epfl.pop.model.network.method.{GreetServer, Rumor} import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse} import ch.epfl.pop.model.objects.{Channel, PublicKey} +import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} import ch.epfl.pop.pubsub.{AskPatternConstants, ClientActor, PubSubMediator} import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors, WriteRumor} @@ -87,7 +89,7 @@ object ParamsHandler extends AskPatternConstants { case graphMessage @ _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "GreetServerHandler received an unexpected message:" + graphMessage, None)) }.filter(_ => false) - def rumorHandler(dbActorRef: AskableActorRef): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { + def rumorHandler(dbActorRef: AskableActorRef, connectionMediatorRef: AskableActorRef): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { case Right(jsonRpcMessage: JsonRpcRequest) => jsonRpcMessage.method match { case MethodType.rumor => @@ -105,6 +107,18 @@ object ParamsHandler extends AskPatternConstants { case failure => val writeRumor = dbActorRef ? WriteRumor(rumor) } + + // asks for a random server + val randomServerRef = connectionMediatorRef ? ConnectionMediator.GetRandomPeer() + Await.result(randomServerRef, duration) match { + case ConnectionMediator.GetRandomPeerAck(serverRef) => + serverRef ! ClientAnswer( + Right(jsonRpcMessage) + ) + + // do not send if there is nobody to receive + case ConnectionMediator.NoPeer => + } Right(jsonRpcMessage) case _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "RumorHandler received a non expected jsonRpcRequest", jsonRpcMessage.id)) } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 11c072b6b4..10919eb468 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -4,14 +4,18 @@ import akka.NotUsed import akka.actor.Status.Failure import akka.actor.{ActorRef, ActorSystem, Props} import akka.pattern.AskableActorRef -import akka.testkit.TestKit +import akka.testkit.{TestKit, TestProbe} import ch.epfl.pop.pubsub.{AskPatternConstants, MessageRegistry, PubSubMediator} -import ch.epfl.pop.storage.{DbActor, InMemoryStorage} +import ch.epfl.pop.storage.{DbActor, InMemoryStorage, SecurityModuleActor} import akka.pattern.ask import akka.stream.scaladsl.{Flow, Sink, Source} import ch.epfl.pop.IOHelper.readJsonFromPath +import ch.epfl.pop.config.RuntimeEnvironment +import ch.epfl.pop.decentralized.{ConnectionMediator, Monitor} import ch.epfl.pop.model.network.JsonRpcRequest -import ch.epfl.pop.model.network.method.Rumor +import ch.epfl.pop.model.network.method.{GreetServer, Rumor} +import ch.epfl.pop.model.objects.{Base64Data, PublicKey} +import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.GraphMessage import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors} import org.scalatest.BeforeAndAfterAll @@ -26,8 +30,11 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" val messageRegistry: MessageRegistry = MessageRegistry() val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props, "PubSubMediator") val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage)), "DbActor") + val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) + val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef)) + val connectionMediatorRef: AskableActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) - val rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = ParamsHandler.rumorHandler(dbActorRef) + val rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = ParamsHandler.rumorHandler(dbActorRef, connectionMediatorRef) val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" @@ -57,11 +64,24 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" test("rumor handler should fail on processing something else than a rumor") { val publishRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath("src/test/scala/util/examples/json/election/open_election.json")) - + val output = Source.single(Right(publishRequest)).via(rumorHandler).runWith(Sink.head) - - Await.result(output, duration) shouldBe a[Left[_,_]] - + + Await.result(output, duration) shouldBe a[Left[_, _]] + + } + + test("rumor handler should forward a rumor to a random server") { + val peerServer = TestProbe() + + // register server + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + + val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) + + Await.result(output, duration) + + peerServer.expectMsg(duration, ClientAnswer(Right(rumorRequest))) } From 6d20228e84333fd1cf713b80a75bc7304919b8a5 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Mon, 29 Apr 2024 15:56:34 +0200 Subject: [PATCH 04/22] add gossipManager class --- .../decentralized/ConnectionMediator.scala | 16 +++-- .../pop/decentralized/GossipManager.scala | 62 +++++++++++++++++++ .../ch/epfl/pop/decentralized/Monitor.scala | 14 ++++- .../pubsub/graph/handlers/ParamsHandler.scala | 3 +- .../decentralized/GossipManagerSuite.scala | 5 ++ .../graph/handlers/RumorHandlerSuite.scala | 2 +- 6 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala create mode 100644 be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala index 231153360e..3a1632db4b 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala @@ -23,6 +23,7 @@ final case class ConnectionMediator( implicit val system: ActorSystem = ActorSystem() private var serverMap: HashMap[ActorRef, GreetServer] = HashMap() + // Ping Monitor to inform it of our ActorRef monitorRef ! ConnectionMediator.Ping() @@ -80,12 +81,15 @@ final case class ConnectionMediator( ) ) - case ConnectionMediator.GetRandomPeer() => + case ConnectionMediator.GetRandomPeer(excludes) => if (serverMap.isEmpty) sender() ! ConnectionMediator.NoPeer else - val serverRefs = serverMap.keys.toList - sender() ! ConnectionMediator.GetRandomPeerAck(serverRefs(Random.nextInt(serverRefs.size))) + val serverRefs = serverMap.filter((k,_) => !excludes.contains(k)) + val randomKey = serverRefs.keys.toList(Random.nextInt(serverRefs.size)) + sender() ! ConnectionMediator.GetRandomPeerAck(randomKey, serverRefs(randomKey)) + + case GossipManager.Ping => } } @@ -100,10 +104,10 @@ object ConnectionMediator { final case class ServerLeft(serverRef: ActorRef) extends Event final case class Ping() extends Event final case class ReadPeersClientAddress() extends Event - final case class GetRandomPeer() extends Event - + final case class GetRandomPeer(excludes: List[ActorRef] = List.empty) extends Event + sealed trait ConnectionMediatorMessage final case class ReadPeersClientAddressAck(list: List[String]) extends ConnectionMediatorMessage - final case class GetRandomPeerAck(serverRef: ActorRef) extends ConnectionMediatorMessage + final case class GetRandomPeerAck(serverRef: ActorRef, greetServer: GreetServer) extends ConnectionMediatorMessage final case class NoPeer() extends ConnectionMediatorMessage } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala new file mode 100644 index 0000000000..d74ff56d9b --- /dev/null +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -0,0 +1,62 @@ +package ch.epfl.pop.decentralized + +import akka.actor.{Actor, ActorRef, Props} +import akka.pattern.AskableActorRef +import ch.epfl.pop.decentralized.GossipManager.MonitoredRumor +import ch.epfl.pop.model.network.JsonRpcRequest +import ch.epfl.pop.model.network.method.{GreetServer, Rumor} +import ch.epfl.pop.pubsub.AskPatternConstants +import ch.epfl.pop.pubsub.ClientActor.ClientAnswer + +import scala.:: +import scala.concurrent.Await + +final case class GossipManager( + dbActorRef: AskableActorRef, + monitorRef: ActorRef, + connectionMediator: AskableActorRef, + stopProbability: Double +) extends Actor with AskPatternConstants { + + private type ServerInfos = (ActorRef, GreetServer) + + monitorRef ! GossipManager.Ping() + + private var activeGossipProtocol: Map[Rumor, List[ServerInfos]] = Map.empty + + override def receive: Receive = { + case GossipManager.MonitoredRumor(jsonRpcRumor) => + val rumor = jsonRpcRumor.getParams.asInstanceOf[Rumor] + + val activeGossip = activeGossipProtocol.get(rumor) + val excludedPeers : List[ActorRef] = activeGossip match { + case Some(peersInfosList) => peersInfosList.map(_._1) + case None => List.empty + } + + val randomPeer = connectionMediator ? ConnectionMediator.GetRandomPeer(excludedPeers) + Await.result(randomPeer, duration) match { + case ConnectionMediator.GetRandomPeerAck(serverRef, greetServer) => + val alreadySent: List[ServerInfos] = activeGossip match + case Some(peers) => peers :+ (serverRef -> greetServer) + case None => List(serverRef -> greetServer) + activeGossipProtocol += (rumor -> alreadySent) + serverRef ! ClientAnswer( + Right(jsonRpcRumor) + ) + } + + } + +} + +object GossipManager { + def props(dbActorRef: AskableActorRef, monitorRef: ActorRef, connectionMediator: AskableActorRef ,stopProbability: Double = 0.5): Props = + Props(GossipManager(dbActorRef, monitorRef, connectionMediator, stopProbability)) + + sealed trait Event + final case class MonitoredRumor(jsonRpcRumor: JsonRpcRequest) + + sealed trait GossipManagerMessage + final case class Ping() extends GossipManagerMessage +} diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala index 70cf78a090..fc51ad33da 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala @@ -7,8 +7,8 @@ import akka.pattern.{AskableActorRef, ask} import akka.stream.scaladsl.Sink import ch.epfl.pop.config.RuntimeEnvironment.{readServerPeers, serverPeersListPath} import ch.epfl.pop.decentralized.Monitor.TriggerHeartbeat -import ch.epfl.pop.model.network.JsonRpcRequest -import ch.epfl.pop.model.network.method.{Heartbeat, ParamsWithMap} +import ch.epfl.pop.model.network.{JsonRpcRequest, MethodType} +import ch.epfl.pop.model.network.method.{Heartbeat, Params, ParamsWithMap} import ch.epfl.pop.model.objects.{Channel, Hash} import ch.epfl.pop.pubsub.AskPatternConstants import ch.epfl.pop.pubsub.graph.GraphMessage @@ -42,6 +42,7 @@ final case class Monitor( // Monitor is self-contained, // To that end it doesn't know the ref of the connectionMediator private var connectionMediatorRef = ActorRef.noSender + private var gossipManagerRef = ActorRef.noSender override def receive: Receive = LoggingReceive { @@ -72,7 +73,11 @@ final case class Monitor( jsonRpcMessage.getParams match { case _: ParamsWithMap => /* Actively ignoring this specific message */ // For any other message, we schedule a single heartbeat to reduce messages propagation delay + case _ => + if(jsonRpcMessage.method == MethodType.rumor){ + gossipManagerRef ! GossipManager.MonitoredRumor(jsonRpcMessage) + } if (someServerConnected && !timers.isTimerActive(singleHbKey)) { log.info(s"Scheduling single heartbeat") timers.startSingleTimer(singleHbKey, TriggerHeartbeat, messageDelay) @@ -84,6 +89,11 @@ final case class Monitor( connectionMediatorRef = sender() new Thread(new FileMonitor(connectionMediatorRef)).start() + case GossipManager.Ping() => + log.info("Received GossipManager Ping") + gossipManagerRef = sender() + + case _ => /* DO NOTHING */ } } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index 3617479ad4..1e57d1e7a4 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -99,13 +99,14 @@ object ParamsHandler extends AskPatternConstants { val messages: Map[Channel, List[Message]] = rumor.messages // check if rumor already received - val readRumorDb = dbActorRef ? ReadRumors(Map(senderPk.base64Data.data -> List(rumorId))) + val readRumorDb = dbActorRef ? ReadRumors(Map(senderPk -> List(rumorId))) Await.result(readRumorDb, duration) match { // already present case DbActorReadRumors(foundRumors) => // do nothing // absent case failure => val writeRumor = dbActorRef ? WriteRumor(rumor) + } // asks for a random server diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala new file mode 100644 index 0000000000..1447aefdcb --- /dev/null +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -0,0 +1,5 @@ +package ch.epfl.pop.decentralized + +class GossipManagerSuite { + +} diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 10919eb468..5daf4387c0 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -52,7 +52,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" Await.result(output, duration) - val readRumor = dbActorRef ? ReadRumors(Map(rumor.senderPk.base64Data.data -> List(rumor.rumorId))) + val readRumor = dbActorRef ? ReadRumors(Map(rumor.senderPk -> List(rumor.rumorId))) Await.result(readRumor, duration) shouldBe a[DbActorReadRumors] } From eb3114f8fe55139dcbce3cf7cf266d1604b6af45 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 30 Apr 2024 18:24:19 +0200 Subject: [PATCH 05/22] added gossipManager and adapted necessary classes --- .../src/main/scala/ch/epfl/pop/Server.scala | 11 ++- .../decentralized/ConnectionMediator.scala | 13 ++- .../pop/decentralized/GossipManager.scala | 88 +++++++++++++------ .../ch/epfl/pop/decentralized/Monitor.scala | 5 +- .../ch/epfl/pop/pubsub/PublishSubscribe.scala | 8 +- .../pubsub/graph/handlers/ParamsHandler.scala | 12 --- .../decentralized/GossipManagerSuite.scala | 52 ++++++++++- .../GetMessagesByIdResponseHandlerSuite.scala | 2 +- .../graph/handlers/RumorHandlerSuite.scala | 12 --- 9 files changed, 137 insertions(+), 66 deletions(-) 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 0726cc941b..9a89120178 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/Server.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/Server.scala @@ -2,17 +2,17 @@ package ch.epfl.pop import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.Behaviors -import akka.actor.typed.scaladsl.adapter._ +import akka.actor.typed.scaladsl.adapter.* import akka.actor.{ActorRef, Props} import akka.http.scaladsl.Http -import akka.http.scaladsl.server.Directives._ +import akka.http.scaladsl.server.Directives.* import akka.http.scaladsl.server.{RequestContext, RouteResult} import akka.pattern.{AskableActorRef, ask} import akka.util.Timeout import ch.epfl.pop.authentication.{GetRequestHandler, PopchaWebSocketResponseHandler} import ch.epfl.pop.config.RuntimeEnvironment -import ch.epfl.pop.config.RuntimeEnvironment._ -import ch.epfl.pop.decentralized.{ConnectionMediator, Monitor} +import ch.epfl.pop.config.RuntimeEnvironment.* +import ch.epfl.pop.decentralized.{ConnectionMediator, GossipManager, Monitor} import ch.epfl.pop.pubsub.{MessageRegistry, PubSubMediator, PublishSubscribe} import ch.epfl.pop.storage.{DbActor, SecurityModuleActor} import org.iq80.leveldb.Options @@ -51,6 +51,7 @@ object Server { // Create necessary actors for server-server communications val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef)) val connectionMediatorRef: ActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + val gossipManagerRef: ActorRef = system.actorOf(GossipManager.props(dbActorRef, monitorRef, connectionMediatorRef)) // Setup routes def publishSubscribeRoute: RequestContext => Future[RouteResult] = { @@ -63,6 +64,7 @@ object Server { messageRegistry, monitorRef, connectionMediatorRef, + gossipManagerRef, isServer = false )(system) ) @@ -75,6 +77,7 @@ object Server { messageRegistry, monitorRef, connectionMediatorRef, + gossipManagerRef, isServer = true )(system) ) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala index 3a1632db4b..2f3b78d2ca 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala @@ -10,6 +10,7 @@ import ch.epfl.pop.model.network.{JsonRpcRequest, MethodType} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.validators.RpcValidator import ch.epfl.pop.pubsub.{AskPatternConstants, MessageRegistry, PublishSubscribe} +import akka.pattern.ask import scala.collection.immutable.HashMap import scala.util.Random @@ -23,6 +24,7 @@ final case class ConnectionMediator( implicit val system: ActorSystem = ActorSystem() private var serverMap: HashMap[ActorRef, GreetServer] = HashMap() + private var gossipManagerRef: AskableActorRef = _ // Ping Monitor to inform it of our ActorRef @@ -43,6 +45,7 @@ final case class ConnectionMediator( messageRegistry, monitorRef, self, + gossipManagerRef, isServer = true, initGreetServer = true ) @@ -85,11 +88,13 @@ final case class ConnectionMediator( if (serverMap.isEmpty) sender() ! ConnectionMediator.NoPeer else - val serverRefs = serverMap.filter((k,_) => !excludes.contains(k)) + val serverRefs = serverMap.filter((k, _) => !excludes.contains(k)) val randomKey = serverRefs.keys.toList(Random.nextInt(serverRefs.size)) sender() ! ConnectionMediator.GetRandomPeerAck(randomKey, serverRefs(randomKey)) - - case GossipManager.Ping => + + case GossipManager.Ping() => + gossipManagerRef = sender() + } } @@ -105,7 +110,7 @@ object ConnectionMediator { final case class Ping() extends Event final case class ReadPeersClientAddress() extends Event final case class GetRandomPeer(excludes: List[ActorRef] = List.empty) extends Event - + sealed trait ConnectionMediatorMessage final case class ReadPeersClientAddressAck(list: List[String]) extends ConnectionMediatorMessage final case class GetRandomPeerAck(serverRef: ActorRef, greetServer: GreetServer) extends ConnectionMediatorMessage diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index d74ff56d9b..96671fbc36 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -1,62 +1,96 @@ package ch.epfl.pop.decentralized +import akka.NotUsed import akka.actor.{Actor, ActorRef, Props} import akka.pattern.AskableActorRef +import akka.stream.scaladsl.Flow import ch.epfl.pop.decentralized.GossipManager.MonitoredRumor -import ch.epfl.pop.model.network.JsonRpcRequest +import ch.epfl.pop.decentralized.{ConnectionMediator, GossipManager} import ch.epfl.pop.model.network.method.{GreetServer, Rumor} +import ch.epfl.pop.model.network.{JsonRpcRequest, MethodType} import ch.epfl.pop.pubsub.AskPatternConstants import ch.epfl.pop.pubsub.ClientActor.ClientAnswer +import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} +import ch.epfl.pop.storage.DbActor +import ch.epfl.pop.storage.DbActor.DbActorReadRumors -import scala.:: import scala.concurrent.Await final case class GossipManager( dbActorRef: AskableActorRef, monitorRef: ActorRef, connectionMediator: AskableActorRef, - stopProbability: Double + stopProbability: Double = 0.5 ) extends Actor with AskPatternConstants { private type ServerInfos = (ActorRef, GreetServer) monitorRef ! GossipManager.Ping() + connectionMediator ? ConnectionMediator.Ping() private var activeGossipProtocol: Map[Rumor, List[ServerInfos]] = Map.empty - override def receive: Receive = { - case GossipManager.MonitoredRumor(jsonRpcRumor) => - val rumor = jsonRpcRumor.getParams.asInstanceOf[Rumor] - - val activeGossip = activeGossipProtocol.get(rumor) - val excludedPeers : List[ActorRef] = activeGossip match { - case Some(peersInfosList) => peersInfosList.map(_._1) - case None => List.empty - } - - val randomPeer = connectionMediator ? ConnectionMediator.GetRandomPeer(excludedPeers) - Await.result(randomPeer, duration) match { - case ConnectionMediator.GetRandomPeerAck(serverRef, greetServer) => - val alreadySent: List[ServerInfos] = activeGossip match - case Some(peers) => peers :+ (serverRef -> greetServer) - case None => List(serverRef -> greetServer) - activeGossipProtocol += (rumor -> alreadySent) - serverRef ! ClientAnswer( - Right(jsonRpcRumor) - ) - } + private def isRumorNew(rumor: Rumor): Boolean = { + val readRumorDb = dbActorRef ? DbActor.ReadRumors(Map(rumor.senderPk -> List(rumor.rumorId))) + Await.result(readRumorDb, duration) match + case DbActorReadRumors(foundRumors) => false + case failure => true + } + + private def getPeersForRumor(rumor: Rumor): List[ServerInfos] = { + val activeGossip = activeGossipProtocol.get(rumor) + activeGossip match + case Some(peersInfosList) => peersInfosList + case None => List.empty + } + + private def sendRumorToRandomPeer(jsonRpcRequest: JsonRpcRequest, rumor: Rumor): Unit = { + // checks the peers to which we already forwarded the message + val activeGossip: List[ServerInfos] = getPeersForRumor(rumor) + // selects a random peer from remaining peers + val randomPeer = connectionMediator ? ConnectionMediator.GetRandomPeer(activeGossip.map(_._1)) + Await.result(randomPeer, duration) match { + // updates the list based on response + // if some peers are available we send + case ConnectionMediator.GetRandomPeerAck(serverRef, greetServer) => + val alreadySent: List[ServerInfos] = activeGossip :+ (serverRef -> greetServer) + activeGossipProtocol += (rumor -> alreadySent) + serverRef ! ClientAnswer( + Right(jsonRpcRequest) + ) + // else remove entry + case ConnectionMediator.NoPeer => + activeGossipProtocol = activeGossipProtocol.removed(rumor) + } + } + override def receive: Receive = { + case GossipManager.SendRumorToRandomPeer(jsonRpcRequest, rumor) => + sendRumorToRandomPeer(jsonRpcRequest, rumor) } } -object GossipManager { - def props(dbActorRef: AskableActorRef, monitorRef: ActorRef, connectionMediator: AskableActorRef ,stopProbability: Double = 0.5): Props = - Props(GossipManager(dbActorRef, monitorRef, connectionMediator, stopProbability)) +object GossipManager extends AskPatternConstants { + def props(dbActorRef: AskableActorRef, monitorRef: ActorRef, connectionMediatorRef: AskableActorRef): Props = + Props(new GossipManager(dbActorRef, monitorRef, connectionMediatorRef)) + + def gossipHandler(gossipManager: AskableActorRef): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { + case Right(jsonRpcRequest: JsonRpcRequest) => + jsonRpcRequest.method match + case MethodType.rumor => + val rumor = jsonRpcRequest.getParams.asInstanceOf[Rumor] + gossipManager ? SendRumorToRandomPeer(jsonRpcRequest, rumor) + Right(jsonRpcRequest) + case _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "GossipManager received a non expected jsonRpcRequest", jsonRpcRequest.id)) + case graphMessage @ _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "GossipManager received an unexpected message:" + graphMessage, None)) + } sealed trait Event final case class MonitoredRumor(jsonRpcRumor: JsonRpcRequest) + final case class SendRumorToRandomPeer(jsonRpcRequest: JsonRpcRequest, rumor: Rumor) sealed trait GossipManagerMessage final case class Ping() extends GossipManagerMessage + } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala index fc51ad33da..2303cbcf3b 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/Monitor.scala @@ -73,9 +73,9 @@ final case class Monitor( jsonRpcMessage.getParams match { case _: ParamsWithMap => /* Actively ignoring this specific message */ // For any other message, we schedule a single heartbeat to reduce messages propagation delay - + case _ => - if(jsonRpcMessage.method == MethodType.rumor){ + if (jsonRpcMessage.method == MethodType.rumor) { gossipManagerRef ! GossipManager.MonitoredRumor(jsonRpcMessage) } if (someServerConnected && !timers.isTimerActive(singleHbKey)) { @@ -92,7 +92,6 @@ final case class Monitor( case GossipManager.Ping() => log.info("Received GossipManager Ping") gossipManagerRef = sender() - case _ => /* DO NOTHING */ } diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala index 791993a3c6..d249ac5725 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala @@ -6,7 +6,7 @@ import akka.http.scaladsl.model.ws.{Message, TextMessage} import akka.pattern.{AskableActorRef, ask} import akka.stream.FlowShape import akka.stream.scaladsl.{Broadcast, Flow, GraphDSL, Merge, Partition, Sink} -import ch.epfl.pop.decentralized.Monitor +import ch.epfl.pop.decentralized.{GossipManager, Monitor} import ch.epfl.pop.model.network.MethodType.* import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType} import ch.epfl.pop.pubsub.graph.* @@ -18,11 +18,13 @@ object PublishSubscribe { private var securityModuleActorRef: AskableActorRef = _ private var connectionMediatorRef: AskableActorRef = _ private var mediatorActorRef: ActorRef = _ + private var gossipManager: AskableActorRef = _ def getDbActorRef: AskableActorRef = dbActorRef def getSecurityModuleActorRef: AskableActorRef = securityModuleActorRef def getConnectionMediatorRef: AskableActorRef = connectionMediatorRef def getMediatorActorRef: ActorRef = mediatorActorRef + def getGossipManager: AskableActorRef = gossipManager def buildGraph( mediatorActorRefT: ActorRef, @@ -31,6 +33,7 @@ object PublishSubscribe { messageRegistry: MessageRegistry, monitorRef: ActorRef, connectionMediatorRefT: ActorRef, + gossipManager: AskableActorRef, isServer: Boolean, initGreetServer: Boolean = false )(implicit system: ActorSystem): Flow[Message, Message, NotUsed] = Flow.fromGraph(GraphDSL.create() { @@ -148,6 +151,7 @@ object PublishSubscribe { val getMessagesByIdPartition = builder.add(ParamsWithMapHandler.getMessagesByIdHandler(dbActorRef)) val greetServerPartition = builder.add(ParamsHandler.greetServerHandler(clientActorRef)) val rumorPartition = builder.add(ParamsHandler.rumorHandler(dbActorRef, connectionMediatorRef)) + val gossipManagerPartition = builder.add(GossipManager.gossipHandler(gossipManager)) val merger = builder.add(Merge[GraphMessage](totalPorts)) @@ -162,7 +166,7 @@ object PublishSubscribe { methodPartitioner.out(portHeartbeat) ~> heartbeatPartition ~> merger methodPartitioner.out(portGetMessagesById) ~> getMessagesByIdPartition ~> merger methodPartitioner.out(portGreetServer) ~> greetServerPartition ~> merger - methodPartitioner.out(portRumor) ~> rumorPartition ~> merger + methodPartitioner.out(portRumor) ~> gossipManagerPartition ~> rumorPartition ~> merger /* close the shape */ FlowShape(input.in, merger.out) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index 1e57d1e7a4..2a06fb5ad7 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -108,18 +108,6 @@ object ParamsHandler extends AskPatternConstants { val writeRumor = dbActorRef ? WriteRumor(rumor) } - - // asks for a random server - val randomServerRef = connectionMediatorRef ? ConnectionMediator.GetRandomPeer() - Await.result(randomServerRef, duration) match { - case ConnectionMediator.GetRandomPeerAck(serverRef) => - serverRef ! ClientAnswer( - Right(jsonRpcMessage) - ) - - // do not send if there is nobody to receive - case ConnectionMediator.NoPeer => - } Right(jsonRpcMessage) case _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "RumorHandler received a non expected jsonRpcRequest", jsonRpcMessage.id)) } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index 1447aefdcb..bce9283939 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -1,5 +1,55 @@ package ch.epfl.pop.decentralized -class GossipManagerSuite { +import akka.NotUsed +import akka.actor.{ActorRef, ActorSystem, Props} +import akka.pattern.AskableActorRef +import akka.testkit.{TestKit, TestProbe} +import ch.epfl.pop.config.RuntimeEnvironment +import ch.epfl.pop.pubsub.{AskPatternConstants, MessageRegistry, PubSubMediator} +import ch.epfl.pop.storage.{DbActor, InMemoryStorage, SecurityModuleActor} +import org.scalatest.funsuite.{AnyFunSuite, AnyFunSuiteLike} +import org.scalatest.matchers.should.Matchers +import akka.pattern.ask +import akka.stream.scaladsl.{Flow, Sink, Source} +import ch.epfl.pop.IOHelper.readJsonFromPath +import ch.epfl.pop.model.network.JsonRpcRequest +import ch.epfl.pop.model.network.method.{GreetServer, Rumor} +import ch.epfl.pop.model.objects.{Base64Data, PublicKey} +import ch.epfl.pop.pubsub.ClientActor.ClientAnswer +import ch.epfl.pop.pubsub.graph.GraphMessage +import scala.concurrent.Await + +class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSystem")) with AnyFunSuiteLike with AskPatternConstants with Matchers { + + val inMemoryStorage: InMemoryStorage = InMemoryStorage() + val messageRegistry: MessageRegistry = MessageRegistry() + val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props, "PubSubMediator") + val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage)), "DbActor") + val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) + val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef)) + val connectionMediatorRef: AskableActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + + val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" + + val rumorRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath(pathCorrectRumor)) + + val rumor: Rumor = rumorRequest.getParams.asInstanceOf[Rumor] + + test("gossip handler should forward a rumor to a random server") { + + val gossipManager: ActorRef = system.actorOf(GossipManager.props(dbActorRef, monitorRef, connectionMediatorRef)) + val gossipHandler = GossipManager.gossipHandler(gossipManager) + + val peerServer = TestProbe() + + // register server + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + + val output = Source.single(Right(rumorRequest)).via(gossipHandler).runWith(Sink.head) + + Await.result(output, duration) + + peerServer.expectMsg(duration, ClientAnswer(Right(rumorRequest))) + } } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala index cc901d6e46..8e6b2b711d 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala @@ -36,7 +36,7 @@ class GetMessagesByIdResponseHandlerSuite extends TestKit(ActorSystem("GetMessag val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(testSecurityDirectory))) // Inject dbActor above - PublishSubscribe.buildGraph(pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry, ActorRef.noSender, ActorRef.noSender, isServer = false) + PublishSubscribe.buildGraph(pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry, ActorRef.noSender, ActorRef.noSender, ActorRef.noSender, isServer = false) // handler we want to test val responseHandler: Flow[GraphMessage, GraphMessage, NotUsed] = GetMessagesByIdResponseHandler.responseHandler(MessageRegistry()) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 5daf4387c0..33471736e4 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -71,18 +71,6 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" } - test("rumor handler should forward a rumor to a random server") { - val peerServer = TestProbe() - // register server - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer.ref, GreetServer(PublicKey(Base64Data("")), "", "")) - - val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) - - Await.result(output, duration) - - peerServer.expectMsg(duration, ClientAnswer(Right(rumorRequest))) - - } } From 01dc4687dfa456a707379ccfe2f64a356667e905 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 30 Apr 2024 18:25:10 +0200 Subject: [PATCH 06/22] scalaFmt --- .../scala/ch/epfl/pop/decentralized/ConnectionMediator.scala | 5 ++--- .../scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala | 2 +- .../epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala index 2f3b78d2ca..a052222952 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala @@ -25,7 +25,6 @@ final case class ConnectionMediator( private var serverMap: HashMap[ActorRef, GreetServer] = HashMap() private var gossipManagerRef: AskableActorRef = _ - // Ping Monitor to inform it of our ActorRef monitorRef ! ConnectionMediator.Ping() @@ -91,10 +90,10 @@ final case class ConnectionMediator( val serverRefs = serverMap.filter((k, _) => !excludes.contains(k)) val randomKey = serverRefs.keys.toList(Random.nextInt(serverRefs.size)) sender() ! ConnectionMediator.GetRandomPeerAck(randomKey, serverRefs(randomKey)) - + case GossipManager.Ping() => gossipManagerRef = sender() - + } } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index bce9283939..efb60a3e0f 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -40,7 +40,7 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys val gossipManager: ActorRef = system.actorOf(GossipManager.props(dbActorRef, monitorRef, connectionMediatorRef)) val gossipHandler = GossipManager.gossipHandler(gossipManager) - + val peerServer = TestProbe() // register server diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 33471736e4..a6b8992aca 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -71,6 +71,4 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" } - - } From 9bb6f556fe5897664c9f2feb661ab73498cdec4f Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Wed, 1 May 2024 10:51:30 +0200 Subject: [PATCH 07/22] tested gossip manager --- .../decentralized/GossipManagerSuite.scala | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index efb60a3e0f..5e8dd95127 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -52,4 +52,28 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys peerServer.expectMsg(duration, ClientAnswer(Right(rumorRequest))) } + + test("gossip handler should send to only one server if multiples are present") { + val gossipManager: ActorRef = system.actorOf(GossipManager.props(dbActorRef, monitorRef, connectionMediatorRef)) + val gossipHandler = GossipManager.gossipHandler(gossipManager) + + val peerServer1 = TestProbe() + val peerServer2 = TestProbe() + val peerServer3 = TestProbe() + val peerServer4 = TestProbe() + + val peers = List(peerServer1, peerServer2, peerServer3, peerServer4) + + // register server + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer1.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer2.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer3.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer4.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + + val output = Source.single(Right(rumorRequest)).via(gossipHandler).runWith(Sink.head) + + peers.map(_.receiveOne(duration)).count(_ != null) shouldBe 1 + + } + } From 5e857338d59c91b473388847c0dfdb3f6ebcba64 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Thu, 2 May 2024 15:26:21 +0200 Subject: [PATCH 08/22] changed map to jsonRPC to track jsonID + handle response --- .../pop/decentralized/GossipManager.scala | 57 ++++++++++++++----- .../ch/epfl/pop/pubsub/PublishSubscribe.scala | 6 +- .../decentralized/GossipManagerSuite.scala | 48 +++++++++++++++- 3 files changed, 93 insertions(+), 18 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index 96671fbc36..3eb054bf80 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -4,10 +4,10 @@ import akka.NotUsed import akka.actor.{Actor, ActorRef, Props} import akka.pattern.AskableActorRef import akka.stream.scaladsl.Flow -import ch.epfl.pop.decentralized.GossipManager.MonitoredRumor +import ch.epfl.pop.decentralized.GossipManager.{MonitoredRumor, SUCCESS} import ch.epfl.pop.decentralized.{ConnectionMediator, GossipManager} import ch.epfl.pop.model.network.method.{GreetServer, Rumor} -import ch.epfl.pop.model.network.{JsonRpcRequest, MethodType} +import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType} import ch.epfl.pop.pubsub.AskPatternConstants import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} @@ -15,6 +15,7 @@ import ch.epfl.pop.storage.DbActor import ch.epfl.pop.storage.DbActor.DbActorReadRumors import scala.concurrent.Await +import scala.util.Random final case class GossipManager( dbActorRef: AskableActorRef, @@ -28,7 +29,7 @@ final case class GossipManager( monitorRef ! GossipManager.Ping() connectionMediator ? ConnectionMediator.Ping() - private var activeGossipProtocol: Map[Rumor, List[ServerInfos]] = Map.empty + private var activeGossipProtocol: Map[JsonRpcRequest, List[ServerInfos]] = Map.empty private def isRumorNew(rumor: Rumor): Boolean = { val readRumorDb = dbActorRef ? DbActor.ReadRumors(Map(rumor.senderPk -> List(rumor.rumorId))) @@ -37,16 +38,16 @@ final case class GossipManager( case failure => true } - private def getPeersForRumor(rumor: Rumor): List[ServerInfos] = { - val activeGossip = activeGossipProtocol.get(rumor) + private def getPeersForRumor(jsonRpcRequest: JsonRpcRequest): List[ServerInfos] = { + val activeGossip = activeGossipProtocol.get(jsonRpcRequest) activeGossip match case Some(peersInfosList) => peersInfosList case None => List.empty } - private def sendRumorToRandomPeer(jsonRpcRequest: JsonRpcRequest, rumor: Rumor): Unit = { + private def sendRumorToRandomPeer(rumorRpc: JsonRpcRequest): Unit = { // checks the peers to which we already forwarded the message - val activeGossip: List[ServerInfos] = getPeersForRumor(rumor) + val activeGossip: List[ServerInfos] = getPeersForRumor(rumorRpc) // selects a random peer from remaining peers val randomPeer = connectionMediator ? ConnectionMediator.GetRandomPeer(activeGossip.map(_._1)) Await.result(randomPeer, duration) match { @@ -54,19 +55,36 @@ final case class GossipManager( // if some peers are available we send case ConnectionMediator.GetRandomPeerAck(serverRef, greetServer) => val alreadySent: List[ServerInfos] = activeGossip :+ (serverRef -> greetServer) - activeGossipProtocol += (rumor -> alreadySent) + activeGossipProtocol += (rumorRpc -> alreadySent) serverRef ! ClientAnswer( - Right(jsonRpcRequest) + Right(rumorRpc) ) // else remove entry case ConnectionMediator.NoPeer => - activeGossipProtocol = activeGossipProtocol.removed(rumor) + activeGossipProtocol = activeGossipProtocol.removed(rumorRpc) + } + } + + private def processResponse(response: JsonRpcResponse): Unit = { + val activeGossipPeers = activeGossipProtocol.filter((k, _) => k.id == response.id) + // response is expected because only one entry exists + if (activeGossipPeers.size == 1) { + activeGossipPeers.foreach { (rumorRpc, _) => + if (response.id.get == SUCCESS && Random.nextDouble() < stopProbability) { + activeGossipProtocol -= rumorRpc + } else { + sendRumorToRandomPeer(rumorRpc) + } + } } } override def receive: Receive = { - case GossipManager.SendRumorToRandomPeer(jsonRpcRequest, rumor) => - sendRumorToRandomPeer(jsonRpcRequest, rumor) + case GossipManager.SendRumorToRandomPeer(rumorRpc) => + sendRumorToRandomPeer(rumorRpc) + + case GossipManager.ProcessResponse(jsonRpcResponse) => + processResponse(jsonRpcResponse) } } @@ -79,16 +97,25 @@ object GossipManager extends AskPatternConstants { case Right(jsonRpcRequest: JsonRpcRequest) => jsonRpcRequest.method match case MethodType.rumor => - val rumor = jsonRpcRequest.getParams.asInstanceOf[Rumor] - gossipManager ? SendRumorToRandomPeer(jsonRpcRequest, rumor) + gossipManager ? SendRumorToRandomPeer(jsonRpcRequest) Right(jsonRpcRequest) case _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "GossipManager received a non expected jsonRpcRequest", jsonRpcRequest.id)) case graphMessage @ _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "GossipManager received an unexpected message:" + graphMessage, None)) } + def monitorResponse(gossipManager: AskableActorRef): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { + case Right(jsonRpcResponse: JsonRpcResponse) => + gossipManager ? ProcessResponse(jsonRpcResponse) + Right(jsonRpcResponse) + case graphMessage @ _ => graphMessage + } + + private final val SUCCESS = 0 + sealed trait Event final case class MonitoredRumor(jsonRpcRumor: JsonRpcRequest) - final case class SendRumorToRandomPeer(jsonRpcRequest: JsonRpcRequest, rumor: Rumor) + final case class SendRumorToRandomPeer(jsonRpcRequest: JsonRpcRequest) + final case class ProcessResponse(jsonRpcResponse: JsonRpcResponse) sealed trait GossipManagerMessage final case class Ping() extends GossipManagerMessage diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala index d249ac5725..dcfc6c8ba1 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala @@ -71,7 +71,9 @@ object PublishSubscribe { )) val requestPartition = builder.add(validateRequests(clientActorRef, messageRegistry)) - val responsePartition = builder.add(GetMessagesByIdResponseHandler.responseHandler(messageRegistry)) + + val gossipMonitorPartition = builder.add(GossipManager.monitorResponse(gossipManager)) + val getMsgByIdResponsePartition = builder.add(GetMessagesByIdResponseHandler.responseHandler(messageRegistry)) // ResponseHandler messages do not go in the merger val merger = builder.add(Merge[GraphMessage](totalPorts - 1)) @@ -90,7 +92,7 @@ object PublishSubscribe { methodPartitioner.out(portPipelineError) ~> merger methodPartitioner.out(portRpcRequest) ~> requestPartition ~> merger - methodPartitioner.out(portRpcResponse) ~> responsePartition ~> droppingSink + methodPartitioner.out(portRpcResponse) ~> gossipMonitorPartition ~> getMsgByIdResponsePartition ~> droppingSink merger ~> broadcast broadcast ~> jsonRpcAnswerGenerator ~> jsonRpcAnswerer ~> output diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index 5e8dd95127..886ba7dbc6 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -12,11 +12,12 @@ import org.scalatest.matchers.should.Matchers import akka.pattern.ask import akka.stream.scaladsl.{Flow, Sink, Source} import ch.epfl.pop.IOHelper.readJsonFromPath -import ch.epfl.pop.model.network.JsonRpcRequest +import ch.epfl.pop.model.network.{ErrorObject, JsonRpcRequest, JsonRpcResponse} import ch.epfl.pop.model.network.method.{GreetServer, Rumor} import ch.epfl.pop.model.objects.{Base64Data, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.GraphMessage +import ch.epfl.pop.pubsub.graph.validators.RpcValidator import scala.concurrent.Await @@ -72,8 +73,53 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys val output = Source.single(Right(rumorRequest)).via(gossipHandler).runWith(Sink.head) + Await.result(output, duration) + peers.map(_.receiveOne(duration)).count(_ != null) shouldBe 1 } + test("gossip handler should send rumor if there is an ongoing gossip protocol") { + val gossipManager: ActorRef = system.actorOf(GossipManager.props(dbActorRef, monitorRef, connectionMediatorRef)) + val gossipHandler = GossipManager.gossipHandler(gossipManager) + val gossipMonitor = GossipManager.monitorResponse(gossipManager) + + val peerServer1 = TestProbe() + val peerServer2 = TestProbe() + val peerServer3 = TestProbe() + val peerServer4 = TestProbe() + + val peers = List(peerServer1, peerServer2, peerServer3, peerServer4) + + // register server + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer1.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer2.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer3.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer4.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + + val outputRumor = Source.single(Right(rumorRequest)).via(gossipHandler).runWith(Sink.head) + + Await.result(outputRumor, duration) + + val received = peers.map(_.receiveOne(duration)) + + received.count(_ != null) shouldBe 1 + + val remainingPeers = peers.lazyZip(received).filter((_, recv) => recv == null).map(_._1) + + remainingPeers.size shouldBe peers.size - 1 + + val response = Right(JsonRpcResponse( + RpcValidator.JSON_RPC_VERSION, + ErrorObject(0, "received new Rumor"), + rumorRequest.id + )) + + val outputResponse = Source.single(response).via(gossipMonitor).runWith(Sink.head) + + Await.result(outputResponse, duration) + remainingPeers.map(_.receiveOne(duration)).count(_ != null) shouldBe 1 + + } + } From 6954f2c5b4bd05fee69e3868e17be9875396a5e7 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Thu, 2 May 2024 16:20:40 +0200 Subject: [PATCH 09/22] add responses to rumor handler and tests --- .../pop/decentralized/GossipManager.scala | 6 ++-- .../pubsub/graph/handlers/ParamsHandler.scala | 20 +++++++++---- .../decentralized/GossipManagerSuite.scala | 4 +-- .../graph/handlers/RumorHandlerSuite.scala | 30 ++++++++++++++++++- 4 files changed, 47 insertions(+), 13 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index 3eb054bf80..58c100c7c3 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -4,7 +4,7 @@ import akka.NotUsed import akka.actor.{Actor, ActorRef, Props} import akka.pattern.AskableActorRef import akka.stream.scaladsl.Flow -import ch.epfl.pop.decentralized.GossipManager.{MonitoredRumor, SUCCESS} +import ch.epfl.pop.decentralized.GossipManager.MonitoredRumor import ch.epfl.pop.decentralized.{ConnectionMediator, GossipManager} import ch.epfl.pop.model.network.method.{GreetServer, Rumor} import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType} @@ -70,7 +70,7 @@ final case class GossipManager( // response is expected because only one entry exists if (activeGossipPeers.size == 1) { activeGossipPeers.foreach { (rumorRpc, _) => - if (response.id.get == SUCCESS && Random.nextDouble() < stopProbability) { + if (response.result.isEmpty && Random.nextDouble() < stopProbability) { activeGossipProtocol -= rumorRpc } else { sendRumorToRandomPeer(rumorRpc) @@ -110,8 +110,6 @@ object GossipManager extends AskPatternConstants { case graphMessage @ _ => graphMessage } - private final val SUCCESS = 0 - sealed trait Event final case class MonitoredRumor(jsonRpcRumor: JsonRpcRequest) final case class SendRumorToRandomPeer(jsonRpcRequest: JsonRpcRequest) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index 2a06fb5ad7..079d746bb4 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -5,12 +5,12 @@ import akka.actor.ActorRef import akka.pattern.AskableActorRef import akka.stream.scaladsl.Flow import ch.epfl.pop.decentralized.ConnectionMediator -import ch.epfl.pop.model.network.MethodType +import ch.epfl.pop.model.network.{ErrorObject, JsonRpcRequest, JsonRpcResponse, MethodType, ResultObject} import ch.epfl.pop.model.network.method.message.Message import ch.epfl.pop.model.network.method.{GreetServer, Rumor} -import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse} import ch.epfl.pop.model.objects.{Channel, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer +import ch.epfl.pop.pubsub.graph.validators.RpcValidator import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} import ch.epfl.pop.pubsub.{AskPatternConstants, ClientActor, PubSubMediator} import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors, WriteRumor} @@ -102,13 +102,21 @@ object ParamsHandler extends AskPatternConstants { val readRumorDb = dbActorRef ? ReadRumors(Map(senderPk -> List(rumorId))) Await.result(readRumorDb, duration) match { // already present - case DbActorReadRumors(foundRumors) => // do nothing + case DbActorReadRumors(foundRumors) => + Right(JsonRpcResponse( + RpcValidator.JSON_RPC_VERSION, + ErrorObject(-3, s"rumor $rumorId already present"), + jsonRpcMessage.id + )) // absent case failure => - val writeRumor = dbActorRef ? WriteRumor(rumor) - + dbActorRef ? WriteRumor(rumor) + Right(JsonRpcResponse( + RpcValidator.JSON_RPC_VERSION, + ResultObject(0), + jsonRpcMessage.id + )) } - Right(jsonRpcMessage) case _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "RumorHandler received a non expected jsonRpcRequest", jsonRpcMessage.id)) } case graphMessage @ _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "RumorHandler received an unexpected message:" + graphMessage, None)) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index 886ba7dbc6..8b5589957f 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -12,7 +12,7 @@ import org.scalatest.matchers.should.Matchers import akka.pattern.ask import akka.stream.scaladsl.{Flow, Sink, Source} import ch.epfl.pop.IOHelper.readJsonFromPath -import ch.epfl.pop.model.network.{ErrorObject, JsonRpcRequest, JsonRpcResponse} +import ch.epfl.pop.model.network.{ErrorObject, JsonRpcRequest, JsonRpcResponse, ResultObject} import ch.epfl.pop.model.network.method.{GreetServer, Rumor} import ch.epfl.pop.model.objects.{Base64Data, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer @@ -111,7 +111,7 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys val response = Right(JsonRpcResponse( RpcValidator.JSON_RPC_VERSION, - ErrorObject(0, "received new Rumor"), + ResultObject(0), rumorRequest.id )) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index a6b8992aca..9809425ebd 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -12,7 +12,7 @@ import akka.stream.scaladsl.{Flow, Sink, Source} import ch.epfl.pop.IOHelper.readJsonFromPath import ch.epfl.pop.config.RuntimeEnvironment import ch.epfl.pop.decentralized.{ConnectionMediator, Monitor} -import ch.epfl.pop.model.network.JsonRpcRequest +import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse} import ch.epfl.pop.model.network.method.{GreetServer, Rumor} import ch.epfl.pop.model.objects.{Base64Data, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer @@ -71,4 +71,32 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" } + test("rumor handler should output a success response if rumor is a new rumor") { + + val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) + + val outputResult = Await.result(output, duration) + + outputResult shouldBe a[Right[_, JsonRpcResponse]] + + outputResult match + case Right(jsonRpcResponse: JsonRpcResponse) => jsonRpcResponse.result.isDefined shouldBe true + case _ => 1 shouldBe 0 + + } + + test("rumor handler should output a error response if rumor is a old rumor") { + val dbWrite = dbActorRef ? DbActor.WriteRumor(rumor) + Await.result(dbWrite, duration) + + val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) + + val outputResult = Await.result(output, duration) + + outputResult shouldBe a[Right[_, JsonRpcResponse]] + + outputResult match + case Right(jsonRpcResponse: JsonRpcResponse) => jsonRpcResponse.error.isDefined shouldBe true + case _ => 1 shouldBe 0 + } } From 9def47a8911ad7a27c5cbeb5ccb545799d7dc90b Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Thu, 2 May 2024 17:20:38 +0200 Subject: [PATCH 10/22] rumor handler to new readRumor and tests --- .../pop/decentralized/GossipManager.scala | 8 ++-- .../pubsub/graph/handlers/ParamsHandler.scala | 32 ++++++++-------- .../graph/handlers/RumorHandlerSuite.scala | 37 ++++++++++++------- 3 files changed, 46 insertions(+), 31 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index 58c100c7c3..8f0c3ad906 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -32,10 +32,12 @@ final case class GossipManager( private var activeGossipProtocol: Map[JsonRpcRequest, List[ServerInfos]] = Map.empty private def isRumorNew(rumor: Rumor): Boolean = { - val readRumorDb = dbActorRef ? DbActor.ReadRumors(Map(rumor.senderPk -> List(rumor.rumorId))) + val readRumorDb = dbActorRef ? DbActor.ReadRumors(rumor.senderPk -> rumor.rumorId) Await.result(readRumorDb, duration) match - case DbActorReadRumors(foundRumors) => false - case failure => true + case DbActorReadRumors(foundRumors) => + foundRumors match + case Some(_) => false + case None => true } private def getPeersForRumor(jsonRpcRequest: JsonRpcRequest): List[ServerInfos] = { diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index 079d746bb4..550e4ce8d6 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -99,23 +99,25 @@ object ParamsHandler extends AskPatternConstants { val messages: Map[Channel, List[Message]] = rumor.messages // check if rumor already received - val readRumorDb = dbActorRef ? ReadRumors(Map(senderPk -> List(rumorId))) + val readRumorDb = dbActorRef ? ReadRumors(senderPk -> rumorId) Await.result(readRumorDb, duration) match { // already present - case DbActorReadRumors(foundRumors) => - Right(JsonRpcResponse( - RpcValidator.JSON_RPC_VERSION, - ErrorObject(-3, s"rumor $rumorId already present"), - jsonRpcMessage.id - )) - // absent - case failure => - dbActorRef ? WriteRumor(rumor) - Right(JsonRpcResponse( - RpcValidator.JSON_RPC_VERSION, - ResultObject(0), - jsonRpcMessage.id - )) + case DbActorReadRumors(foundRumor) => + foundRumor match + case Some(_) => + Right(JsonRpcResponse( + RpcValidator.JSON_RPC_VERSION, + ErrorObject(-3, s"rumor $rumorId already present"), + jsonRpcMessage.id + )) + // absent + case None => + dbActorRef ? WriteRumor(rumor) + Right(JsonRpcResponse( + RpcValidator.JSON_RPC_VERSION, + ResultObject(0), + jsonRpcMessage.id + )) } case _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "RumorHandler received a non expected jsonRpcRequest", jsonRpcMessage.id)) } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 9809425ebd..66f17ffce3 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -18,23 +18,34 @@ import ch.epfl.pop.model.objects.{Base64Data, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.GraphMessage import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors} -import org.scalatest.BeforeAndAfterAll +import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuiteLike import org.scalatest.matchers.should.Matchers.{a, shouldBe} import scala.concurrent.Await -class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem")) with AnyFunSuiteLike with AskPatternConstants with BeforeAndAfterAll { - - val inMemoryStorage: InMemoryStorage = InMemoryStorage() - val messageRegistry: MessageRegistry = MessageRegistry() - val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props, "PubSubMediator") - val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage)), "DbActor") - val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) - val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef)) - val connectionMediatorRef: AskableActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) - - val rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = ParamsHandler.rumorHandler(dbActorRef, connectionMediatorRef) +class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem")) with AnyFunSuiteLike with AskPatternConstants with BeforeAndAfterAll with BeforeAndAfterEach { + + private var inMemoryStorage: InMemoryStorage = _ + private var messageRegistry: MessageRegistry = _ + private var pubSubMediatorRef: ActorRef = _ + private var dbActorRef: AskableActorRef = _ + private var securityModuleActorRef: AskableActorRef = _ + private var monitorRef: ActorRef = _ + private var connectionMediatorRef: AskableActorRef = _ + private var rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = _ + + override def beforeEach(): Unit = { + inMemoryStorage = InMemoryStorage() + messageRegistry = MessageRegistry() + pubSubMediatorRef = system.actorOf(PubSubMediator.props) + dbActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage))) + securityModuleActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) + monitorRef = system.actorOf(Monitor.props(dbActorRef)) + connectionMediatorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + + rumorHandler = ParamsHandler.rumorHandler(dbActorRef, connectionMediatorRef) + } val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" @@ -52,7 +63,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" Await.result(output, duration) - val readRumor = dbActorRef ? ReadRumors(Map(rumor.senderPk -> List(rumor.rumorId))) + val readRumor = dbActorRef ? ReadRumors(rumor.senderPk -> rumor.rumorId) Await.result(readRumor, duration) shouldBe a[DbActorReadRumors] } From 68b7e601e2e6c750926ca6ec2224f87f1db64a77 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Sat, 4 May 2024 10:25:35 +0200 Subject: [PATCH 11/22] fix inter-test dependencies --- .../decentralized/GossipManagerSuite.scala | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index 8b5589957f..c6f3141b74 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -18,18 +18,30 @@ import ch.epfl.pop.model.objects.{Base64Data, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.GraphMessage import ch.epfl.pop.pubsub.graph.validators.RpcValidator +import org.scalatest.BeforeAndAfterEach import scala.concurrent.Await -class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSystem")) with AnyFunSuiteLike with AskPatternConstants with Matchers { +class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSystem")) with AnyFunSuiteLike with AskPatternConstants with Matchers with BeforeAndAfterEach { + + private var inMemoryStorage: InMemoryStorage = _ + private var messageRegistry: MessageRegistry = _ + private var pubSubMediatorRef: ActorRef = _ + private var dbActorRef: AskableActorRef = _ + private var securityModuleActorRef: AskableActorRef = _ + private var monitorRef: ActorRef = _ + private var connectionMediatorRef: AskableActorRef = _ + + override def beforeEach(): Unit = { + inMemoryStorage = InMemoryStorage() + messageRegistry = MessageRegistry() + pubSubMediatorRef = system.actorOf(PubSubMediator.props) + dbActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage))) + securityModuleActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) + monitorRef = system.actorOf(Monitor.props(dbActorRef)) + connectionMediatorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) - val inMemoryStorage: InMemoryStorage = InMemoryStorage() - val messageRegistry: MessageRegistry = MessageRegistry() - val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props, "PubSubMediator") - val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage)), "DbActor") - val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) - val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef)) - val connectionMediatorRef: AskableActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + } val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" From 2070a62f234fdc267659f1957ae2dd00fa91d55c Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Sat, 4 May 2024 10:42:23 +0200 Subject: [PATCH 12/22] scalaFmt --- .../scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index c6f3141b74..a82a2fe186 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -31,7 +31,7 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys private var securityModuleActorRef: AskableActorRef = _ private var monitorRef: ActorRef = _ private var connectionMediatorRef: AskableActorRef = _ - + override def beforeEach(): Unit = { inMemoryStorage = InMemoryStorage() messageRegistry = MessageRegistry() From a4f3b34772817f6a35baa942ab7990f60cbf117b Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Sat, 4 May 2024 20:15:02 +0200 Subject: [PATCH 13/22] changed getMsgId handler to more general message handler and added processing of message in rumor handler --- .../ch/epfl/pop/pubsub/PublishSubscribe.scala | 8 +++---- .../pubsub/graph/handlers/ParamsHandler.scala | 12 ++++++++--- ...ler.scala => ProcessMessagesHandler.scala} | 21 +++++++++++++------ .../GetMessagesByIdResponseHandlerSuite.scala | 2 +- .../graph/handlers/RumorHandlerSuite.scala | 4 +++- 5 files changed, 32 insertions(+), 15 deletions(-) rename be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/{GetMessagesByIdResponseHandler.scala => ProcessMessagesHandler.scala} (85%) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala index dcfc6c8ba1..c853227e4a 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/PublishSubscribe.scala @@ -10,7 +10,7 @@ import ch.epfl.pop.decentralized.{GossipManager, Monitor} import ch.epfl.pop.model.network.MethodType.* import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType} import ch.epfl.pop.pubsub.graph.* -import ch.epfl.pop.pubsub.graph.handlers.{GetMessagesByIdResponseHandler, ParamsHandler, ParamsWithMapHandler, ParamsWithMessageHandler} +import ch.epfl.pop.pubsub.graph.handlers.{ProcessMessagesHandler, ParamsHandler, ParamsWithMapHandler, ParamsWithMessageHandler} object PublishSubscribe { @@ -73,7 +73,7 @@ object PublishSubscribe { val requestPartition = builder.add(validateRequests(clientActorRef, messageRegistry)) val gossipMonitorPartition = builder.add(GossipManager.monitorResponse(gossipManager)) - val getMsgByIdResponsePartition = builder.add(GetMessagesByIdResponseHandler.responseHandler(messageRegistry)) + val getMsgByIdResponsePartition = builder.add(ProcessMessagesHandler.getMsgByIdResponseHandler(messageRegistry)) // ResponseHandler messages do not go in the merger val merger = builder.add(Merge[GraphMessage](totalPorts - 1)) @@ -103,7 +103,7 @@ object PublishSubscribe { } }) - def validateRequests(clientActorRef: ActorRef, messageRegistry: MessageRegistry): Flow[GraphMessage, GraphMessage, NotUsed] = + def validateRequests(clientActorRef: ActorRef, messageRegistry: MessageRegistry)(implicit system: ActorSystem): Flow[GraphMessage, GraphMessage, NotUsed] = Flow.fromGraph(GraphDSL.create() { implicit builder: GraphDSL.Builder[NotUsed] => { @@ -152,7 +152,7 @@ object PublishSubscribe { val heartbeatPartition = builder.add(ParamsWithMapHandler.heartbeatHandler(dbActorRef)) val getMessagesByIdPartition = builder.add(ParamsWithMapHandler.getMessagesByIdHandler(dbActorRef)) val greetServerPartition = builder.add(ParamsHandler.greetServerHandler(clientActorRef)) - val rumorPartition = builder.add(ParamsHandler.rumorHandler(dbActorRef, connectionMediatorRef)) + val rumorPartition = builder.add(ParamsHandler.rumorHandler(dbActorRef, messageRegistry)) val gossipManagerPartition = builder.add(GossipManager.gossipHandler(gossipManager)) val merger = builder.add(Merge[GraphMessage](totalPorts)) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index 550e4ce8d6..b024398877 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -1,7 +1,7 @@ package ch.epfl.pop.pubsub.graph.handlers import akka.NotUsed -import akka.actor.ActorRef +import akka.actor.{ActorRef, ActorSystem} import akka.pattern.AskableActorRef import akka.stream.scaladsl.Flow import ch.epfl.pop.decentralized.ConnectionMediator @@ -12,7 +12,7 @@ import ch.epfl.pop.model.objects.{Channel, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.validators.RpcValidator import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} -import ch.epfl.pop.pubsub.{AskPatternConstants, ClientActor, PubSubMediator} +import ch.epfl.pop.pubsub.{AskPatternConstants, ClientActor, MessageRegistry, PubSubMediator} import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors, WriteRumor} import scala.concurrent.ExecutionContext.Implicits.global @@ -89,7 +89,7 @@ object ParamsHandler extends AskPatternConstants { case graphMessage @ _ => Left(PipelineError(ErrorCodes.SERVER_ERROR.id, "GreetServerHandler received an unexpected message:" + graphMessage, None)) }.filter(_ => false) - def rumorHandler(dbActorRef: AskableActorRef, connectionMediatorRef: AskableActorRef): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { + def rumorHandler(dbActorRef: AskableActorRef, messageRegistry: MessageRegistry)(implicit system: ActorSystem): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { case Right(jsonRpcMessage: JsonRpcRequest) => jsonRpcMessage.method match { case MethodType.rumor => @@ -113,6 +113,12 @@ object ParamsHandler extends AskPatternConstants { // absent case None => dbActorRef ? WriteRumor(rumor) + val success = ProcessMessagesHandler.rumorHandler(messageRegistry, rumor) + if (success) { + system.log.info(s"All messages from rumor $rumorId were processed correctly") + } else { + system.log.info(s"Some messages from rumor $rumorId were not processed") + } Right(JsonRpcResponse( RpcValidator.JSON_RPC_VERSION, ResultObject(0), diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ProcessMessagesHandler.scala similarity index 85% rename from be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandler.scala rename to be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ProcessMessagesHandler.scala index b9881f0206..66544d4c37 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ProcessMessagesHandler.scala @@ -3,7 +3,7 @@ package ch.epfl.pop.pubsub.graph.handlers import akka.NotUsed import akka.actor.{ActorRef, ActorSystem} import akka.stream.scaladsl.{Flow, Sink, Source} -import ch.epfl.pop.model.network.method.Publish +import ch.epfl.pop.model.network.method.{Publish, Rumor} import ch.epfl.pop.model.network.method.message.Message import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse, MethodType, ResultObject} import ch.epfl.pop.model.objects.Channel @@ -17,7 +17,7 @@ import scala.concurrent.Await //This object's job is to handle responses it receives from other servers after sending a heartbeat. // When receiving the missing messages, the server's job is to write them on the database. -object GetMessagesByIdResponseHandler extends AskPatternConstants { +object ProcessMessagesHandler extends AskPatternConstants { private val MAX_RETRY_PER_MESSAGE = 10 private val SUCCESS = 0 @@ -31,13 +31,11 @@ object GetMessagesByIdResponseHandler extends AskPatternConstants { * @return * Left if some messages couldn't be validated after MAX_RETRY_PER_MESSAGE times, Right for success */ - def responseHandler(messageRegistry: MessageRegistry)(implicit system: ActorSystem): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { + def getMsgByIdResponseHandler(messageRegistry: MessageRegistry)(implicit system: ActorSystem): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { case Right(JsonRpcResponse(_, Some(resultObject), None, _)) => resultObject.resultMap match { case Some(resultMap) => - val receivedResponse = wrapMsgInPublish(resultMap, MAX_RETRY_PER_MESSAGE) - val validator = PublishSubscribe.validateRequests(ActorRef.noSender, messageRegistry) - val success: Boolean = passThroughPipeline(receivedResponse, validator) + val success: Boolean = processMsgMap(resultMap, messageRegistry) if (success) { Right(JsonRpcResponse(RpcValidator.JSON_RPC_VERSION, new ResultObject(SUCCESS), None)) } else { @@ -51,6 +49,17 @@ object GetMessagesByIdResponseHandler extends AskPatternConstants { case value @ _ => value } + def rumorHandler(messageRegistry: MessageRegistry, rumor: Rumor)(implicit system: ActorSystem): Boolean = { + val msgMap = rumor.messages.map((k, v) => (k, v.toSet)) + processMsgMap(msgMap, messageRegistry) + } + + private def processMsgMap(msgMap: Map[Channel, Set[Message]], messageRegistry: MessageRegistry)(implicit system: ActorSystem): Boolean = { + val receivedResponse = wrapMsgInPublish(msgMap, MAX_RETRY_PER_MESSAGE) + val validator = PublishSubscribe.validateRequests(ActorRef.noSender, messageRegistry) + passThroughPipeline(receivedResponse, validator) + } + /** Will try to digest each GraphMessage until their retry-counter reaches 0 or they all get validated * * @param receivedResponse diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala index 8e6b2b711d..453a6eef06 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/GetMessagesByIdResponseHandlerSuite.scala @@ -39,7 +39,7 @@ class GetMessagesByIdResponseHandlerSuite extends TestKit(ActorSystem("GetMessag PublishSubscribe.buildGraph(pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry, ActorRef.noSender, ActorRef.noSender, ActorRef.noSender, isServer = false) // handler we want to test - val responseHandler: Flow[GraphMessage, GraphMessage, NotUsed] = GetMessagesByIdResponseHandler.responseHandler(MessageRegistry()) + val responseHandler: Flow[GraphMessage, GraphMessage, NotUsed] = ProcessMessagesHandler.getMsgByIdResponseHandler(MessageRegistry()) // loading the files val pathIncorrectGetMessageById: String = "src/test/scala/util/examples/json/get_messages_by_id_answer_with_wrong_messages.json" diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 66f17ffce3..b88ebc4441 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -44,7 +44,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" monitorRef = system.actorOf(Monitor.props(dbActorRef)) connectionMediatorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) - rumorHandler = ParamsHandler.rumorHandler(dbActorRef, connectionMediatorRef) + rumorHandler = ParamsHandler.rumorHandler(dbActorRef, messageRegistry) } val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" @@ -110,4 +110,6 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" case Right(jsonRpcResponse: JsonRpcResponse) => jsonRpcResponse.error.isDefined shouldBe true case _ => 1 shouldBe 0 } + + test("rumor handler should process messages received in a rumor") {} } From 21a9afd6a14ec6802c4d360a4c2594b83232cfb3 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Sun, 5 May 2024 02:40:26 +0200 Subject: [PATCH 14/22] added json of correct suite of msg and tests for processing --- .../graph/handlers/RumorHandlerSuite.scala | 60 +++++- .../json/rumor/rumor_correct_msg.json | 181 ++++++++++++++++++ 2 files changed, 232 insertions(+), 9 deletions(-) create mode 100644 be2-scala/src/test/scala/util/examples/json/rumor/rumor_correct_msg.json diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index b88ebc4441..9127c4028f 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -5,27 +5,32 @@ import akka.actor.Status.Failure import akka.actor.{ActorRef, ActorSystem, Props} import akka.pattern.AskableActorRef import akka.testkit.{TestKit, TestProbe} -import ch.epfl.pop.pubsub.{AskPatternConstants, MessageRegistry, PubSubMediator} +import ch.epfl.pop.pubsub.{AskPatternConstants, MessageRegistry, PubSubMediator, PublishSubscribe} import ch.epfl.pop.storage.{DbActor, InMemoryStorage, SecurityModuleActor} import akka.pattern.ask import akka.stream.scaladsl.{Flow, Sink, Source} import ch.epfl.pop.IOHelper.readJsonFromPath import ch.epfl.pop.config.RuntimeEnvironment import ch.epfl.pop.decentralized.{ConnectionMediator, Monitor} +import ch.epfl.pop.model.network.method.message.Message import ch.epfl.pop.model.network.{JsonRpcRequest, JsonRpcResponse} import ch.epfl.pop.model.network.method.{GreetServer, Rumor} -import ch.epfl.pop.model.objects.{Base64Data, PublicKey} +import ch.epfl.pop.model.objects.{Base64Data, Channel, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.GraphMessage import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuiteLike -import org.scalatest.matchers.should.Matchers.{a, shouldBe} +import org.scalatest.matchers.should.Matchers +import org.scalatest.matchers.should.Matchers.{a, equal, should, shouldBe} import scala.concurrent.Await +import scala.concurrent.duration.FiniteDuration class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem")) with AnyFunSuiteLike with AskPatternConstants with BeforeAndAfterAll with BeforeAndAfterEach { + val MAX_TIME: FiniteDuration = duration.mul(2) + private var inMemoryStorage: InMemoryStorage = _ private var messageRegistry: MessageRegistry = _ private var pubSubMediatorRef: ActorRef = _ @@ -34,6 +39,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" private var monitorRef: ActorRef = _ private var connectionMediatorRef: AskableActorRef = _ private var rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = _ + private var processDuration: FiniteDuration = _ override def beforeEach(): Unit = { inMemoryStorage = InMemoryStorage() @@ -44,10 +50,27 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" monitorRef = system.actorOf(Monitor.props(dbActorRef)) connectionMediatorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + // Inject dbActor above + PublishSubscribe.buildGraph(pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry, ActorRef.noSender, ActorRef.noSender, ActorRef.noSender, isServer = false) + + val nbMessaages = rumor.messages.values.foldLeft(0)((acc, msgs) => acc + msgs.size) + processDuration = duration.mul(nbMessaages) + rumorHandler = ParamsHandler.rumorHandler(dbActorRef, messageRegistry) } - val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor.json" + // Helper function + // Get rid of decoded data to compare against original message + private def getMessages(channel: Channel): Set[Message] = { + val ask = dbActorRef ? DbActor.Catchup(channel) + Await.result(ask, MAX_TIME) match { + case DbActor.DbActorCatchupAck(list) => list + .map(msg => Message(msg.data, msg.sender, msg.signature, msg.message_id, msg.witness_signatures, None)).toSet + case _ => Matchers.fail(s"Couldn't catchup on channel: $channel") + } + } + + val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor_correct_msg.json" val rumorRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath(pathCorrectRumor)) @@ -61,7 +84,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" test("rumor handler should write new rumors in memory") { val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) - Await.result(output, duration) + Await.result(output, processDuration) val readRumor = dbActorRef ? ReadRumors(rumor.senderPk -> rumor.rumorId) Await.result(readRumor, duration) shouldBe a[DbActorReadRumors] @@ -78,7 +101,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" val output = Source.single(Right(publishRequest)).via(rumorHandler).runWith(Sink.head) - Await.result(output, duration) shouldBe a[Left[_, _]] + Await.result(output, processDuration) shouldBe a[Left[_, _]] } @@ -86,7 +109,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) - val outputResult = Await.result(output, duration) + val outputResult = Await.result(output, processDuration) outputResult shouldBe a[Right[_, JsonRpcResponse]] @@ -98,7 +121,7 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" test("rumor handler should output a error response if rumor is a old rumor") { val dbWrite = dbActorRef ? DbActor.WriteRumor(rumor) - Await.result(dbWrite, duration) + Await.result(dbWrite, processDuration) val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) @@ -111,5 +134,24 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" case _ => 1 shouldBe 0 } - test("rumor handler should process messages received in a rumor") {} + test("rumor handler should process messages received in a rumor") { + + val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) + + val outputResult = Await.result(output, processDuration) + + val ask = dbActorRef ? DbActor.GetAllChannels() + val channelsInDb = Await.result(ask, MAX_TIME) match { + case DbActor.DbActorGetAllChannelsAck(channels) => channels + case err @ _ => Matchers.fail(err.toString) + } + + val channelsInRumor = rumor.messages.keySet + channelsInRumor.diff(channelsInDb) should equal(Set.empty) + + val messagesInDb: Set[Message] = channelsInDb.foldLeft(Set.empty: Set[Message])((acc, channel) => acc ++ getMessages(channel)) + val messagesInRumor = rumor.messages.values.foldLeft(Set.empty: Set[Message])((acc, set) => acc ++ set) + + messagesInRumor.diff(messagesInDb) should equal(Set.empty) + } } diff --git a/be2-scala/src/test/scala/util/examples/json/rumor/rumor_correct_msg.json b/be2-scala/src/test/scala/util/examples/json/rumor/rumor_correct_msg.json new file mode 100644 index 0000000000..fd0ff5b281 --- /dev/null +++ b/be2-scala/src/test/scala/util/examples/json/rumor/rumor_correct_msg.json @@ -0,0 +1,181 @@ +{ + "jsonrpc": "2.0", + "id": 4, + "method": "rumor", + "params": { + "sender_id": "J9fBzJV70Jk5c-i3277Uq4CmeL4t53WDfUghaK0HpeM=", + "rumor_id": 1, + "messages": { + "/root": [ + { + "data": "eyJvYmplY3QiOiJsYW8iLCJhY3Rpb24iOiJjcmVhdGUiLCJuYW1lIjoidGVzdCIsImNyZWF0aW9uIjoxNjgzMzE2MjMyLCJvcmdhbml6ZXIiOiJVdXZyOWNGZTByN2o4ZjY0YVB2cms3WjY1SnQ0a3Y5bkd6NEhYZ295MzhRPSIsIndpdG5lc3NlcyI6W10sImlkIjoibzNueUlJdnJtRHJpd1lYbkg1LThDbVlkeVpjejRCUE5UOTlXN2Rjcms5TT0ifQ==", + "message_id": "b1yZTpJ06PuYlPlzggt0mkaLm815-y8hwtck8tAy9FU=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "xdFqCJpI4KUwtZamrh0qGAo5w8GQNLGHmTTZY0WpN4y9IzkOtgQwL6R1ZgMHqnhQP0ji5uZjhsjcSdX7muraAg==", + "witness_signatures": [] + } + ], + "/root/o3nyIIvrmDriwYXnH5-8CmYdyZcz4BPNT99W7dcrk9M=/iNBfwu7qLstfwC9aF0Jm9fyZGCJYqfE12PCkA5Ic5JE=": [ + { + "data": "eyJvYmplY3QiOiJlbGVjdGlvbiIsImFjdGlvbiI6ImNhc3Rfdm90ZSIsImxhbyI6Im8zbnlJSXZybURyaXdZWG5INS04Q21ZZHlaY3o0QlBOVDk5VzdkY3JrOU09IiwiY3JlYXRlZF9hdCI6MTY4MzMxNjQ3NCwidm90ZXMiOlt7ImlkIjoiTFpwSnkxWVpqSW91eElwOU1YbF9PVFU0Zl96Sm96a2lJVGo2ZVlob2ppbz0iLCJxdWVzdGlvbiI6InFmVk9HVzQxcmwtQ09nWVZMYmxUVnlFOTBmSXpRcmZRS0gyMXJ3eXFoMUE9Iiwidm90ZSI6MH0seyJpZCI6ImZoelB4cmVLY19lalVMZkNKZ0NlQUxqRXo3b2Z6OGN3bHZzMzJFQXFzTnc9IiwicXVlc3Rpb24iOiI1U1h0Yi16TmFic0ZIUFdRbjRNU1pzRWswRHFjRUVYejBOVExNNVNaLTJZPSIsInZvdGUiOjB9XSwiZWxlY3Rpb24iOiJpTkJmd3U3cUxzdGZ3QzlhRjBKbTlmeVpHQ0pZcWZFMTJQQ2tBNUljNUpFPSJ9", + "message_id": "ADnsFmRBIDm_w337Xvnk0hjlRFKFdAGRgODbK_nq5cw=", + "sender": "VHfxTlbM3nTnLQuKnKfs1fGP2cwVT8KJkc-sRGs_2KM=", + "signature": "FBe3_zRlDE3lLuKBid-kSmuTGe3tD8I5_OkhPj9RpDhquJhY8ZvM9ImD5A4EjNYXj0u-9h3-GXHc6nwrnF_kCw==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJlbGVjdGlvbiIsImFjdGlvbiI6Im9wZW4iLCJsYW8iOiJvM255SUl2cm1Ecml3WVhuSDUtOENtWWR5WmN6NEJQTlQ5OVc3ZGNyazlNPSIsIm9wZW5lZF9hdCI6MTY4MzMxNjQ3MSwiZWxlY3Rpb24iOiJpTkJmd3U3cUxzdGZ3QzlhRjBKbTlmeVpHQ0pZcWZFMTJQQ2tBNUljNUpFPSJ9", + "message_id": "CvPnQaEMpnJipXi21bNk8VpNubyvHYER6SRfY1C12ys=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "6c3jD8s4VwIeiv3-OrSkGxAG0SfPGwwX1YO-ioYcBVPZqsR_KQCEM6cb_M_XdSMag3Z6HVAXsWsu7vBtFKNtDA==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJlbGVjdGlvbiIsImFjdGlvbiI6ImVuZCIsImxhbyI6Im8zbnlJSXZybURyaXdZWG5INS04Q21ZZHlaY3o0QlBOVDk5VzdkY3JrOU09IiwiY3JlYXRlZF9hdCI6MTY4MzMxNjQ4NCwicmVnaXN0ZXJlZF92b3RlcyI6IlNYZ0pyRWxVTDBNdXpBVzdsNmFObl9zWmJHVmYxay1yVXhfZ0E3QnlhdXc9IiwiZWxlY3Rpb24iOiJpTkJmd3U3cUxzdGZ3QzlhRjBKbTlmeVpHQ0pZcWZFMTJQQ2tBNUljNUpFPSJ9", + "message_id": "uGw3dgqxAUQssOfPzWmTCAFarJ126PIDQ7v8GFtiSuE=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "uSv9ncI79kX15KDGZZqQYtuziL3isPBY-amVMb-iviox_plWU2-FvhLGOSb6RTChw5IoLTpIBssAeVJjvNUmDQ==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJlbGVjdGlvbiIsImFjdGlvbiI6ImNhc3Rfdm90ZSIsImxhbyI6Im8zbnlJSXZybURyaXdZWG5INS04Q21ZZHlaY3o0QlBOVDk5VzdkY3JrOU09IiwiY3JlYXRlZF9hdCI6MTY4MzMxNjQ3OCwidm90ZXMiOlt7ImlkIjoidDBXcnRZM0EzR05MLTM2QWVRc0EwMUIyYzlFTFF3Q3FuZkgyNXlnNG1Ubz0iLCJxdWVzdGlvbiI6InFmVk9HVzQxcmwtQ09nWVZMYmxUVnlFOTBmSXpRcmZRS0gyMXJ3eXFoMUE9Iiwidm90ZSI6MX0seyJpZCI6Im9MVFBYcF9RVHZ1WHItcGVVTnJXbFpLc21ESXowbXpTY0s3UkFJQTk0S3c9IiwicXVlc3Rpb24iOiI1U1h0Yi16TmFic0ZIUFdRbjRNU1pzRWswRHFjRUVYejBOVExNNVNaLTJZPSIsInZvdGUiOjF9XSwiZWxlY3Rpb24iOiJpTkJmd3U3cUxzdGZ3QzlhRjBKbTlmeVpHQ0pZcWZFMTJQQ2tBNUljNUpFPSJ9", + "message_id": "ElTvO-z8zGbri3fW8LP5gTj9JG4Koqxal2hwZcyLaK8=", + "sender": "nAHrfl9nLW9OO_sRr1q9mkfdA_S_ig5156aAMykzAu0=", + "signature": "GsiR5fgh-ibGmkbeySnaebjHgi_EGJobUrviiCqdJEaC4h3QY6QzmfUvCubQvTpQsGIz6QifRAsqopWSXleIDQ==", + "witness_signatures": [] + } + ], + "/root/o3nyIIvrmDriwYXnH5-8CmYdyZcz4BPNT99W7dcrk9M=/social/VHfxTlbM3nTnLQuKnKfs1fGP2cwVT8KJkc-sRGs_2KM=": [ + { + "data": "eyJvYmplY3QiOiJjaGlycCIsImFjdGlvbiI6ImFkZCIsInRleHQiOiJDaGlycCAxIiwidGltZXN0YW1wIjoxNjgzMzE2MzM5fQ==", + "message_id": "KrVUL4EWcLsuW1719EmzJJEgoljkD3vu6nk7lHHBxxA=", + "sender": "VHfxTlbM3nTnLQuKnKfs1fGP2cwVT8KJkc-sRGs_2KM=", + "signature": "lDYrTHKs9wIERpQcq1MUql-ZG7Q3JzExPTt7B9KKLo1Ge2HGeb-ASjq-iO2xWrkB5v4jAFz8l369rr-NmK-wAg==", + "witness_signatures": [] + } + ], + "/root/o3nyIIvrmDriwYXnH5-8CmYdyZcz4BPNT99W7dcrk9M=/social/chirps": [ + { + "data": "eyJhY3Rpb24iOiJub3RpZnlfYWRkIiwiY2hhbm5lbCI6Ii9yb290L28zbnlJSXZybURyaXdZWG5INS04Q21ZZHlaY3o0QlBOVDk5VzdkY3JrOU09L3NvY2lhbC9WSGZ4VGxiTTNuVG5MUXVLbktmczFmR1AyY3dWVDhLSmtjLXNSR3NfMktNPSIsImNoaXJwX2lkIjoiS3JWVUw0RVdjTHN1VzE3MTlFbXpKSkVnb2xqa0QzdnU2bms3bEhIQnh4QT0iLCJvYmplY3QiOiJjaGlycCIsInRpbWVzdGFtcCI6MTY4MzMxNjMzOX0=", + "message_id": "0ue6CuLnQ1WgfdYo2TvMDqLZ2TcJaR1bqsapuRPeF3E=", + "sender": "pLGlHn5foBNLuY1Kd_s2y7-XZEAyaS4HcPVeD-VbRKE=", + "signature": "pXcTqKnsnsfUuMVzxVEGu0kyFlEjwhe_iR-a4BfuYE7b5F8mgt2s00BuAFyMKROPzOoVvddzdyyULeHejWmMBg==", + "witness_signatures": [] + }, + { + "data": "eyJhY3Rpb24iOiJub3RpZnlfYWRkIiwiY2hhbm5lbCI6Ii9yb290L28zbnlJSXZybURyaXdZWG5INS04Q21ZZHlaY3o0QlBOVDk5VzdkY3JrOU09L3NvY2lhbC9uQUhyZmw5bkxXOU9PX3NScjFxOW1rZmRBX1NfaWc1MTU2YUFNeWt6QXUwPSIsImNoaXJwX2lkIjoiNEtSNkxwZ3p6aVdJOGJ4QTJNRnd5dmNEN1ZZMjAtc2gxZld1SVk2TV9Vaz0iLCJvYmplY3QiOiJjaGlycCIsInRpbWVzdGFtcCI6MTY4MzMxNjM1M30=", + "message_id": "JrYTauKi7jkMzDydI6mew6D40Ma7Yy2kcyp16WOblDw=", + "sender": "pLGlHn5foBNLuY1Kd_s2y7-XZEAyaS4HcPVeD-VbRKE=", + "signature": "v50rLTDXb-bxv7arZbsB9Sf-CDhjSENzzgBK_-BHXUxsM4iA1e3MV_BSVvkiqnUfqknVNDhzh4jBpgr-RvxwCA==", + "witness_signatures": [] + } + ], + "/root/o3nyIIvrmDriwYXnH5-8CmYdyZcz4BPNT99W7dcrk9M=/coin": [ + { + "data": "eyJvYmplY3QiOiJjb2luIiwiYWN0aW9uIjoicG9zdF90cmFuc2FjdGlvbiIsInRyYW5zYWN0aW9uIjp7InZlcnNpb24iOjEsImlucHV0cyI6W3sidHhfb3V0X2hhc2giOiJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBPSIsInR4X291dF9pbmRleCI6MCwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleSI6IlV1dnI5Y0ZlMHI3ajhmNjRhUHZyazdaNjVKdDRrdjluR3o0SFhnb3kzOFE9Iiwic2lnIjoiTzF5ZTZOcVQ3UnFxRXk2enRRN2paMm1RR2xSU05TYjEwN1A2dnRfLW9TS3pwYk9MdHZ0TzZ1eHdDUWJNb3ZVYzJGUzJWeDNzN3Y2UVNCWXlwaDdURFE9PSJ9fV0sIm91dHB1dHMiOlt7InZhbHVlIjoxMDAsInNjcmlwdCI6eyJ0eXBlIjoiUDJQS0giLCJwdWJrZXlfaGFzaCI6IjRfRXl6cFRDQllkZmpTMXpmTGFERUtxSUthMD0ifX0seyJ2YWx1ZSI6MTAwLCJzY3JpcHQiOnsidHlwZSI6IlAyUEtIIiwicHVia2V5X2hhc2giOiJveWN6OGQ1enRBRlNMelgtVURJalFvOGlIRkk9In19XSwibG9ja190aW1lIjowfSwidHJhbnNhY3Rpb25faWQiOiJwaW8tR3VjaTFPb1NORHF3NUtGX3BqQUZBallhMjV2S2VXVm45emxrTTJvPSJ9", + "message_id": "jEHNwNC_rMHa-5NszrbCtc62hQmBXAWAnGRektF6x84=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "BSCtOJvyi-h89WeCW_baoVhLB0Gy6wJw8yGzRP97kMEzX8ZuCYaNjB_mnPClyTdVjP3Mhk19WOhdAVBOJdthBg==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJjb2luIiwiYWN0aW9uIjoicG9zdF90cmFuc2FjdGlvbiIsInRyYW5zYWN0aW9uIjp7InZlcnNpb24iOjEsImlucHV0cyI6W3sidHhfb3V0X2hhc2giOiJwaW8tR3VjaTFPb1NORHF3NUtGX3BqQUZBallhMjV2S2VXVm45emxrTTJvPSIsInR4X291dF9pbmRleCI6MSwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleSI6IlZIZnhUbGJNM25UbkxRdUtuS2ZzMWZHUDJjd1ZUOEtKa2Mtc1JHc18yS009Iiwic2lnIjoiLU85QkRjTUQ1VzFQWmpPSGs3Z3BwLUFDZm44U2RxMHdpcXdiTXdYQWlVOGVBS1BWb01XTFhRdzF0RTlZRjdPQlMzblVJeGxvUjhGZEhlakZfeTFpQUE9PSJ9fV0sIm91dHB1dHMiOlt7InZhbHVlIjo1MCwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleV9oYXNoIjoiNF9FeXpwVENCWWRmalMxemZMYURFS3FJS2EwPSJ9fSx7InZhbHVlIjo1MCwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleV9oYXNoIjoib3ljejhkNXp0QUZTTHpYLVVESWpRbzhpSEZJPSJ9fV0sImxvY2tfdGltZSI6MH0sInRyYW5zYWN0aW9uX2lkIjoiWTRTVm1LcTFPdG84ZmVfS1ZMUlpBV0QxLWwyWmJkb0ZnU2E4VmVlMEtSMD0ifQ==", + "message_id": "T1oT7v-smRCHmB-czWfl5waZX2co5JLUSq9jM4dV6iU=", + "sender": "vF3T8FXyWVxu2_HnelFu23dZKKrRbZfOXQcugPY0mr8=", + "signature": "if-CmvJZ9mh8pNrH0Qv7wXmAM7YzT-AB-76nQz_YKjCTjQ3JRQM2W1xxUTRI7_sd202mZs_Z3GiMoOP0Zu3_Ag==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJjb2luIiwiYWN0aW9uIjoicG9zdF90cmFuc2FjdGlvbiIsInRyYW5zYWN0aW9uIjp7InZlcnNpb24iOjEsImlucHV0cyI6W3sidHhfb3V0X2hhc2giOiJwaW8tR3VjaTFPb1NORHF3NUtGX3BqQUZBallhMjV2S2VXVm45emxrTTJvPSIsInR4X291dF9pbmRleCI6MCwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleSI6Im5BSHJmbDluTFc5T09fc1JyMXE5bWtmZEFfU19pZzUxNTZhQU15a3pBdTA9Iiwic2lnIjoiQjNOTE82LV9EdTJ3Sy1ubFFsUU5XUm1aNG1sTVVMNTBmeFhyaWNLaHktRkMwSUV5YnR2NDhQWnVVdGZXZC1LNFo2YUcxakFUWDRodk1LNHEybi1qQUE9PSJ9fSx7InR4X291dF9oYXNoIjoiWTRTVm1LcTFPdG84ZmVfS1ZMUlpBV0QxLWwyWmJkb0ZnU2E4VmVlMEtSMD0iLCJ0eF9vdXRfaW5kZXgiOjAsInNjcmlwdCI6eyJ0eXBlIjoiUDJQS0giLCJwdWJrZXkiOiJuQUhyZmw5bkxXOU9PX3NScjFxOW1rZmRBX1NfaWc1MTU2YUFNeWt6QXUwPSIsInNpZyI6IkIzTkxPNi1fRHUyd0stbmxRbFFOV1JtWjRtbE1VTDUwZnhYcmljS2h5LUZDMElFeWJ0djQ4UFp1VXRmV2QtSzRaNmFHMWpBVFg0aHZNSzRxMm4takFBPT0ifX1dLCJvdXRwdXRzIjpbeyJ2YWx1ZSI6NTAsInNjcmlwdCI6eyJ0eXBlIjoiUDJQS0giLCJwdWJrZXlfaGFzaCI6IjRfRXl6cFRDQllkZmpTMXpmTGFERUtxSUthMD0ifX0seyJ2YWx1ZSI6MTAwLCJzY3JpcHQiOnsidHlwZSI6IlAyUEtIIiwicHVia2V5X2hhc2giOiI0X0V5enBUQ0JZZGZqUzF6ZkxhREVLcUlLYTA9In19XSwibG9ja190aW1lIjowfSwidHJhbnNhY3Rpb25faWQiOiJkU3NZeDdRdzFMdXE3a1phLTV0RXU3dllIbG5qTC1jNENsbTVnZHh3ODdJPSJ9", + "message_id": "5hWtZA3q-b7bmCP33wZQKfYmiRXmZEAjlAV3NrDQNW4=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "B3T-FIBsymtCKn2aDhM-W37CPiOszfCziR-MXMV2swLQl46hMdcqleljjXi6cyXwYhhAU1t6dnailaLQKnJgBw==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJjb2luIiwiYWN0aW9uIjoicG9zdF90cmFuc2FjdGlvbiIsInRyYW5zYWN0aW9uIjp7InZlcnNpb24iOjEsImlucHV0cyI6W3sidHhfb3V0X2hhc2giOiJkU3NZeDdRdzFMdXE3a1phLTV0RXU3dllIbG5qTC1jNENsbTVnZHh3ODdJPSIsInR4X291dF9pbmRleCI6MCwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleSI6Im5BSHJmbDluTFc5T09fc1JyMXE5bWtmZEFfU19pZzUxNTZhQU15a3pBdTA9Iiwic2lnIjoidjJERzVOUFREcF9UV1FGbVEwSXc1M1hqblpRUFF6YXRUaEhicmVjWUdkS2hhRkRlMEkwbU5KWlJUQ3lKOGs2MGtsNnRwaG9CMzlib0hKRkhlNHdvQVE9PSJ9fSx7InR4X291dF9oYXNoIjoiZFNzWXg3UXcxTHVxN2taYS01dEV1N3ZZSGxuakwtYzRDbG01Z2R4dzg3ST0iLCJ0eF9vdXRfaW5kZXgiOjEsInNjcmlwdCI6eyJ0eXBlIjoiUDJQS0giLCJwdWJrZXkiOiJuQUhyZmw5bkxXOU9PX3NScjFxOW1rZmRBX1NfaWc1MTU2YUFNeWt6QXUwPSIsInNpZyI6InYyREc1TlBURHBfVFdRRm1RMEl3NTNYam5aUVBRemF0VGhIYnJlY1lHZEtoYUZEZTBJMG1OSlpSVEN5SjhrNjBrbDZ0cGhvQjM5Ym9ISkZIZTR3b0FRPT0ifX0seyJ0eF9vdXRfaGFzaCI6ImRTc1l4N1F3MUx1cTdrWmEtNXRFdTd2WUhsbmpMLWM0Q2xtNWdkeHc4N0k9IiwidHhfb3V0X2luZGV4IjowLCJzY3JpcHQiOnsidHlwZSI6IlAyUEtIIiwicHVia2V5IjoibkFIcmZsOW5MVzlPT19zUnIxcTlta2ZkQV9TX2lnNTE1NmFBTXlrekF1MD0iLCJzaWciOiJ2MkRHNU5QVERwX1RXUUZtUTBJdzUzWGpuWlFQUXphdFRoSGJyZWNZR2RLaGFGRGUwSTBtTkpaUlRDeUo4azYwa2w2dHBob0IzOWJvSEpGSGU0d29BUT09In19LHsidHhfb3V0X2hhc2giOiJkU3NZeDdRdzFMdXE3a1phLTV0RXU3dllIbG5qTC1jNENsbTVnZHh3ODdJPSIsInR4X291dF9pbmRleCI6MSwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleSI6Im5BSHJmbDluTFc5T09fc1JyMXE5bWtmZEFfU19pZzUxNTZhQU15a3pBdTA9Iiwic2lnIjoidjJERzVOUFREcF9UV1FGbVEwSXc1M1hqblpRUFF6YXRUaEhicmVjWUdkS2hhRkRlMEkwbU5KWlJUQ3lKOGs2MGtsNnRwaG9CMzlib0hKRkhlNHdvQVE9PSJ9fV0sIm91dHB1dHMiOlt7InZhbHVlIjo1MCwic2NyaXB0Ijp7InR5cGUiOiJQMlBLSCIsInB1YmtleV9oYXNoIjoib3ljejhkNXp0QUZTTHpYLVVESWpRbzhpSEZJPSJ9fSx7InZhbHVlIjoxMDAsInNjcmlwdCI6eyJ0eXBlIjoiUDJQS0giLCJwdWJrZXlfaGFzaCI6IjRfRXl6cFRDQllkZmpTMXpmTGFERUtxSUthMD0ifX1dLCJsb2NrX3RpbWUiOjB9LCJ0cmFuc2FjdGlvbl9pZCI6InBLOHF4X3doUktjbGoyMXFSRjlWZWJ5NnNoWk02V25rYTdwbHdPLW0zRDQ9In0=", + "message_id": "SpexxR28mMd8GU40As8ASex44YHAH0rLsENvhj-7wJ0=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "8xBeUuLWJ58sir2CLnWXmLaxIX8xQ-H-G6Rctg_OuDH6vBRKnXX0zMvHxvwK92YPwQM1OvgcyR7WE52uj13lCw==", + "witness_signatures": [] + } + ], + "/root/o3nyIIvrmDriwYXnH5-8CmYdyZcz4BPNT99W7dcrk9M=": [ + { + "data": "eyJvYmplY3QiOiJyb2xsX2NhbGwiLCJhY3Rpb24iOiJvcGVuIiwib3BlbmVkX2F0IjoxNjgzMzE2MjgxLCJvcGVucyI6InFRS0NZZ2FuMVEtVnhBbTZHOVNQc0JBZTk4WEJib1BaSld2QlBtSzBDY1k9IiwidXBkYXRlX2lkIjoiWkN1UW1zRkFXRE5KZVNHbHlXVDZFQ18zbWZiZVBxQlVsZzl5Rm9LYUhTND0ifQ==", + "message_id": "EMeZ68FSh8Yhv-PtIc_rYL_LIaoI4LpTqykwZFJ_PZk=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "pSimUI1a3ELfEDthcrc7BkQIaDavXlLIn-m3kyNUgz3yN1Busci9eb9MlWsNWZyTFM7drwStDwzIIue147U0Bg==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJyb2xsX2NhbGwiLCJhY3Rpb24iOiJjbG9zZSIsImNsb3NlZF9hdCI6MTY4MzMxNjMyNywiYXR0ZW5kZWVzIjpbIm5BSHJmbDluTFc5T09fc1JyMXE5bWtmZEFfU19pZzUxNTZhQU15a3pBdTA9IiwiVkhmeFRsYk0zblRuTFF1S25LZnMxZkdQMmN3VlQ4S0prYy1zUkdzXzJLTT0iXSwiY2xvc2VzIjoiWkN1UW1zRkFXRE5KZVNHbHlXVDZFQ18zbWZiZVBxQlVsZzl5Rm9LYUhTND0iLCJ1cGRhdGVfaWQiOiIwWXA3c0Nad19fWFJUem9oWXZ5MXJmNkVqUTlVd0pURzNsQm5GaXBXdFZFPSJ9", + "message_id": "fYExF6GV1hzG5dlLXKTyKnzwrOu1ArFq4DhRvoGCsMw=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "ySSXnTZQwwWSTIJCdLVA6iuaNvYlvZk_t5gECRU9PGQZt0hBfzxRvy3OJ--4_X0E3Q-jHvGUP6o2QJFgRkbUBQ==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJtZWV0aW5nIiwiYWN0aW9uIjoiY3JlYXRlIiwibmFtZSI6IkEgbWVldGluZyIsImNyZWF0aW9uIjoxNjgzMzE2NDIxLCJsb2NhdGlvbiI6IkJDNDEwIiwic3RhcnQiOjE2ODMzMTY0MjEsImVuZCI6MTY4MzMyMDAxMiwiaWQiOiJTWkNsVDFtZGZYTUFDcE5VazNJX3BBVEJfeTd2b2dxd3NKREpMWDVYb3FZPSJ9", + "message_id": "Ocwqc0rbSkd9cfkiw6gLVP_T3037PE0jFCzVGaZjdt8=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "Qq3Xh1wB2LT15LwXCb9NY8ssd5zyR6JvlcUddhFdX6sd4EJXDQUnkFdpqGcdRs0c4j0FxY6XLvWV4FRduBQdCA==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJyb2xsX2NhbGwiLCJhY3Rpb24iOiJjcmVhdGUiLCJuYW1lIjoicm9sbGNhbGwgIiwiY3JlYXRpb24iOjE2ODMzMTYyNzcsInByb3Bvc2VkX3N0YXJ0IjoxNjgzMzE2Mjc3LCJwcm9wb3NlZF9lbmQiOjE2ODMzMTk4NjAsImxvY2F0aW9uIjoiQkM0MTAiLCJkZXNjcmlwdGlvbiI6Im5vdCBhIHJvYm90IiwiaWQiOiJxUUtDWWdhbjFRLVZ4QW02RzlTUHNCQWU5OFhCYm9QWkpXdkJQbUswQ2NZPSJ9", + "message_id": "SzErSVrALRSJmx_dY2osAbRYPcoFtcxLvHZkiNoUTAw=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "S0BpcAUBmoXUWDBk_UBYAn-OtOtm-KkTKKLo0bxufcwyjYYoTVq6YbrR5GDA9ApOw7l_8dcsUq2KhegJnA45AQ==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJlbGVjdGlvbiIsImFjdGlvbiI6InNldHVwIiwibGFvIjoibzNueUlJdnJtRHJpd1lYbkg1LThDbVlkeVpjejRCUE5UOTlXN2Rjcms5TT0iLCJ2ZXJzaW9uIjoiT1BFTl9CQUxMT1QiLCJpZCI6ImlOQmZ3dTdxTHN0ZndDOWFGMEptOWZ5WkdDSllxZkUxMlBDa0E1SWM1SkU9IiwibmFtZSI6IkVsZWN0aW9uIiwiY3JlYXRlZF9hdCI6MTY4MzMxNjQ2Niwic3RhcnRfdGltZSI6MTY4MzMxNjQ2NiwiZW5kX3RpbWUiOjE2ODMzMjAwMjYsInF1ZXN0aW9ucyI6W3siaWQiOiJxZlZPR1c0MXJsLUNPZ1lWTGJsVFZ5RTkwZkl6UXJmUUtIMjFyd3lxaDFBPSIsInF1ZXN0aW9uIjoiUXVlc3Rpb24gMSIsImJhbGxvdF9vcHRpb25zIjpbImFuc3dlciAxIiwiYW5zd2VyIDIiXSwidm90aW5nX21ldGhvZCI6IlBsdXJhbGl0eSIsIndyaXRlX2luIjpmYWxzZX0seyJpZCI6IjVTWHRiLXpOYWJzRkhQV1FuNE1TWnNFazBEcWNFRVh6ME5UTE01U1otMlk9IiwicXVlc3Rpb24iOiJRdWVzdGlvbiAyIiwiYmFsbG90X29wdGlvbnMiOlsiYW5zd2VyIDEiLCJhbnN3ZXIgMiJdLCJ2b3RpbmdfbWV0aG9kIjoiUGx1cmFsaXR5Iiwid3JpdGVfaW4iOmZhbHNlfV19", + "message_id": "xV_G8YljVJkWzSQmyDWwBzT0g-NhQLMOuLaXtSWndzI=", + "sender": "Uuvr9cFe0r7j8f64aPvrk7Z65Jt4kv9nGz4HXgoy38Q=", + "signature": "7sB1HsPav6-q05IGQVqjAPDWgQxFzMPbIno8Ayz2_9pWjwR8uuv9F7olJCkNk__qQlGiiH2-Y2RJQI_qRD0bAQ==", + "witness_signatures": [] + } + ], + "/root/o3nyIIvrmDriwYXnH5-8CmYdyZcz4BPNT99W7dcrk9M=/social/nAHrfl9nLW9OO_sRr1q9mkfdA_S_ig5156aAMykzAu0=": [ + { + "data": "eyJvYmplY3QiOiJjaGlycCIsImFjdGlvbiI6ImFkZCIsInRleHQiOiJDaGlycCAyIiwidGltZXN0YW1wIjoxNjgzMzE2MzUzfQ==", + "message_id": "4KR6LpgzziWI8bxA2MFwyvcD7VY20-sh1fWuIY6M_Uk=", + "sender": "nAHrfl9nLW9OO_sRr1q9mkfdA_S_ig5156aAMykzAu0=", + "signature": "7zMIKJ1-QxVDDRSvbXuqMU2aRhi4ojH0EFo1BrmVM1f-is4V7ozhm4hMkHGJexOZnrK9GG4fyKNnoLgnCEFKAA==", + "witness_signatures": [] + } + ], + "/root/o3nyIIvrmDriwYXnH5-8CmYdyZcz4BPNT99W7dcrk9M=/social/reactions": [ + { + "data": "eyJvYmplY3QiOiJyZWFjdGlvbiIsImFjdGlvbiI6ImFkZCIsInJlYWN0aW9uX2NvZGVwb2ludCI6IvCfkY4iLCJjaGlycF9pZCI6IktyVlVMNEVXY0xzdVcxNzE5RW16SkpFZ29samtEM3Z1Nm5rN2xISEJ4eEE9IiwidGltZXN0YW1wIjoxNjgzMzE2MzU2fQ==", + "message_id": "JQjFMIR63dUEQnAkr7z7DPRMHrPQ1xA1nEJkxcy8VBc=", + "sender": "VHfxTlbM3nTnLQuKnKfs1fGP2cwVT8KJkc-sRGs_2KM=", + "signature": "gZCLvRDXlNK9xwjcUrTH2OiXupXT6iWF_ZH7cJJI5U9jLEZTFErDBBTijVKmfiqqFPSJm1P7pXpW_V82cmJJCA==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJyZWFjdGlvbiIsImFjdGlvbiI6ImFkZCIsInJlYWN0aW9uX2NvZGVwb2ludCI6IvCfkY4iLCJjaGlycF9pZCI6IjRLUjZMcGd6emlXSThieEEyTUZ3eXZjRDdWWTIwLXNoMWZXdUlZNk1fVWs9IiwidGltZXN0YW1wIjoxNjgzMzE2MzU3fQ==", + "message_id": "NIafSZHwoaYstNPS8SaNSowfMfaZxVmSdCWGfU7g08s=", + "sender": "VHfxTlbM3nTnLQuKnKfs1fGP2cwVT8KJkc-sRGs_2KM=", + "signature": "JAmx7llCWFnXVel89QxVbKXN-uEfP2eC11X-osvzKu16wT8qBI5k6O8S5HhqRaa5oPvzZTXJ537avKcfjy-LAw==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJyZWFjdGlvbiIsImFjdGlvbiI6ImFkZCIsInJlYWN0aW9uX2NvZGVwb2ludCI6IvCfkY0iLCJjaGlycF9pZCI6IjRLUjZMcGd6emlXSThieEEyTUZ3eXZjRDdWWTIwLXNoMWZXdUlZNk1fVWs9IiwidGltZXN0YW1wIjoxNjgzMzE2MzU4fQ==", + "message_id": "DH6FHm-x8PlQbTvav5kdWpK0gMApFJrGpCDtcI4s5JU=", + "sender": "nAHrfl9nLW9OO_sRr1q9mkfdA_S_ig5156aAMykzAu0=", + "signature": "6KEv9VfzNHnCQgG_gXWN5d8BJYaU4x9tYoYfJ8KQhezmKc2Bd7Qk330n9o-eA8ciWuPEyxoKUM2bO0YvSOrEBA==", + "witness_signatures": [] + }, + { + "data": "eyJvYmplY3QiOiJyZWFjdGlvbiIsImFjdGlvbiI6ImFkZCIsInJlYWN0aW9uX2NvZGVwb2ludCI6IvCfkY0iLCJjaGlycF9pZCI6IktyVlVMNEVXY0xzdVcxNzE5RW16SkpFZ29samtEM3Z1Nm5rN2xISEJ4eEE9IiwidGltZXN0YW1wIjoxNjgzMzE2MzU5fQ==", + "message_id": "STC4LM2BeQMpWKLT4KlBMME00xvtMXcGxAyID_6zcos=", + "sender": "nAHrfl9nLW9OO_sRr1q9mkfdA_S_ig5156aAMykzAu0=", + "signature": "In663OLt7aQpi0UNOVb-535opm_jYo2GEch9gmppQmbJWRLoOVomsBcZefqV3c_kED6VXfgFa67__6FkbP4IBQ==", + "witness_signatures": [] + } + ] + } + } +} From f9319b6af568420127c5232db1ab136050f00b8c Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Mon, 6 May 2024 18:55:16 +0200 Subject: [PATCH 15/22] corrected inconsistent tests --- .../pop/decentralized/GossipManager.scala | 6 +-- .../pubsub/graph/handlers/ParamsHandler.scala | 6 +-- .../graph/handlers/RumorHandlerSuite.scala | 53 ++++++++----------- 3 files changed, 27 insertions(+), 38 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index 8f0c3ad906..2aa8900952 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -12,7 +12,7 @@ import ch.epfl.pop.pubsub.AskPatternConstants import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} import ch.epfl.pop.storage.DbActor -import ch.epfl.pop.storage.DbActor.DbActorReadRumors +import ch.epfl.pop.storage.DbActor.DbActorReadRumor import scala.concurrent.Await import scala.util.Random @@ -32,9 +32,9 @@ final case class GossipManager( private var activeGossipProtocol: Map[JsonRpcRequest, List[ServerInfos]] = Map.empty private def isRumorNew(rumor: Rumor): Boolean = { - val readRumorDb = dbActorRef ? DbActor.ReadRumors(rumor.senderPk -> rumor.rumorId) + val readRumorDb = dbActorRef ? DbActor.ReadRumor(rumor.senderPk -> rumor.rumorId) Await.result(readRumorDb, duration) match - case DbActorReadRumors(foundRumors) => + case DbActorReadRumor(foundRumors) => foundRumors match case Some(_) => false case None => true diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala index b024398877..942b88e19a 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ParamsHandler.scala @@ -13,7 +13,7 @@ import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.validators.RpcValidator import ch.epfl.pop.pubsub.graph.{ErrorCodes, GraphMessage, PipelineError} import ch.epfl.pop.pubsub.{AskPatternConstants, ClientActor, MessageRegistry, PubSubMediator} -import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors, WriteRumor} +import ch.epfl.pop.storage.DbActor.{DbActorReadRumor, ReadRumor, WriteRumor} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.{Await, Future} @@ -99,10 +99,10 @@ object ParamsHandler extends AskPatternConstants { val messages: Map[Channel, List[Message]] = rumor.messages // check if rumor already received - val readRumorDb = dbActorRef ? ReadRumors(senderPk -> rumorId) + val readRumorDb = dbActorRef ? ReadRumor(senderPk -> rumorId) Await.result(readRumorDb, duration) match { // already present - case DbActorReadRumors(foundRumor) => + case DbActorReadRumor(foundRumor) => foundRumor match case Some(_) => Right(JsonRpcResponse( diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 9127c4028f..8c704b0406 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -18,7 +18,7 @@ import ch.epfl.pop.model.network.method.{GreetServer, Rumor} import ch.epfl.pop.model.objects.{Base64Data, Channel, PublicKey} import ch.epfl.pop.pubsub.ClientActor.ClientAnswer import ch.epfl.pop.pubsub.graph.GraphMessage -import ch.epfl.pop.storage.DbActor.{DbActorReadRumors, ReadRumors} +import ch.epfl.pop.storage.DbActor.{DbActorReadRumor, ReadRumor} import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} import org.scalatest.funsuite.AnyFunSuiteLike import org.scalatest.matchers.should.Matchers @@ -31,32 +31,28 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" val MAX_TIME: FiniteDuration = duration.mul(2) - private var inMemoryStorage: InMemoryStorage = _ - private var messageRegistry: MessageRegistry = _ - private var pubSubMediatorRef: ActorRef = _ - private var dbActorRef: AskableActorRef = _ - private var securityModuleActorRef: AskableActorRef = _ - private var monitorRef: ActorRef = _ - private var connectionMediatorRef: AskableActorRef = _ - private var rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = _ - private var processDuration: FiniteDuration = _ + private val inMemoryStorage: InMemoryStorage = InMemoryStorage() + private val messageRegistry: MessageRegistry = MessageRegistry() + private val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props) + private val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage))) + private val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) + private val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef)) + private var connectionMediatorRef: AskableActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + private val rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = ParamsHandler.rumorHandler(dbActorRef, messageRegistry) + // Inject dbActor above + PublishSubscribe.buildGraph(pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry, ActorRef.noSender, ActorRef.noSender, ActorRef.noSender, isServer = false) - override def beforeEach(): Unit = { - inMemoryStorage = InMemoryStorage() - messageRegistry = MessageRegistry() - pubSubMediatorRef = system.actorOf(PubSubMediator.props) - dbActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage))) - securityModuleActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) - monitorRef = system.actorOf(Monitor.props(dbActorRef)) - connectionMediatorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor_correct_msg.json" - // Inject dbActor above - PublishSubscribe.buildGraph(pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry, ActorRef.noSender, ActorRef.noSender, ActorRef.noSender, isServer = false) + val rumorRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath(pathCorrectRumor)) - val nbMessaages = rumor.messages.values.foldLeft(0)((acc, msgs) => acc + msgs.size) - processDuration = duration.mul(nbMessaages) + val rumor: Rumor = rumorRequest.getParams.asInstanceOf[Rumor] - rumorHandler = ParamsHandler.rumorHandler(dbActorRef, messageRegistry) + private val nbMessages = rumor.messages.values.foldLeft(0)((acc, msgs) => acc + msgs.size) + private var processDuration: FiniteDuration = duration.mul(nbMessages) + + override def beforeEach(): Unit = { + inMemoryStorage.elements = Map.empty } // Helper function @@ -70,12 +66,6 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" } } - val pathCorrectRumor: String = "src/test/scala/util/examples/json/rumor/rumor_correct_msg.json" - - val rumorRequest: JsonRpcRequest = JsonRpcRequest.buildFromJson(readJsonFromPath(pathCorrectRumor)) - - val rumor: Rumor = rumorRequest.getParams.asInstanceOf[Rumor] - override def afterAll(): Unit = { // Stops the testKit TestKit.shutdownActorSystem(system) @@ -86,8 +76,8 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" Await.result(output, processDuration) - val readRumor = dbActorRef ? ReadRumors(rumor.senderPk -> rumor.rumorId) - Await.result(readRumor, duration) shouldBe a[DbActorReadRumors] + val readRumor = dbActorRef ? ReadRumor(rumor.senderPk -> rumor.rumorId) + Await.result(readRumor, duration) shouldBe DbActorReadRumor(Some(rumor)) } test("rumor handler should handle correct rumors without error") { @@ -135,7 +125,6 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" } test("rumor handler should process messages received in a rumor") { - val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) val outputResult = Await.result(output, processDuration) From 7f87a0f8ee90f0c3d1c4077b27f861838a3c46dd Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 7 May 2024 14:09:15 +0200 Subject: [PATCH 16/22] removed processing of rumors, only have interface for now --- .../handlers/ProcessMessagesHandler.scala | 3 +- .../graph/handlers/RumorHandlerSuite.scala | 31 ++++--------------- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ProcessMessagesHandler.scala b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ProcessMessagesHandler.scala index 66544d4c37..a6c5e446ba 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ProcessMessagesHandler.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/pubsub/graph/handlers/ProcessMessagesHandler.scala @@ -50,8 +50,7 @@ object ProcessMessagesHandler extends AskPatternConstants { } def rumorHandler(messageRegistry: MessageRegistry, rumor: Rumor)(implicit system: ActorSystem): Boolean = { - val msgMap = rumor.messages.map((k, v) => (k, v.toSet)) - processMsgMap(msgMap, messageRegistry) + true } private def processMsgMap(msgMap: Map[Channel, Set[Message]], messageRegistry: MessageRegistry)(implicit system: ActorSystem): Boolean = { diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index 8c704b0406..c2b67c3379 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -33,11 +33,11 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" private val inMemoryStorage: InMemoryStorage = InMemoryStorage() private val messageRegistry: MessageRegistry = MessageRegistry() - private val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props) - private val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage))) - private val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath))) - private val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef)) - private var connectionMediatorRef: AskableActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry)) + private val pubSubMediatorRef: ActorRef = system.actorOf(PubSubMediator.props, "pubSubRumor") + private val dbActorRef: AskableActorRef = system.actorOf(Props(DbActor(pubSubMediatorRef, messageRegistry, inMemoryStorage)), "dbRumor") + private val securityModuleActorRef: AskableActorRef = system.actorOf(Props(SecurityModuleActor(RuntimeEnvironment.securityPath)), "securityRumor") + private val monitorRef: ActorRef = system.actorOf(Monitor.props(dbActorRef), "monitorRumor") + private var connectionMediatorRef: AskableActorRef = system.actorOf(ConnectionMediator.props(monitorRef, pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry), "connMediatorRumor") private val rumorHandler: Flow[GraphMessage, GraphMessage, NotUsed] = ParamsHandler.rumorHandler(dbActorRef, messageRegistry) // Inject dbActor above PublishSubscribe.buildGraph(pubSubMediatorRef, dbActorRef, securityModuleActorRef, messageRegistry, ActorRef.noSender, ActorRef.noSender, ActorRef.noSender, isServer = false) @@ -123,24 +123,5 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" case Right(jsonRpcResponse: JsonRpcResponse) => jsonRpcResponse.error.isDefined shouldBe true case _ => 1 shouldBe 0 } - - test("rumor handler should process messages received in a rumor") { - val output = Source.single(Right(rumorRequest)).via(rumorHandler).runWith(Sink.head) - - val outputResult = Await.result(output, processDuration) - - val ask = dbActorRef ? DbActor.GetAllChannels() - val channelsInDb = Await.result(ask, MAX_TIME) match { - case DbActor.DbActorGetAllChannelsAck(channels) => channels - case err @ _ => Matchers.fail(err.toString) - } - - val channelsInRumor = rumor.messages.keySet - channelsInRumor.diff(channelsInDb) should equal(Set.empty) - - val messagesInDb: Set[Message] = channelsInDb.foldLeft(Set.empty: Set[Message])((acc, channel) => acc ++ getMessages(channel)) - val messagesInRumor = rumor.messages.values.foldLeft(Set.empty: Set[Message])((acc, set) => acc ++ set) - - messagesInRumor.diff(messagesInDb) should equal(Set.empty) - } + } From 9ecd16e87434f5b465b3d157203c168ea5aec56d Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 7 May 2024 14:13:59 +0200 Subject: [PATCH 17/22] scalaFmt --- .../ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala index c2b67c3379..23b85e06eb 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/pubsub/graph/handlers/RumorHandlerSuite.scala @@ -123,5 +123,5 @@ class RumorHandlerSuite extends TestKit(ActorSystem("RumorActorSuiteActorSystem" case Right(jsonRpcResponse: JsonRpcResponse) => jsonRpcResponse.error.isDefined shouldBe true case _ => 1 shouldBe 0 } - + } From dda3f50f71879f25a8d58114548a2afb64bf10a9 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Sun, 12 May 2024 11:37:55 +0200 Subject: [PATCH 18/22] addressed comments --- .../decentralized/ConnectionMediator.scala | 2 +- .../pop/decentralized/GossipManager.scala | 22 ++++++++++--------- .../decentralized/GossipManagerSuite.scala | 14 +++++------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala index a052222952..f75e2c411a 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/ConnectionMediator.scala @@ -85,7 +85,7 @@ final case class ConnectionMediator( case ConnectionMediator.GetRandomPeer(excludes) => if (serverMap.isEmpty) - sender() ! ConnectionMediator.NoPeer + sender() ! ConnectionMediator.NoPeer() else val serverRefs = serverMap.filter((k, _) => !excludes.contains(k)) val randomKey = serverRefs.keys.toList(Random.nextInt(serverRefs.size)) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index 2aa8900952..9d011a8c9d 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -1,7 +1,8 @@ package ch.epfl.pop.decentralized import akka.NotUsed -import akka.actor.{Actor, ActorRef, Props} +import akka.actor.{Actor, ActorLogging, ActorRef, Props} +import akka.event.slf4j.Logger import akka.pattern.AskableActorRef import akka.stream.scaladsl.Flow import ch.epfl.pop.decentralized.GossipManager.MonitoredRumor @@ -22,12 +23,12 @@ final case class GossipManager( monitorRef: ActorRef, connectionMediator: AskableActorRef, stopProbability: Double = 0.5 -) extends Actor with AskPatternConstants { +) extends Actor with AskPatternConstants with ActorLogging { private type ServerInfos = (ActorRef, GreetServer) monitorRef ! GossipManager.Ping() - connectionMediator ? ConnectionMediator.Ping() + connectionMediator ? GossipManager.Ping() private var activeGossipProtocol: Map[JsonRpcRequest, List[ServerInfos]] = Map.empty @@ -35,9 +36,7 @@ final case class GossipManager( val readRumorDb = dbActorRef ? DbActor.ReadRumor(rumor.senderPk -> rumor.rumorId) Await.result(readRumorDb, duration) match case DbActorReadRumor(foundRumors) => - foundRumors match - case Some(_) => false - case None => true + foundRumors.isEmpty } private def getPeersForRumor(jsonRpcRequest: JsonRpcRequest): List[ServerInfos] = { @@ -64,11 +63,14 @@ final case class GossipManager( // else remove entry case ConnectionMediator.NoPeer => activeGossipProtocol = activeGossipProtocol.removed(rumorRpc) + case _ => + log.info(s"Actor $self received an unexpected message waiting for a random peer") } } private def processResponse(response: JsonRpcResponse): Unit = { val activeGossipPeers = activeGossipProtocol.filter((k, _) => k.id == response.id) + // response is expected because only one entry exists if (activeGossipPeers.size == 1) { activeGossipPeers.foreach { (rumorRpc, _) => @@ -78,14 +80,14 @@ final case class GossipManager( sendRumorToRandomPeer(rumorRpc) } } - } + } else if (activeGossipPeers.size > 1) {} } override def receive: Receive = { case GossipManager.SendRumorToRandomPeer(rumorRpc) => sendRumorToRandomPeer(rumorRpc) - case GossipManager.ProcessResponse(jsonRpcResponse) => + case GossipManager.ManageGossipResponse(jsonRpcResponse) => processResponse(jsonRpcResponse) } @@ -107,7 +109,7 @@ object GossipManager extends AskPatternConstants { def monitorResponse(gossipManager: AskableActorRef): Flow[GraphMessage, GraphMessage, NotUsed] = Flow[GraphMessage].map { case Right(jsonRpcResponse: JsonRpcResponse) => - gossipManager ? ProcessResponse(jsonRpcResponse) + gossipManager ? ManageGossipResponse(jsonRpcResponse) Right(jsonRpcResponse) case graphMessage @ _ => graphMessage } @@ -115,7 +117,7 @@ object GossipManager extends AskPatternConstants { sealed trait Event final case class MonitoredRumor(jsonRpcRumor: JsonRpcRequest) final case class SendRumorToRandomPeer(jsonRpcRequest: JsonRpcRequest) - final case class ProcessResponse(jsonRpcResponse: JsonRpcResponse) + final case class ManageGossipResponse(jsonRpcResponse: JsonRpcResponse) sealed trait GossipManagerMessage final case class Ping() extends GossipManagerMessage diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index a82a2fe186..7f3d58c3f5 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -78,10 +78,9 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys val peers = List(peerServer1, peerServer2, peerServer3, peerServer4) // register server - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer1.ref, GreetServer(PublicKey(Base64Data("")), "", "")) - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer2.ref, GreetServer(PublicKey(Base64Data("")), "", "")) - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer3.ref, GreetServer(PublicKey(Base64Data("")), "", "")) - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer4.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + for (peer <- peers) { + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peer.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + } val output = Source.single(Right(rumorRequest)).via(gossipHandler).runWith(Sink.head) @@ -104,10 +103,9 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys val peers = List(peerServer1, peerServer2, peerServer3, peerServer4) // register server - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer1.ref, GreetServer(PublicKey(Base64Data("")), "", "")) - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer2.ref, GreetServer(PublicKey(Base64Data("")), "", "")) - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer3.ref, GreetServer(PublicKey(Base64Data("")), "", "")) - connectionMediatorRef ? ConnectionMediator.NewServerConnected(peerServer4.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + for (peer <- peers) { + connectionMediatorRef ? ConnectionMediator.NewServerConnected(peer.ref, GreetServer(PublicKey(Base64Data("")), "", "")) + } val outputRumor = Source.single(Right(rumorRequest)).via(gossipHandler).runWith(Sink.head) From 2691bcfdfdcb2df946274432f592ff9be47b67a2 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Mon, 13 May 2024 13:35:42 +0200 Subject: [PATCH 19/22] added unmatched cases --- .../scala/ch/epfl/pop/decentralized/GossipManager.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index 9d011a8c9d..70f4106ed6 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -80,7 +80,9 @@ final case class GossipManager( sendRumorToRandomPeer(rumorRpc) } } - } else if (activeGossipPeers.size > 1) {} + } else { + log.info(s"Unexpected match for active gossip. Response with id ${response.id} matched with ${activeGossipPeers.size} entries") + } } override def receive: Receive = { @@ -89,6 +91,9 @@ final case class GossipManager( case GossipManager.ManageGossipResponse(jsonRpcResponse) => processResponse(jsonRpcResponse) + + case _ => + log.info(s"Actor $self received an unexpected message") } } From e50cae552b2631a750f9a3675c1289123cf36928 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 14 May 2024 11:27:50 +0200 Subject: [PATCH 20/22] remove if duplicate --- .../main/scala/ch/epfl/pop/decentralized/GossipManager.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index 70f4106ed6..e054d9bcd1 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -82,6 +82,10 @@ final case class GossipManager( } } else { log.info(s"Unexpected match for active gossip. Response with id ${response.id} matched with ${activeGossipPeers.size} entries") + //removes duplicate entries to come back to a stable state + activeGossipPeers.foreach{ (rumorRpc, _) => + activeGossipProtocol -= rumorRpc + } } } From e3e7252322f52adc2188ad31dc1e90bd3937c2b0 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 14 May 2024 15:43:00 +0200 Subject: [PATCH 21/22] add comments --- .../ch/epfl/pop/decentralized/GossipManagerSuite.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index 7f3d58c3f5..34b6943450 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -107,26 +107,26 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys connectionMediatorRef ? ConnectionMediator.NewServerConnected(peer.ref, GreetServer(PublicKey(Base64Data("")), "", "")) } + // processes the rumor => sends to random peer val outputRumor = Source.single(Right(rumorRequest)).via(gossipHandler).runWith(Sink.head) Await.result(outputRumor, duration) + //checks that only one peers received the rumor val received = peers.map(_.receiveOne(duration)) - received.count(_ != null) shouldBe 1 - val remainingPeers = peers.lazyZip(received).filter((_, recv) => recv == null).map(_._1) - remainingPeers.size shouldBe peers.size - 1 + // sends back to the gossipManager a response that the rumor is new val response = Right(JsonRpcResponse( RpcValidator.JSON_RPC_VERSION, ResultObject(0), rumorRequest.id )) + // by processing the reponse, gossipManager should send again a rumor to a new peer val outputResponse = Source.single(response).via(gossipMonitor).runWith(Sink.head) - Await.result(outputResponse, duration) remainingPeers.map(_.receiveOne(duration)).count(_ != null) shouldBe 1 From 43639b2ff7d7fa62fdca10765f0c0a0d63bed8d8 Mon Sep 17 00:00:00 2001 From: Daniel Tavares Agostinho Date: Tue, 14 May 2024 15:51:51 +0200 Subject: [PATCH 22/22] scalafmt --- .../main/scala/ch/epfl/pop/decentralized/GossipManager.scala | 4 ++-- .../scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala index e054d9bcd1..ac8f47e252 100644 --- a/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala +++ b/be2-scala/src/main/scala/ch/epfl/pop/decentralized/GossipManager.scala @@ -82,8 +82,8 @@ final case class GossipManager( } } else { log.info(s"Unexpected match for active gossip. Response with id ${response.id} matched with ${activeGossipPeers.size} entries") - //removes duplicate entries to come back to a stable state - activeGossipPeers.foreach{ (rumorRpc, _) => + // removes duplicate entries to come back to a stable state + activeGossipPeers.foreach { (rumorRpc, _) => activeGossipProtocol -= rumorRpc } } diff --git a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala index 34b6943450..8396431d7a 100644 --- a/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala +++ b/be2-scala/src/test/scala/ch/epfl/pop/decentralized/GossipManagerSuite.scala @@ -112,7 +112,7 @@ class GossipManagerSuite extends TestKit(ActorSystem("GossipManagerSuiteActorSys Await.result(outputRumor, duration) - //checks that only one peers received the rumor + // checks that only one peers received the rumor val received = peers.map(_.receiveOne(duration)) received.count(_ != null) shouldBe 1 val remainingPeers = peers.lazyZip(received).filter((_, recv) => recv == null).map(_._1)