Skip to content
This repository has been archived by the owner on Jul 25, 2023. It is now read-only.

Commit

Permalink
Rework Transaction type to allow full range of transaction kinds
Browse files Browse the repository at this point in the history
  • Loading branch information
rootmos committed Nov 1, 2018
1 parent c8c34b5 commit eb212b4
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 129 deletions.
103 changes: 51 additions & 52 deletions types/src/main/scala/co/upvest/arweave4s/adt/Transaction.scala
Expand Up @@ -2,11 +2,26 @@ package co.upvest.arweave4s.adt

import co.upvest.arweave4s.utils.CryptoUtils

trait Transaction extends Signable {
def lastTx: Option[Transaction.Id]
def owner: Owner
def reward: Winston
def tpe: Transaction.Type
final case class Transaction(
lastTx: Option[Transaction.Id],
owner: Owner,
reward: Winston,
data: Option[Base64EncodedBytes],
tags: Option[Seq[Tag.Custom]],
target: Option[Address],
quantity: Option[Winston],
) extends Signable {
lazy val signingData = Array.concat(
owner.bytes,
target map { _.bytes } getOrElse Array.empty,
data map { _.bytes } getOrElse Array.empty,
quantity.toString.getBytes,
reward.toString.getBytes,
lastTx map { _.bytes } getOrElse Array.empty,
tags.toSeq.flatten.map { t => t.name ++ t.value }.flatten.toArray
)

def withData(d: Array[Byte]) = this.copy(data = Some(Data(d)))
}

object Transaction {
Expand All @@ -20,53 +35,37 @@ object Transaction {
CryptoUtils.base64UrlDecode(s) map { new Id(_) }
}

sealed trait Type
object Type {
case object Transfer extends Type
case object Data extends Type
}
def data(
lastTx: Option[Id],
owner: Owner,
data: Base64EncodedBytes,
reward: Winston,
tags: Seq[Tag.Custom]
): Transaction = Transaction(
lastTx,
owner,
reward,
Some(data),
Some(tags),
None,
None
)

/**
* Signing the transaction according to the documentation.
*
* unencode <- Takes input X and returns the completely unencoded form
* sign <- Takes data D and key K returns a signature of D signed with K
*
* owner <- unencode(owner)
* target <- unencode(target)
* data <- unencode(data)
* quantity <- unencode(quantity)
* reward <- unencode(reward)
* last_tx <- unencode(last_tx)
*
* sig_data <- owner + target + data + quantity + reward + last_tx
* signature <- sign(sig_data, key)
*
*/
case class Data(lastTx: Option[Id], owner: Owner, data: Base64EncodedBytes, reward: Winston, tags: Seq[Tag.Custom]) extends Transaction {
val tpe: Type = Type.Data
lazy val signingData = Array.concat(
owner.bytes,
Array.empty,
data.bytes,
Winston.Zero.toString.getBytes,
reward.toString.getBytes,
lastTx map { _.bytes } getOrElse Array.empty,
tags.map{ t => t.name ++ t.value }.flatten.toArray
)
}

case class Transfer(lastTx: Option[Id], owner: Owner, target: Address, quantity: Winston, reward: Winston) extends Transaction {
val tpe: Type = Type.Transfer
lazy val signingData = Array.concat(
owner.bytes,
target.bytes,
Array.empty,
quantity.toString.getBytes,
reward.toString.getBytes,
lastTx map { _.bytes } getOrElse Array.empty
)
}
def transfer(
lastTx: Option[Id],
owner: Owner,
reward: Winston,
target: Address,
quantity: Winston,
): Transaction = Transaction(
lastTx,
owner,
reward,
None,
None,
Some(target),
Some(quantity)
)

sealed trait WithStatus
object WithStatus {
Expand All @@ -76,7 +75,7 @@ object Transaction {
case class Accepted(stx: Signed[Transaction]) extends WithStatus
}

implicit class SignedTransaction[T <: Transaction](stx: Signed[T]) {
implicit class SignedTransaction(stx: Signed[Transaction]) {
def id: Id = new Id(CryptoUtils.sha256(stx.signature.bytes))
}
}
104 changes: 27 additions & 77 deletions types/src/main/scala/co/upvest/arweave4s/marshalling/Marshaller.scala
Expand Up @@ -3,8 +3,6 @@ package co.upvest.arweave4s.marshalling
import co.upvest.arweave4s.adt.{Transaction, _}
import co.upvest.arweave4s.utils.{CirceComplaints, CryptoUtils, EmptyStringAsNone}
import cats.syntax.option._
import io.circe
import io.circe.Decoder.Result
import io.circe.syntax._
import cats.syntax.flatMap._
import cats.instances.either._
Expand Down Expand Up @@ -137,82 +135,34 @@ trait Marshaller {
)
}

implicit lazy val dataTransactionDecoder = new Decoder[Transaction.Data] {
override def apply(c: HCursor): Result[Transaction.Data] =
for {
lastTx <- c.downField("last_tx").as[EmptyStringAsNone[Transaction.Id]]
owner <- c.downField("owner").as[Owner]
data <- c.downField("data").as[Data]
reward <- c.downField("reward").as[Winston]
tags <- {
import TagsInTransaction.decoder
c.downField("tags").as[Seq[Tag.Custom]]
}
} yield Transaction.Data(lastTx, owner, data, reward, tags)
}

implicit lazy val dataTransactionEncoder: Encoder[Signed[Transaction.Data]] =
tx =>
Json.obj(
"id" := tx.id,
"last_tx" -> tx.lastTx.noneAsEmptyString,
"target" := "",
"owner" := tx.owner,
"reward" := tx.reward,
"quantity" := Winston.Zero,
"data" := tx.data,
"tags" -> {
import TagsInTransaction.encoder
tx.tags.asJson
},
"signature" := tx.signature
)

implicit lazy val transferTransactionEncoder: Encoder[Signed[Transaction.Transfer]] =
tx =>
Json.obj(
"id" := tx.id,
"last_tx" -> tx.lastTx.noneAsEmptyString,
"data" := "",
"owner" := tx.owner,
"target" := tx.target,
"quantity" := tx.quantity,
"reward" := tx.reward,
"signature" := tx.signature
)

implicit lazy val transactionEncoder: Encoder[Signed[Transaction]] = stx =>
stx.t match {
case t: Transaction.Transfer => stx.copy(t = t).asJson
case t: Transaction.Data => stx.copy(t = t).asJson
}

implicit lazy val transferTransactionDecoder =
new Decoder[Transaction.Transfer] {
override def apply(c: HCursor): Result[Transaction.Transfer] =
for {
lastTx <- c.downField("last_tx").as[EmptyStringAsNone[Transaction.Id]]
owner <- c.downField("owner").as[Owner]
target <- c.downField("target").as[Address]
quantity <- c.downField("quantity").as[Winston]
reward <- c.downField("reward").as[Winston]
} yield Transaction.Transfer(lastTx, owner, target, quantity, reward)
implicit lazy val transactionDecoder: Decoder[Transaction] = c => for {
lastTx <- c.downField("last_tx").as[EmptyStringAsNone[Transaction.Id]]
owner <- c.downField("owner").as[Owner]
reward <- c.downField("reward").as[Winston]
data <- c.downField("data").as[Option[Data]]
tags <- {
import TagsInTransaction.decoder
c.downField("tags").as[Option[Seq[Tag.Custom]]]
}

implicit lazy val transactionDecoder = new Decoder[Transaction] {
override def apply(c: HCursor): Result[Transaction] =
(for {
d <- c.downField("data").as[EmptyStringAsNone[Data]]
q <- c.downField("quantity").as[Option[Winston]]
} yield (d.toOption, q)) >>= {
case (None, Some(_)) => transferTransactionDecoder(c)
case (Some(_), Some(Winston.Zero)) => dataTransactionDecoder(c)
case _ => Left(circe.DecodingFailure(
message = s"unknown transaction type",
ops = Nil
))
}
}
target <- c.downField("target").as[Option[Address]]
quantity <- c.downField("quantity").as[Option[Winston]]
} yield Transaction(lastTx, owner, reward, data, tags, target, quantity)

implicit lazy val transactionEncoder: Encoder[Signed[Transaction]] = tx =>
Json.obj(
"id" := tx.id,
"last_tx" -> tx.lastTx.noneAsEmptyString,
"reward" := tx.reward,
"owner" := tx.owner,
"data" := tx.data,
"tags" -> {
import TagsInTransaction.encoder
tx.tags.asJson
},
"target" := tx.target,
"quantity" := tx.quantity,
"signature" := tx.signature
)

implicit def signedDecoder[T <: Signable: Decoder]: Decoder[Signed[T]] =
c =>
Expand Down

0 comments on commit eb212b4

Please sign in to comment.