Skip to content

Commit

Permalink
feat: add JsonCanonicalizer and use in serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
scasplte2 committed Feb 15, 2025
1 parent bde0f68 commit 1cf361e
Show file tree
Hide file tree
Showing 27 changed files with 1,240 additions and 142 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ jobs:
- name: Build and publish Tessellation artifacts locally
working-directory: tessellation
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: sbt publishLocal

- name: Build and publish Metakit artifacts to Maven
run: sbt ci-release
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
PGP_SECRET: ${{ secrets.PGP_SECRET }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,63 +7,52 @@ import org.tessellation.currency.dataApplication._
import org.tessellation.currency.dataApplication.dataApplication.DataApplicationBlock
import org.tessellation.security.signature.Signed

import io.constellationnetwork.metagraph_sdk.std.JsonBinaryCodec
import io.constellationnetwork.metagraph_sdk.std.JsonBinaryCodec.{simpleJsonDeserialization, simpleJsonSerialization}
import io.constellationnetwork.metagraph_sdk.std.JsonBinaryCodec._

import io.circe.{Decoder, Encoder}
import org.http4s.EntityDecoder
import org.http4s.circe.CirceEntityCodec.circeEntityDecoder

abstract class MetagraphCommonService[F[_], TX <: DataUpdate, PUB <: DataOnChainState, PRV <: DataCalculatedState](
implicit
txEncoder: Encoder[TX],
txDecoder: Decoder[TX],
prvEncoder: Encoder[PRV],
prvDecoder: Decoder[PRV],
val txBinCodec: JsonBinaryCodec[F, TX],
val pubBinCodec: JsonBinaryCodec[F, PUB],
val prvBinCodec: JsonBinaryCodec[F, PRV],
async: Async[F]
txEncoder: Encoder[TX],
pubEncoder: Encoder[PUB],
prvEncoder: Encoder[PRV],
txDecoder: Decoder[TX],
pubDecoder: Decoder[PUB],
prvDecoder: Decoder[PRV],
async: Async[F]
) {

val signedDataEntityDecoder: EntityDecoder[F, Signed[TX]] = circeEntityDecoder

implicit val dataBlockCodec: JsonBinaryCodec[F, Signed[DataApplicationBlock]] =
new JsonBinaryCodec[F, Signed[DataApplicationBlock]] {
implicit def dataEncoder: Encoder[DataUpdate] = txEncoder.contramap(_.asInstanceOf[TX])
implicit def dataUpdateEncoder: Encoder[DataUpdate] = txEncoder.contramap(_.asInstanceOf[TX])

implicit def dataDecoder: Decoder[DataUpdate] = txDecoder.widen
implicit def dataUpdateDecoder: Decoder[DataUpdate] = txDecoder.widen

override def serialize(obj: Signed[DataApplicationBlock]): F[Array[Byte]] =
simpleJsonSerialization(obj)

override def deserialize(bytes: Array[Byte]): F[Either[Throwable, Signed[DataApplicationBlock]]] =
simpleJsonDeserialization(bytes)
}
val signedDataEntityDecoder: EntityDecoder[F, Signed[TX]] = circeEntityDecoder

def serializeState(state: PUB): F[Array[Byte]] =
pubBinCodec.serialize(state)
state.toBinary

def deserializeState(bytes: Array[Byte]): F[Either[Throwable, PUB]] =
pubBinCodec.deserialize(bytes)
bytes.fromBinary[PUB]

def serializeCalculatedState(calculatedState: PRV): F[Array[Byte]] =
prvBinCodec.serialize(calculatedState)
calculatedState.toBinary

def deserializeCalculatedState(bytes: Array[Byte]): F[Either[Throwable, PRV]] =
prvBinCodec.deserialize(bytes)
bytes.fromBinary[PRV]

def serializeUpdate(update: TX): F[Array[Byte]] =
txBinCodec.serialize(update)
update.toBinary

def deserializeUpdate(bytes: Array[Byte]): F[Either[Throwable, TX]] =
txBinCodec.deserialize(bytes)
bytes.fromBinary[TX]

def serializeBlock(block: Signed[DataApplicationBlock]): F[Array[Byte]] =
dataBlockCodec.serialize(block)
block.toBinary

def deserializeBlock(bytes: Array[Byte]): F[Either[Throwable, Signed[DataApplicationBlock]]] =
dataBlockCodec.deserialize(bytes)
bytes.fromBinary[Signed[DataApplicationBlock]]

def dataEncoder: Encoder[TX] = txEncoder

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,66 +2,75 @@ package io.constellationnetwork.metagraph_sdk.std

import java.nio.charset.StandardCharsets

import cats.effect.Sync
import cats.implicits.{toFlatMapOps, toFunctorOps}
import cats.MonadThrow
import cats.syntax.all._

import org.tessellation.currency.dataApplication.DataUpdate
import org.tessellation.currency.schema.currency.CurrencyIncrementalSnapshot
import org.tessellation.schema.ID.Id

import io.constellationnetwork.metagraph_sdk.std.JsonCanonicalizer.JsonPrinterEncodeOps

import io.circe.jawn.JawnParser
import io.circe.syntax.EncoderOps
import io.circe.{Decoder, Encoder, Printer}
import io.circe.{Decoder, Encoder}
import org.bouncycastle.util.encoders.Base64

trait JsonBinaryCodec[F[_], A] {
def serialize(content: A): F[Array[Byte]]
def deserialize(content: Array[Byte]): F[Either[Throwable, A]]
def serialize(content: A): F[Array[Byte]]
def deserialize(bytes: Array[Byte]): F[Either[Throwable, A]]
}

object JsonBinaryCodec {

private val printer = Printer(dropNullValues = true, indent = "", sortKeys = true)

def apply[F[_], A](implicit ev: JsonBinaryCodec[F, A]): JsonBinaryCodec[F, A] = ev

def simpleJsonSerialization[F[_]: Sync, A: Encoder](content: A): F[Array[Byte]] =
Sync[F].delay(content.asJson.printWith(printer).getBytes("UTF-8"))

def simpleJsonDeserialization[F[_]: Sync, A: Decoder](content: Array[Byte]): F[Either[Throwable, A]] =
Sync[F].delay(JawnParser(false).decodeByteArray[A](content))
def fromBinary[F[_], A](bytes: Array[Byte])(implicit codec: JsonBinaryCodec[F, A]): F[Either[Throwable, A]] =
codec.deserialize(bytes)

def serializeDataUpdate[F[_]: Sync, U <: DataUpdate: Encoder](content: U): F[Array[Byte]] = for {
jsonBytes <- simpleJsonSerialization(content)
base64String <- Sync[F].delay(Base64.toBase64String(jsonBytes))
prefixedString = s"\u0019Constellation Signed Data:\n${base64String.length}\n$base64String"
} yield prefixedString.getBytes("UTF-8")
implicit def derive[F[_]: MonadThrow, A: Encoder: Decoder]: JsonBinaryCodec[F, A] =
new JsonBinaryCodec[F, A] {

def deserializeDataUpdate[F[_]: Sync, U <: DataUpdate: Decoder](
bytes: Array[Byte]
): F[Either[Throwable, DataUpdate]] = for {
base64String <- Sync[F].pure(new String(bytes, StandardCharsets.UTF_8).split("\n").drop(2).mkString)
jsonBytes <- Sync[F].delay(Base64.decode(base64String))
result <- simpleJsonDeserialization(jsonBytes)
} yield result
def serialize(content: A): F[Array[Byte]] =
for {
str <- content.toCanonical
bytes <- str.getBytes("UTF-8").pure[F]
} yield bytes

implicit def currencyIncrementalSnapshotCodec[F[_]: Sync]: JsonBinaryCodec[F, CurrencyIncrementalSnapshot] =
new JsonBinaryCodec[F, CurrencyIncrementalSnapshot] {

override def serialize(obj: CurrencyIncrementalSnapshot): F[Array[Byte]] =
simpleJsonSerialization(obj)
def deserialize(bytes: Array[Byte]): F[Either[Throwable, A]] =
for {
str <- new String(bytes, "UTF-8").pure[F]
result <- JawnParser(false).decode[A](str).pure[F]
} yield result
}

override def deserialize(bytes: Array[Byte]): F[Either[Throwable, CurrencyIncrementalSnapshot]] =
simpleJsonDeserialization(bytes)
implicit def deriveDataUpdate[F[_]: MonadThrow, U <: DataUpdate: Encoder: Decoder]: JsonBinaryCodec[F, U] =
new JsonBinaryCodec[F, U] {

def serialize(content: U): F[Array[Byte]] =
for {
str <- content.toCanonical
bytes <- str.getBytes("UTF-8").pure[F]
base64String <- Base64.toBase64String(bytes).pure[F]
prefixedString = s"\u0019Constellation Signed Data:\n${base64String.length}\n$base64String"
result <- prefixedString.getBytes("UTF-8").pure[F]
} yield result

def deserialize(bytes: Array[Byte]): F[Either[Throwable, U]] =
for {
str <- new String(bytes, StandardCharsets.UTF_8).pure[F]
base64String <- str.split("\n").drop(2).mkString.pure[F]
bytes <- Base64.decode(base64String).pure[F]
jsonStr <- new String(bytes, "UTF-8").pure[F]
result <- JawnParser(false).decode[U](jsonStr).pure[F]
} yield result
}

implicit def idCodec[F[_]: Sync]: JsonBinaryCodec[F, Id] =
new JsonBinaryCodec[F, Id] {
implicit class JsonBinaryEncodeOps[F[_], A](private val _v: A) extends AnyVal {

override def serialize(obj: Id): F[Array[Byte]] =
simpleJsonSerialization(obj)
def toBinary(implicit codec: JsonBinaryCodec[F, A]): F[Array[Byte]] =
codec.serialize(_v)
}

override def deserialize(bytes: Array[Byte]): F[Either[Throwable, Id]] =
simpleJsonDeserialization(bytes)
}
implicit class JsonBinaryDecodeOps[F[_]](private val _v: Array[Byte]) extends AnyVal {

def fromBinary[A](implicit codec: JsonBinaryCodec[F, A]): F[Either[Throwable, A]] =
codec.deserialize(_v)
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
package io.constellationnetwork.metagraph_sdk.std

import cats.Functor
import cats.implicits.toFunctorOps
import cats.MonadThrow
import cats.syntax.functor._

import org.tessellation.security.hash.Hash

trait JsonBinaryHasher[F[_]] {
def hash[A](data: A): F[Hash]
def hash[A](data: A)(implicit codec: JsonBinaryCodec[F, A]): F[Hash]
}

object JsonBinaryHasher {
def apply[F[_]](implicit ev: JsonBinaryHasher[F]): JsonBinaryHasher[F] = ev

implicit class FromJsonBinaryCodec[F[_]: Functor, A](obj: A)(implicit bin: JsonBinaryCodec[F, A]) {
def hash: F[Hash] = bin.serialize(obj).map(Hash.fromBytes)
implicit def deriveFromCodec[F[_]: MonadThrow]: JsonBinaryHasher[F] =
new JsonBinaryHasher[F] {

def hash[A](data: A)(implicit codec: JsonBinaryCodec[F, A]): F[Hash] =
codec.serialize(data).map(Hash.fromBytes)
}

implicit class HasherOps[F[_], A](private val _v: A) extends AnyVal {

def hash(implicit mt: MonadThrow[F], codec: JsonBinaryCodec[F, A]): F[Hash] =
JsonBinaryHasher[F].hash(_v)
}
}
Loading

0 comments on commit 1cf361e

Please sign in to comment.