mirror of
https://github.com/fluencelabs/cosmos-hackathon-backend
synced 2025-05-03 10:52:16 +00:00
it works
This commit is contained in:
parent
5ea70dfe32
commit
d5caf69dfd
@ -4,10 +4,12 @@ import java.nio.ByteBuffer
|
|||||||
import java.nio.file.{Files, Path, Paths}
|
import java.nio.file.{Files, Path, Paths}
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
import cats.Monad
|
import cats.{Monad, Traverse}
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.syntax.functor._
|
import cats.syntax.functor._
|
||||||
|
import cats.syntax.applicative._
|
||||||
|
import cats.instances.list._
|
||||||
import cats.syntax.flatMap._
|
import cats.syntax.flatMap._
|
||||||
import cats.syntax.applicative._
|
import cats.syntax.applicative._
|
||||||
import cats.effect.concurrent.{Deferred, Ref}
|
import cats.effect.concurrent.{Deferred, Ref}
|
||||||
@ -45,6 +47,32 @@ class AppRegistry[F[_]: Monad: Concurrent: ContextShift: Timer: LiftIO](
|
|||||||
implicit sttpBackend: SttpBackend[EitherT[F, Throwable, ?],
|
implicit sttpBackend: SttpBackend[EitherT[F, Throwable, ?],
|
||||||
fs2.Stream[F, ByteBuffer]]) {
|
fs2.Stream[F, ByteBuffer]]) {
|
||||||
|
|
||||||
|
private def putApp(app: App) =
|
||||||
|
for {
|
||||||
|
d <- Deferred[F, App]
|
||||||
|
_ <- d.complete(app)
|
||||||
|
_ <- apps.update(
|
||||||
|
map =>
|
||||||
|
map
|
||||||
|
.get(app.name)
|
||||||
|
.fold(map.updated(app.name, d))(_ => map))
|
||||||
|
} yield ()
|
||||||
|
|
||||||
|
def loadExistingContainers(): EitherT[F, Throwable, Unit] = {
|
||||||
|
(for {
|
||||||
|
existingApps <- runner.listFishermenContainers
|
||||||
|
_ = existingApps.foreach(a => println(s"Existing app: $a"))
|
||||||
|
_ <- EitherT.liftF[F, Throwable, Unit] {
|
||||||
|
Traverse[List]
|
||||||
|
.sequence(existingApps.map(putApp))
|
||||||
|
.void
|
||||||
|
}
|
||||||
|
} yield ()).leftMap { e =>
|
||||||
|
println(s"Error loadExistingContainers: $e")
|
||||||
|
new Exception(s"Error on loading existing containers: $e", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def stream(name: String): EitherT[F, Throwable, fs2.Stream[F, Log]] =
|
def stream(name: String): EitherT[F, Throwable, fs2.Stream[F, Log]] =
|
||||||
for {
|
for {
|
||||||
app <- EitherT(getApp(name))
|
app <- EitherT(getApp(name))
|
||||||
@ -80,12 +108,14 @@ class AppRegistry[F[_]: Monad: Concurrent: ContextShift: Timer: LiftIO](
|
|||||||
.toAbsolutePath).attempt.to[F])
|
.toAbsolutePath).attempt.to[F])
|
||||||
_ <- EitherT(IO(Files.createDirectories(baseDir)).attempt.to[F])
|
_ <- EitherT(IO(Files.createDirectories(baseDir)).attempt.to[F])
|
||||||
genesisPath = baseDir.resolve("genesis.json")
|
genesisPath = baseDir.resolve("genesis.json")
|
||||||
_ <- EitherT(IO(Files.write(genesisPath, genesis.getBytes())).attempt.to[F])
|
_ <- EitherT(
|
||||||
|
IO(Files.write(genesisPath, genesis.getBytes())).attempt.to[F])
|
||||||
_ <- log(s"$name saved genesis -> $genesisPath")
|
_ <- log(s"$name saved genesis -> $genesisPath")
|
||||||
|
|
||||||
binaryHash <- EitherT.fromEither(
|
binaryHash <- EitherT.fromEither[F](
|
||||||
ByteVector
|
ByteVector
|
||||||
.fromBase58Descriptive(hash).map(_.drop(2))
|
.fromBase58Descriptive(hash)
|
||||||
|
.map(_.drop(2))
|
||||||
.leftMap(e =>
|
.leftMap(e =>
|
||||||
new Exception(s"Failed to decode binary hash from base64: $e"): Throwable))
|
new Exception(s"Failed to decode binary hash from base64: $e"): Throwable))
|
||||||
|
|
||||||
@ -96,7 +126,12 @@ class AppRegistry[F[_]: Monad: Concurrent: ContextShift: Timer: LiftIO](
|
|||||||
status <- status(name, peer)
|
status <- status(name, peer)
|
||||||
_ <- log(s"$name got peer status")
|
_ <- log(s"$name got peer status")
|
||||||
|
|
||||||
containerId <- runner.run(name, p2pPeer(peer, status), binaryPath, genesisPath)
|
containerId <- runner.run(name,
|
||||||
|
p2pPeer(peer, status),
|
||||||
|
peer.rpcPort,
|
||||||
|
binaryHash,
|
||||||
|
binaryPath,
|
||||||
|
genesisPath)
|
||||||
_ <- log(s"$name container started $containerId")
|
_ <- log(s"$name container started $containerId")
|
||||||
|
|
||||||
app = App(name, containerId, peer, binaryHash, binaryPath)
|
app = App(name, containerId, peer, binaryHash, binaryPath)
|
||||||
@ -125,7 +160,8 @@ class AppRegistry[F[_]: Monad: Concurrent: ContextShift: Timer: LiftIO](
|
|||||||
|
|
||||||
private def p2pPeer(peer: Peer, status: TendermintStatus) = {
|
private def p2pPeer(peer: Peer, status: TendermintStatus) = {
|
||||||
val id = status.node_info.id
|
val id = status.node_info.id
|
||||||
val port = status.node_info.listen_addr.replace("tcp://", "").split(":").tail.head
|
val port =
|
||||||
|
status.node_info.listen_addr.replace("tcp://", "").split(":").tail.head
|
||||||
s"$id@${peer.host}:$port"
|
s"$id@${peer.host}:$port"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,18 +16,23 @@ object Log {
|
|||||||
/*
|
/*
|
||||||
I[2019-06-13|01:07:31.417] Committed state module=state height=7096 txs=1 appHash=4A936D7C00A37D66C7CE38A1118E0BC31C97BEE68C70BA8A9365EE0126079DAE
|
I[2019-06-13|01:07:31.417] Committed state module=state height=7096 txs=1 appHash=4A936D7C00A37D66C7CE38A1118E0BC31C97BEE68C70BA8A9365EE0126079DAE
|
||||||
E[2019-06-13|01:07:33.678] CONSENSUS FAILURE!!! module=consensus err="Panicked on a Consensus Failure: +2/3 committed an invalid block: Wrong Block.Header.AppHash. Expected 4A936D7C00A37D66C7CE38A1118E0BC31C97BEE68C70BA8A9365EE0126079DAE, got 0113492DE4944A90AB6AB3B64D04EAB2D9257D8613FABCACB3CB341CAF79D490"
|
E[2019-06-13|01:07:33.678] CONSENSUS FAILURE!!! module=consensus err="Panicked on a Consensus Failure: +2/3 committed an invalid block: Wrong Block.Header.AppHash. Expected 4A936D7C00A37D66C7CE38A1118E0BC31C97BEE68C70BA8A9365EE0126079DAE, got 0113492DE4944A90AB6AB3B64D04EAB2D9257D8613FABCACB3CB341CAF79D490"
|
||||||
|
panic: Failed to process committed block (7078:EF9EDA6F850812E04B8DB99BA70351084A613F3E3C30CCEB0C9913C736AE789D): Wrong Block.Header.AppHash. Expected 193246169176A238ADB9BD29130879B3B105B62C1B0DA7AC8D3E420FD1C3D959, got AEACE20159424C49BDD1FE769278D78CE8B412F04A34099ADF0016D5D7636CB4
|
||||||
*/
|
*/
|
||||||
|
|
||||||
private val commitRegex =
|
private val commitRegex =
|
||||||
"""(?s).*?Committed state\s+module=state height=(\d+) txs=\d+ appHash=(\S+).*""".r
|
"""(?s).*?Committed state\s+module=state height=(\d+) txs=\d+ appHash=(\S+).*""".r
|
||||||
private val consensusFailedRegex =
|
private val consensusFailedRegex =
|
||||||
"""(?s).*?CONSENSUS FAILURE.*?Expected ([A-Z0-9]+), got ([A-Z0-9]+).*""".r
|
"""(?s).*?CONSENSUS FAILURE.*?Expected ([A-Z0-9]+), got ([A-Z0-9]+).*""".r
|
||||||
|
private val syncAppHashFailedRegex =
|
||||||
|
"""(?s).*Failed to process committed block \((\d+):.*?Expected (\S+), got (\S+)""".r
|
||||||
|
|
||||||
def apply(appName: String, line: String): Option[Log] = line match {
|
def apply(appName: String, line: String): Option[Log] = line match {
|
||||||
case commitRegex(height, hash) =>
|
case commitRegex(height, hash) =>
|
||||||
new Log(appName, Try(height.toLong).toOption, hash, None, true).some
|
new Log(appName, Try(height.toLong).toOption, hash, None, true).some
|
||||||
case consensusFailedRegex(expected, got) =>
|
case consensusFailedRegex(expected, got) =>
|
||||||
new Log(appName, None, got, Some(expected), false).some
|
new Log(appName, None, got, expected.some, false).some
|
||||||
|
case syncAppHashFailedRegex(height, expected, got) =>
|
||||||
|
new Log(appName, height.toLong.some, got, Some(expected), false).some
|
||||||
case str =>
|
case str =>
|
||||||
println(s"skipping $str")
|
println(s"skipping $str")
|
||||||
None
|
None
|
||||||
|
@ -33,6 +33,7 @@ object Main extends IOApp {
|
|||||||
for {
|
for {
|
||||||
runner <- Runner.make[IO]
|
runner <- Runner.make[IO]
|
||||||
appRegistry <- AppRegistry.make[IO](ipfsStore, runner)
|
appRegistry <- AppRegistry.make[IO](ipfsStore, runner)
|
||||||
|
_ <- appRegistry.loadExistingContainers().value.map(IO.fromEither)
|
||||||
_ <- WebsocketServer
|
_ <- WebsocketServer
|
||||||
.make[IO](8080, appRegistry)
|
.make[IO](8080, appRegistry)
|
||||||
.use {
|
.use {
|
||||||
|
@ -6,18 +6,21 @@ import java.util.concurrent.Executors
|
|||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.effect._
|
import cats.effect._
|
||||||
import cats.effect.concurrent.{Deferred, Ref}
|
import cats.effect.concurrent.{Deferred, Ref}
|
||||||
|
import cats.instances.list._
|
||||||
import cats.syntax.applicativeError._
|
import cats.syntax.applicativeError._
|
||||||
import cats.syntax.either._
|
import cats.syntax.either._
|
||||||
import cats.syntax.flatMap._
|
import cats.syntax.flatMap._
|
||||||
import cats.syntax.functor._
|
import cats.syntax.functor._
|
||||||
import cats.{Defer, Monad}
|
import cats.{Defer, Monad, Traverse}
|
||||||
import hackhack.docker.params.{DockerImage, DockerParams}
|
import hackhack.docker.params.{DockerImage, DockerParams}
|
||||||
import io.circe.Json
|
import io.circe.Json
|
||||||
import io.circe.parser.parse
|
import io.circe.parser.parse
|
||||||
|
import scodec.bits.ByteVector
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
import scala.language.{higherKinds, postfixOps}
|
import scala.language.{higherKinds, postfixOps}
|
||||||
import scala.sys.process._
|
import scala.sys.process._
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
||||||
lastPort: Ref[F, Short],
|
lastPort: Ref[F, Short],
|
||||||
@ -32,7 +35,12 @@ case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
|||||||
private def nextPortThousand =
|
private def nextPortThousand =
|
||||||
lastPort.modify(p => (p + 1 toShort, p * 1000 toShort))
|
lastPort.modify(p => (p + 1 toShort, p * 1000 toShort))
|
||||||
|
|
||||||
private def dockerCmd(name: String, peer: String, binaryPath: Path, genesisPath: Path) =
|
private def dockerCmd(name: String,
|
||||||
|
peer: String,
|
||||||
|
rpcPort: Short,
|
||||||
|
binaryHash: ByteVector,
|
||||||
|
binaryPath: Path,
|
||||||
|
genesisPath: Path) =
|
||||||
for {
|
for {
|
||||||
portThousand <- nextPortThousand
|
portThousand <- nextPortThousand
|
||||||
|
|
||||||
@ -41,9 +49,12 @@ case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
|||||||
.port(30656 + portThousand toShort, 26656)
|
.port(30656 + portThousand toShort, 26656)
|
||||||
.port(30657 + portThousand toShort, 26657)
|
.port(30657 + portThousand toShort, 26657)
|
||||||
.port(30658 + portThousand toShort, 26658)
|
.port(30658 + portThousand toShort, 26658)
|
||||||
.option("--name", name)
|
.option("--name", s"fisherman-$name")
|
||||||
.option("-e", s"PEER=$peer")
|
.option("-e", s"PEER=$peer")
|
||||||
|
.option("-e", s"RPCPORT=$rpcPort")
|
||||||
.option("-e", s"MONIKER=$name")
|
.option("-e", s"MONIKER=$name")
|
||||||
|
.option("-e", s"BINARY_HASH=${binaryHash.toHex}")
|
||||||
|
.option("-e", s"BINARY_PATH=${binaryPath.toAbsolutePath.toString}")
|
||||||
.volume(binaryPath.toAbsolutePath.toString, "/binary")
|
.volume(binaryPath.toAbsolutePath.toString, "/binary")
|
||||||
.volume(genesisPath.toAbsolutePath.toString, "/root/genesis.json")
|
.volume(genesisPath.toAbsolutePath.toString, "/root/genesis.json")
|
||||||
.prepared(DockerImage(ContainerImage, "ubuntu"))
|
.prepared(DockerImage(ContainerImage, "ubuntu"))
|
||||||
@ -52,7 +63,7 @@ case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
|||||||
process = cmd.process
|
process = cmd.process
|
||||||
} yield process
|
} yield process
|
||||||
|
|
||||||
private def getLogPath(containerId: String): EitherT[F, Throwable, Path] =
|
private def inspect(containerId: String): EitherT[F, Throwable, Json] =
|
||||||
EitherT(IO(s"docker inspect $containerId".!!).attempt.to[F])
|
EitherT(IO(s"docker inspect $containerId".!!).attempt.to[F])
|
||||||
.subflatMap { inspect =>
|
.subflatMap { inspect =>
|
||||||
parse(inspect)
|
parse(inspect)
|
||||||
@ -65,6 +76,9 @@ case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
|||||||
.asLeft[Json]
|
.asLeft[Json]
|
||||||
)(_.asRight)
|
)(_.asRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def getLogPath(containerId: String): EitherT[F, Throwable, Path] =
|
||||||
|
inspect(containerId)
|
||||||
.subflatMap { json =>
|
.subflatMap { json =>
|
||||||
json.hcursor.get[String]("LogPath")
|
json.hcursor.get[String]("LogPath")
|
||||||
}
|
}
|
||||||
@ -72,14 +86,60 @@ case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
|||||||
EitherT(IO(Paths.get(p)).attempt.to[F])
|
EitherT(IO(Paths.get(p)).attempt.to[F])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def listFishermenContainers: EitherT[F, Throwable, List[App]] =
|
||||||
|
for {
|
||||||
|
ps <- EitherT(
|
||||||
|
IO(
|
||||||
|
Seq("docker",
|
||||||
|
"ps",
|
||||||
|
"-a",
|
||||||
|
"-f",
|
||||||
|
"name=fisherman",
|
||||||
|
"--format",
|
||||||
|
"{{.Names}} {{.ID}}").!!).attempt.to[F])
|
||||||
|
(names, ids) = ps
|
||||||
|
.split("\n")
|
||||||
|
.filter(_.nonEmpty)
|
||||||
|
.toList
|
||||||
|
.map(_.split(" "))
|
||||||
|
.map(a => a.head -> a.last)
|
||||||
|
.unzip
|
||||||
|
inspects <- Traverse[List].sequence(ids.map(inspect))
|
||||||
|
envs <- Traverse[List]
|
||||||
|
.sequence(inspects.map(
|
||||||
|
_.hcursor.downField("Config").get[List[String]]("Env").toEitherT[F]))
|
||||||
|
.leftMap(identity[Throwable])
|
||||||
|
|
||||||
|
envMap <- Try(
|
||||||
|
envs.map(_.map(_.split("=")).map(a => a.head -> a.last).toMap)).toEither
|
||||||
|
.toEitherT[F]
|
||||||
|
|
||||||
|
apps <- Try {
|
||||||
|
ids.zip(names).zip(envMap).map {
|
||||||
|
case ((id, name), env) =>
|
||||||
|
val peer = env
|
||||||
|
.get("PEER")
|
||||||
|
.map(_.split(Array('@', ':')))
|
||||||
|
.map(a => Peer(a(1), a(2).toShort))
|
||||||
|
.get
|
||||||
|
val binaryHash = ByteVector.fromValidHex(env("BINARY_HASH"))
|
||||||
|
val binaryPath = Paths.get(env("BINARY_PATH"))
|
||||||
|
val cleanName = name.replace("fisherman-", "")
|
||||||
|
App(cleanName, id, peer, binaryHash, binaryPath)
|
||||||
|
}
|
||||||
|
}.toEither.toEitherT[F]
|
||||||
|
} yield apps
|
||||||
|
|
||||||
private def log(str: String) = IO(println(str)).to[F]
|
private def log(str: String) = IO(println(str)).to[F]
|
||||||
|
|
||||||
def run(name: String,
|
def run(name: String,
|
||||||
peer: String,
|
peer: String,
|
||||||
|
rpcPort: Short,
|
||||||
|
binaryHash: ByteVector,
|
||||||
binaryPath: Path,
|
binaryPath: Path,
|
||||||
genesisPath: Path): EitherT[F, Throwable, String] = {
|
genesisPath: Path): EitherT[F, Throwable, String] = {
|
||||||
val containerId = for {
|
val containerId = for {
|
||||||
cmd <- dockerCmd(name, peer, binaryPath, genesisPath)
|
cmd <- dockerCmd(name, peer, rpcPort, binaryHash, binaryPath, genesisPath)
|
||||||
_ <- log(s"$name got dockerCmd")
|
_ <- log(s"$name got dockerCmd")
|
||||||
idPromise <- Deferred[F, Either[Throwable, String]]
|
idPromise <- Deferred[F, Either[Throwable, String]]
|
||||||
_ <- Concurrent[F].start(
|
_ <- Concurrent[F].start(
|
||||||
@ -108,13 +168,30 @@ case class Runner[F[_]: Monad: LiftIO: ContextShift: Defer: Concurrent](
|
|||||||
EitherT(containerId)
|
EitherT(containerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
def streamLog(
|
def streamFileLog(
|
||||||
containerId: String): EitherT[F, Throwable, fs2.Stream[F, String]] =
|
containerId: String): EitherT[F, Throwable, fs2.Stream[F, String]] =
|
||||||
for {
|
for {
|
||||||
logPath <- getLogPath(containerId)
|
logPath <- getLogPath(containerId)
|
||||||
_ = println(s"$containerId logPath: $logPath")
|
_ = println(s"$containerId logPath: $logPath")
|
||||||
stream = FileStream.stream[F](logPath)
|
stream = FileStream.stream[F](logPath)
|
||||||
} yield stream
|
} yield stream
|
||||||
|
|
||||||
|
def streamLog(
|
||||||
|
containerId: String): EitherT[F, Throwable, fs2.Stream[F, String]] = {
|
||||||
|
val errors = scala.collection.mutable.ArrayBuffer.empty[String]
|
||||||
|
for {
|
||||||
|
lines <- EitherT(
|
||||||
|
IO(s"docker logs -f $containerId".lineStream(new ProcessLogger {
|
||||||
|
override def out(s: => String): Unit = {}
|
||||||
|
|
||||||
|
override def err(s: => String): Unit = errors += s
|
||||||
|
|
||||||
|
override def buffer[T](f: => T): T = f
|
||||||
|
})).attempt.to[F])
|
||||||
|
stream = fs2.Stream.fromIterator(lines.iterator) ++ fs2.Stream.emits(
|
||||||
|
errors)
|
||||||
|
} yield stream
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object Runner {
|
object Runner {
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
package hackhack
|
package hackhack
|
||||||
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
import cats.effect.concurrent.Ref
|
import cats.effect.concurrent.Ref
|
||||||
import cats.effect.{ExitCode, Resource, _}
|
import cats.effect.{ExitCode, Resource, _}
|
||||||
import cats.syntax.applicativeError._
|
import cats.syntax.applicativeError._
|
||||||
import cats.syntax.flatMap._
|
import cats.syntax.flatMap._
|
||||||
import fs2.Stream
|
import fs2.Stream
|
||||||
import fs2.concurrent.SignallingRef
|
import fs2.concurrent.SignallingRef
|
||||||
import hackhack.ipfs.{IpfsClient, IpfsStore}
|
|
||||||
import io.circe.syntax._
|
import io.circe.syntax._
|
||||||
import org.http4s.HttpRoutes
|
import org.http4s.HttpRoutes
|
||||||
import org.http4s.dsl.Http4sDsl
|
import org.http4s.dsl.Http4sDsl
|
||||||
@ -17,7 +14,6 @@ import org.http4s.server.blaze.BlazeServerBuilder
|
|||||||
import org.http4s.server.middleware.{CORS, CORSConfig}
|
import org.http4s.server.middleware.{CORS, CORSConfig}
|
||||||
import org.http4s.server.websocket.WebSocketBuilder
|
import org.http4s.server.websocket.WebSocketBuilder
|
||||||
import org.http4s.websocket.WebSocketFrame.Text
|
import org.http4s.websocket.WebSocketFrame.Text
|
||||||
import io.circe.parser.parse
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.language.higherKinds
|
import scala.language.higherKinds
|
||||||
@ -32,6 +28,7 @@ case class WebsocketServer[F[_]: ConcurrentEffect: Timer: ContextShift](
|
|||||||
case GET -> Root / "websocket" / appName =>
|
case GET -> Root / "websocket" / appName =>
|
||||||
appRegistry.stream(appName).value.flatMap {
|
appRegistry.stream(appName).value.flatMap {
|
||||||
case Left(e) =>
|
case Left(e) =>
|
||||||
|
println(s"Error while getting stream for $appName: $e")
|
||||||
InternalServerError(s"Error while getting stream for $appName: $e")
|
InternalServerError(s"Error while getting stream for $appName: $e")
|
||||||
|
|
||||||
case Right(stream) =>
|
case Right(stream) =>
|
||||||
@ -48,6 +45,7 @@ case class WebsocketServer[F[_]: ConcurrentEffect: Timer: ContextShift](
|
|||||||
.value
|
.value
|
||||||
.flatMap {
|
.flatMap {
|
||||||
case Left(e) =>
|
case Left(e) =>
|
||||||
|
println(s"Error while running app $appName: $e")
|
||||||
InternalServerError(s"Error while running app $appName: $e")
|
InternalServerError(s"Error while running app $appName: $e")
|
||||||
|
|
||||||
case Right(height) =>
|
case Right(height) =>
|
||||||
@ -59,7 +57,6 @@ case class WebsocketServer[F[_]: ConcurrentEffect: Timer: ContextShift](
|
|||||||
|
|
||||||
}
|
}
|
||||||
// TODO: list of registered apps
|
// TODO: list of registered apps
|
||||||
// TODO: endpoint for consensusHeight
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def close(): F[Unit] = signal.set(true)
|
def close(): F[Unit] = signal.set(true)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user