From 98ca3c21012d1944dbde60a478c8b432c2fcf63a Mon Sep 17 00:00:00 2001 From: Edmundo Lopez Bobeda Date: Mon, 5 Sep 2022 16:25:58 -0400 Subject: [PATCH 1/5] Add first working version. This version works and actually mints an asset. However, it does not work as expected. Indeed, it should keep the different assets in their respective boxes. --- daml/Main.daml | 2 +- daml/Tests/Asset.daml | 1 - daml/Topl/Asset.daml | 5 + daml/Topl/Organization.daml | 3 - daml/Topl/Utils.daml | 1 - pom.xml | 40 +++- src/main/java/co/topl/daml/AliceMain.java | 6 +- .../java/co/topl/daml/AssetOperatorMain.java | 69 ++++++ src/main/java/co/topl/daml/OperatorMain.java | 14 +- src/main/resources/logback.xml | 2 +- .../{processors => }/AbstractProcessor.scala | 8 +- .../topl/daml/{processors => }/Context.scala | 2 +- src/main/scala/co/topl/daml/Test.worksheet.sc | 3 + .../BoxPreservingAssetTransaction.scala | 103 +++++++++ .../AssetMintingRequestProcessor.scala | 202 ++++++++++++++++++ .../SignedMintingRequestProcessor.scala | 145 +++++++++++++ .../UnsignedMintingRequestProcessor.scala | 104 +++++++++ .../topl/daml/{processors => }/package.scala | 9 +- .../processors/SignedTransferProcessor.scala | 61 +++--- .../TransferRequestProcessor.scala} | 10 +- .../UnsignedTransferProcessor.scala | 42 ++-- 21 files changed, 760 insertions(+), 72 deletions(-) create mode 100644 src/main/java/co/topl/daml/AssetOperatorMain.java rename src/main/scala/co/topl/daml/{processors => }/AbstractProcessor.scala (95%) rename src/main/scala/co/topl/daml/{processors => }/Context.scala (90%) create mode 100644 src/main/scala/co/topl/daml/Test.worksheet.sc create mode 100644 src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala create mode 100644 src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala create mode 100644 src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala create mode 100644 src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala rename src/main/scala/co/topl/daml/{processors => }/package.scala (90%) rename src/main/scala/co/topl/daml/{ => polys}/processors/SignedTransferProcessor.scala (91%) rename src/main/scala/co/topl/daml/{processors/TransferProcessor.scala => polys/processors/TransferRequestProcessor.scala} (94%) rename src/main/scala/co/topl/daml/{ => polys}/processors/UnsignedTransferProcessor.scala (89%) diff --git a/daml/Main.daml b/daml/Main.daml index 94fb5b6..1d5b8a6 100644 --- a/daml/Main.daml +++ b/daml/Main.daml @@ -20,7 +20,7 @@ initialize = do eve <- allocateParty "Eve" eveId <- validateUserId "eve" operatorCid <- submit operator do - createCmd Operator with operator = operator, address = "" + createCmd Operator with operator = operator, address = "AUANVY6RqbJtTnQS1AFTQBjXMFYDknhV8NEixHFLmeZynMxVbp64" orgCid <- submit operator do exerciseCmd operatorCid Operator_CreateOrganization with orgName = "Topl" diff --git a/daml/Tests/Asset.daml b/daml/Tests/Asset.daml index d86442d..8d3ae10 100644 --- a/daml/Tests/Asset.daml +++ b/daml/Tests/Asset.daml @@ -18,7 +18,6 @@ module Tests.Asset where exerciseCmd orgId Organization_CreateAsset with requestor = alice version = 1 - issuerAddress = org.address shortName = "Wheat" return (operator, alice, bob, org) -- now we create an asset creator diff --git a/daml/Topl/Asset.daml b/daml/Topl/Asset.daml index 2074cf0..71ad3a6 100644 --- a/daml/Topl/Asset.daml +++ b/daml/Topl/Asset.daml @@ -122,6 +122,11 @@ module Topl.Asset where controller operator do return () + choice SignedAssetMinting_Fail : SignedAssetMintingCid + with reason : Text + controller operator + do + create this with sendStatus = FailedToSend with .. choice SignedAssetMinting_Confirm : SignedAssetMintingCid with txId : Text diff --git a/daml/Topl/Organization.daml b/daml/Topl/Organization.daml index cbefef5..85572a9 100644 --- a/daml/Topl/Organization.daml +++ b/daml/Topl/Organization.daml @@ -153,7 +153,6 @@ module Topl.Organization where newOrg Organization_CreateAsset with requestor = head members version = assetCode.version - issuerAddress = assetCode.issuerAddress shortName = assetCode.shortName))) (create this with wouldBeMembers = [], members = members, assetCodes = []) assetCodes @@ -171,7 +170,6 @@ module Topl.Organization where with requestor : Party version : Int - issuerAddress : Text shortName : Text controller requestor do @@ -179,7 +177,6 @@ module Topl.Organization where let asset = AssetCode with version = version - issuerAddress = issuerAddress shortName = shortName create AssetCreator with operator = operator diff --git a/daml/Topl/Utils.daml b/daml/Topl/Utils.daml index c69cf0a..b0c6022 100644 --- a/daml/Topl/Utils.daml +++ b/daml/Topl/Utils.daml @@ -16,7 +16,6 @@ data SendStatus = New data AssetCode = AssetCode with version : Int - issuerAddress : Text shortName : Text deriving (Eq, Show) diff --git a/pom.xml b/pom.xml index abcf142..440c9cb 100644 --- a/pom.xml +++ b/pom.xml @@ -195,10 +195,10 @@ - run-alice + run-asset-operator - alice + assetoperator @@ -208,7 +208,7 @@ exec-maven-plugin 1.6.0 - co.topl.daml.AliceMain + co.topl.daml.AssetOperatorMain ${ledgerhost} ${ledgerport} @@ -218,6 +218,40 @@ ${keyfilepassword} + + + run-demo-asset-operator + + java + + + + + + + + + run-alice + + + alice + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + co.topl.daml.AliceMain + + ${ledgerhost} + ${ledgerport} + ${keyfilename} + ${key1filepassword} + + run-demo-alice diff --git a/src/main/java/co/topl/daml/AliceMain.java b/src/main/java/co/topl/daml/AliceMain.java index 62db726..3a6386e 100644 --- a/src/main/java/co/topl/daml/AliceMain.java +++ b/src/main/java/co/topl/daml/AliceMain.java @@ -12,14 +12,14 @@ import com.daml.ledger.javaapi.data.Transaction; import com.daml.ledger.rxjava.DamlLedgerClient; import com.daml.ledger.rxjava.UserManagementClient; -import co.topl.daml.processors.UnsignedTransferProcessor; +import co.topl.daml.polys.processors.UnsignedTransferProcessor; import akka.actor.ActorSystem; import co.topl.client.Provider; import akka.http.javadsl.model.Uri; import io.reactivex.Flowable; -import co.topl.daml.processors.DamlAppContext; -import co.topl.daml.processors.ToplContext; +import co.topl.daml.DamlAppContext; +import co.topl.daml.ToplContext; public class AliceMain { diff --git a/src/main/java/co/topl/daml/AssetOperatorMain.java b/src/main/java/co/topl/daml/AssetOperatorMain.java new file mode 100644 index 0000000..4d7641a --- /dev/null +++ b/src/main/java/co/topl/daml/AssetOperatorMain.java @@ -0,0 +1,69 @@ +package co.topl.daml; + +import java.util.Collections; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.daml.ledger.javaapi.data.FiltersByParty; +import com.daml.ledger.javaapi.data.GetUserRequest; +import com.daml.ledger.javaapi.data.LedgerOffset; +import com.daml.ledger.javaapi.data.NoFilter; +import com.daml.ledger.javaapi.data.Transaction; +import com.daml.ledger.rxjava.DamlLedgerClient; +import com.daml.ledger.rxjava.UserManagementClient; +import co.topl.daml.assets.processors.AssetMintingRequestProcessor; +import co.topl.daml.assets.processors.UnsignedMintingRequestProcessor; +import co.topl.daml.assets.processors.SignedMintingRequestProcessor; +import akka.actor.ActorSystem; +import co.topl.client.Provider; +import akka.http.javadsl.model.Uri; + +import io.reactivex.Flowable; +import co.topl.daml.DamlAppContext; +import co.topl.daml.ToplContext; + +public class AssetOperatorMain { + + // constants for referring to users with access to the parties + public static final String OPERATOR_USER = "operator"; + + // application id used for sending commands + private static final String APP_ID = "OperatorMainApp"; + + private static final Logger logger = LoggerFactory.getLogger(OperatorMain.class); + + public static void main(String[] args) { + if (args.length < 4) { + System.err.println("Usage: HOST PORT PROJECTID APIKEY KEYFILENAME KEYFILEPASSWORD"); + System.exit(-1); + } + String host = args[0]; + int port = Integer.parseInt(args[1]); + String projectId = args[2]; + String apiKey = args[3]; + String keyfile = args[4]; + String password = args[5]; + DamlLedgerClient client = DamlLedgerClient.newBuilder(host, port).build(); + client.connect(); + UserManagementClient userManagementClient = client.getUserManagementClient(); + + String operatorParty = userManagementClient.getUser(new GetUserRequest(OPERATOR_USER)).blockingGet().getUser() + .getPrimaryParty().get(); + Flowable transactions = client.getTransactionsClient().getTransactions( + LedgerOffset.LedgerEnd.getInstance(), + new FiltersByParty(Collections.singletonMap(operatorParty, NoFilter.instance)), true); + Uri uri = Uri.create("http://localhost:9085"); + DamlAppContext damlAppContext = new DamlAppContext(APP_ID, operatorParty, client); + ToplContext toplContext = new ToplContext(ActorSystem.create(), new Provider.PrivateTestNet(uri.asScala(), "")); + AssetMintingRequestProcessor assetMintingRequestProcessor = new AssetMintingRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(assetMintingRequestProcessor::processTransaction); + UnsignedMintingRequestProcessor unsignedMintingRequestProcessor = new UnsignedMintingRequestProcessor( + damlAppContext, toplContext, keyfile, password); + transactions.forEach(unsignedMintingRequestProcessor::processTransaction); + SignedMintingRequestProcessor signedMintingRequestProcessor = new SignedMintingRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(signedMintingRequestProcessor::processTransaction); + } +} diff --git a/src/main/java/co/topl/daml/OperatorMain.java b/src/main/java/co/topl/daml/OperatorMain.java index e52a55b..ed7fb9d 100644 --- a/src/main/java/co/topl/daml/OperatorMain.java +++ b/src/main/java/co/topl/daml/OperatorMain.java @@ -12,15 +12,16 @@ import com.daml.ledger.javaapi.data.Transaction; import com.daml.ledger.rxjava.DamlLedgerClient; import com.daml.ledger.rxjava.UserManagementClient; -import co.topl.daml.processors.TransferProcessor; -import co.topl.daml.processors.SignedTransferProcessor; +import co.topl.daml.polys.processors.TransferRequestProcessor; +import co.topl.daml.polys.processors.SignedTransferProcessor; +import co.topl.daml.assets.processors.AssetMintingRequestProcessor; import akka.actor.ActorSystem; import co.topl.client.Provider; import akka.http.javadsl.model.Uri; import io.reactivex.Flowable; -import co.topl.daml.processors.DamlAppContext; -import co.topl.daml.processors.ToplContext; +import co.topl.daml.DamlAppContext; +import co.topl.daml.ToplContext; public class OperatorMain { @@ -54,9 +55,12 @@ public static void main(String[] args) { DamlAppContext damlAppContext = new DamlAppContext(APP_ID, operatorParty, client); ToplContext toplContext = new ToplContext(ActorSystem.create(), new Provider.ValhallaTestNet(uri.asScala(), apiKey)); - TransferProcessor transferProcessor = new TransferProcessor(damlAppContext, toplContext); + TransferRequestProcessor transferProcessor = new TransferRequestProcessor(damlAppContext, toplContext); transactions.forEach(transferProcessor::processTransaction); SignedTransferProcessor signedTransferProcessor = new SignedTransferProcessor(damlAppContext, toplContext); transactions.forEach(signedTransferProcessor::processTransaction); + AssetMintingRequestProcessor assetMintingRequestProcessor = new AssetMintingRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(assetMintingRequestProcessor::processTransaction); } } diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index ca03174..b857a2f 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -12,6 +12,6 @@ - + \ No newline at end of file diff --git a/src/main/scala/co/topl/daml/processors/AbstractProcessor.scala b/src/main/scala/co/topl/daml/AbstractProcessor.scala similarity index 95% rename from src/main/scala/co/topl/daml/processors/AbstractProcessor.scala rename to src/main/scala/co/topl/daml/AbstractProcessor.scala index 657a91c..d3df87d 100644 --- a/src/main/scala/co/topl/daml/processors/AbstractProcessor.scala +++ b/src/main/scala/co/topl/daml/AbstractProcessor.scala @@ -1,12 +1,12 @@ -package co.topl.daml.processors +package co.topl.daml +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent import com.daml.ledger.javaapi.data.Transaction -import io.reactivex.Single import com.google.protobuf.Empty +import io.reactivex.Single import java.util.stream -import com.daml.ledger.javaapi.data.CreatedEvent -import com.daml.ledger.javaapi.data.Command import scala.concurrent.ExecutionContext abstract class AbstractProcessor(damlAppContext: DamlAppContext, toplContext: ToplContext) { diff --git a/src/main/scala/co/topl/daml/processors/Context.scala b/src/main/scala/co/topl/daml/Context.scala similarity index 90% rename from src/main/scala/co/topl/daml/processors/Context.scala rename to src/main/scala/co/topl/daml/Context.scala index 0d853e7..0f7a80b 100644 --- a/src/main/scala/co/topl/daml/processors/Context.scala +++ b/src/main/scala/co/topl/daml/Context.scala @@ -1,4 +1,4 @@ -package co.topl.daml.processors +package co.topl.daml import co.topl.client.Provider import com.daml.ledger.rxjava.DamlLedgerClient diff --git a/src/main/scala/co/topl/daml/Test.worksheet.sc b/src/main/scala/co/topl/daml/Test.worksheet.sc new file mode 100644 index 0000000..671bfdb --- /dev/null +++ b/src/main/scala/co/topl/daml/Test.worksheet.sc @@ -0,0 +1,3 @@ +val x = List(5) + +x.tail diff --git a/src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala b/src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala new file mode 100644 index 0000000..add8a7a --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala @@ -0,0 +1,103 @@ +package co.topl.daml.assets + +import co.topl.attestation.Address +import co.topl.attestation.EvidenceProducer +import co.topl.attestation.Proof +import co.topl.attestation.Proposition +import co.topl.crypto.hash.blake2b256 +import co.topl.modifier.box.Box +import co.topl.modifier.box.SimpleValue +import co.topl.modifier.box.TokenBox +import co.topl.modifier.box.TokenValueHolder +import co.topl.modifier.transaction.Transaction +import co.topl.modifier.transaction.TransferTransaction +import co.topl.utils.Identifiable +import co.topl.utils.Int128 +import co.topl.utils.StringDataTypes.Latin1Data +import com.google.common.primitives.Ints +import com.google.common.primitives.Longs + +import scala.collection.immutable.ListMap +import co.topl.modifier.box.AssetValue +import co.topl.modifier.transaction.AssetTransfer +import co.topl.modifier.box.AssetBox +import co.topl.modifier.box.PolyBox + +class BoxPreservingAssetTransaction[ + P <: Proposition: EvidenceProducer: Identifiable +]( + override val from: IndexedSeq[(Address, Box.Nonce)], + override val to: IndexedSeq[(Address, TokenValueHolder)], + override val attestation: ListMap[P, Proof[P]], + override val fee: Int128, + override val timestamp: Long, + override val data: Option[Latin1Data] = None, + override val minting: Boolean = false, + val mintedAsset: AssetValue, + val previousAssets: IndexedSeq[(Address, TokenValueHolder)] +) extends AssetTransfer(from, to, attestation, fee, timestamp, data, minting) { + + val (asseetFeeOutputParams, assetCoinOutputParams) = + calculateBoxNonce[TokenValueHolder](mintedAsset, previousAssets) + + val assetCoinOutput: Iterable[AssetBox] = + assetCoinOutputParams.map { + case TransferTransaction.BoxParams(evi, nonce, value: AssetValue) => + AssetBox(evi, nonce, value) + case TransferTransaction.BoxParams(_, _, value) => + throw new IllegalArgumentException(s"AssetTransfer Coin output params contained invalid value=$value") + } + + val assetFeeChangeOutput: PolyBox = + PolyBox(asseetFeeOutputParams.evidence, asseetFeeOutputParams.nonce, asseetFeeOutputParams.value) + + override val newBoxes: Iterable[TokenBox[TokenValueHolder]] = { + // this only creates an output if the value of the output boxes is non-zero + val recipientCoinOutput: Iterable[AssetBox] = assetCoinOutput.filter(_.value.quantity > 0) + val hasRecipientOutput: Boolean = recipientCoinOutput.nonEmpty + val hasFeeChangeOutput: Boolean = assetFeeChangeOutput.value.quantity > 0 + + (hasRecipientOutput, hasFeeChangeOutput) match { + case (false, _) => Iterable() + case (true, false) => recipientCoinOutput + case (true, true) => Iterable(assetFeeChangeOutput) ++ recipientCoinOutput + } + } + + /** + * Computes a unique nonce value based on the transaction type and + * inputs and returns the details needed to create the output boxes for the transaction + */ + def calculateBoxNonce[T <: TokenValueHolder]( + pMintedAsset: T, + to: IndexedSeq[(Address, T)] + ): (TransferTransaction.BoxParams[SimpleValue], Iterable[TransferTransaction.BoxParams[T]]) = { + + // known input data (similar to messageToSign but without newBoxes since they aren't known yet) + val txIdPrefix = Transaction.identifier(this).typePrefix + val boxIdsToOpenAccumulator = this.boxIdsToOpen.foldLeft(Array[Byte]())((acc, x) => acc ++ x.hash.value) + val timestampBytes = Longs.toByteArray(this.timestamp) + val feeBytes = this.fee.toByteArray + + val inputBytes = + Array(txIdPrefix) ++ boxIdsToOpenAccumulator ++ timestampBytes ++ feeBytes + + val calcNonce: Int => Box.Nonce = (index: Int) => { + val digest = blake2b256.hash(inputBytes ++ Ints.toByteArray(index)) + Transaction.nonceFromDigest(digest) + } + + val feeChangeParams = + TransferTransaction.BoxParams(this.to.head._1.evidence, calcNonce(0), SimpleValue(this.to.head._2.quantity)) + + val coinOutputParams: IndexedSeq[TransferTransaction.BoxParams[T]] = + (to + .appended((this.to(0)._1, (pMintedAsset)))) + .zipWithIndex + .map { case ((addr, value), idx) => + TransferTransaction.BoxParams(addr.evidence, calcNonce(idx + 1), value) + } + (feeChangeParams, coinOutputParams) + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala new file mode 100644 index 0000000..bbf8b0a --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala @@ -0,0 +1,202 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.data.NonEmptyChain +import co.topl.akkahttprpc.implicits.client.rpcToClient +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.Proposition +import co.topl.attestation.PublicKeyPropositionCurve25519 +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.asset.AssetMintingRequest +import co.topl.daml.processEventAux +import co.topl.daml.utf8StringToLatin1ByteArray +import co.topl.modifier.box.AssetCode +import co.topl.modifier.box.AssetValue +import co.topl.modifier.box.SecurityRoot +import co.topl.modifier.transaction.AssetTransfer +import co.topl.modifier.transaction.builder.BoxSelectionAlgorithms +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.modifier.transaction.serialization.PolyTransferSerializer +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.IdiomaticScalaTransition.implicits.toValidatedOps +import co.topl.utils.Int128 +import co.topl.utils.StringDataTypes +import co.topl.utils.StringDataTypes.Base58Data +import co.topl.utils.StringDataTypes.Latin1Data +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import org.slf4j.LoggerFactory +import scodec.bits.ByteVector +import co.topl.attestation._ + +import java.util.stream +import scala.collection.JavaConverters._ +import scala.concurrent.Await + +import ToplRpc.Transaction.RawAssetTransfer +import co.topl.daml.assets.BoxPreservingAssetTransaction +import scala.collection.immutable.ListMap + +class AssetMintingRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + val logger = LoggerFactory.getLogger(classOf[AssetMintingRequestProcessor]) + + import toplContext.provider._ + + def createParams(assetMintingRequest: AssetMintingRequest): RawAssetTransfer.Params = + RawAssetTransfer.Params( + propositionType = + PublicKeyPropositionCurve25519.typeString, // required fixed string for now, exciting possibilities in the future! + sender = NonEmptyChain + .fromSeq( + assetMintingRequest.from.asScala.toSeq + .map(Base58Data.unsafe) + .map(_.decodeAddress.getOrThrow()) + ) + .get, // Set of addresses whose state you want to use for the transaction + recipients = NonEmptyChain + .fromSeq( + assetMintingRequest.to.asScala.toSeq.map(x => + ( + Base58Data.unsafe(x._1).decodeAddress.getOrThrow(), + AssetValue( + Int128(x._2.intValue()), + AssetCode( + assetMintingRequest.assetCode.version.toByte, + Base58Data + .unsafe(assetMintingRequest.from.get(0)) + .decodeAddress + .getOrThrow(), + Latin1Data.fromData( + utf8StringToLatin1ByteArray(assetMintingRequest.assetCode.shortName) + ) + ), + assetMintingRequest.someCommitRoot + .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) + .orElse(SecurityRoot.empty), + assetMintingRequest.someMetadata + .map(x => + Option( + Latin1Data.fromData( + utf8StringToLatin1ByteArray(x) + ) + ) + ) + .orElse(None) + ) + ) + ) + ) + .get, // Chain of (Recipients, Value) tuples that represent the output boxes + fee = Int128( + assetMintingRequest.fee + ), // fee to be paid to the network for the transaction (unit is nanoPoly) + changeAddress = Base58Data + .unsafe(assetMintingRequest.changeAddress) + .decodeAddress + .getOrThrow(), // who will get ALL the change from the transaction? + consolidationAddress = Base58Data + .unsafe(assetMintingRequest.changeAddress) + .decodeAddress + .getOrThrow(), // who will get ALL the change from the transaction? + minting = true, + data = None, // upto 128 Latin-1 encoded characters of optional data, + boxSelectionAlgorithm = BoxSelectionAlgorithms.All + ) + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(AssetMintingRequest.TEMPLATE_ID, event) { + val mintingRequestContract = + AssetMintingRequest.Contract.fromCreatedEvent(event).id + val assetMintingRequest = + AssetMintingRequest.fromValue( + event.getArguments() + ) + val params = createParams(assetMintingRequest) + val rawTransaction = + RawAssetTransfer.rpc(params).map { x => + x.rawTx + } + import scala.concurrent.duration._ + import scala.language.postfixOps + Await.result( + rawTransaction.fold( + failure => { + logger.info("Failed to obtain raw transaction from server.") + logger.debug("Error: {}", failure) + stream.Stream.of( + mintingRequestContract + .exerciseTransferRequest_Reject() + ) + }, + rawMintingRequest => { + val mintingRequest: AssetTransfer[PublicKeyPropositionCurve25519] = + new BoxPreservingAssetTransaction[PublicKeyPropositionCurve25519]( + rawMintingRequest.from, + rawMintingRequest.to, + ListMap(), + rawMintingRequest.fee, + rawMintingRequest.timestamp, + rawMintingRequest.data, + rawMintingRequest.minting, + AssetValue( + Int128(assetMintingRequest.to.asScala.head._2.intValue()), + AssetCode( + assetMintingRequest.assetCode.version.toByte, + Base58Data + .unsafe(assetMintingRequest.from.get(0)) + .decodeAddress + .getOrThrow(), + Latin1Data.fromData( + utf8StringToLatin1ByteArray(assetMintingRequest.assetCode.shortName) + ) + ), + assetMintingRequest.someCommitRoot + .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) + .orElse(SecurityRoot.empty), + assetMintingRequest.someMetadata + .map(x => + Option( + Latin1Data.fromData( + utf8StringToLatin1ByteArray(x) + ) + ) + ) + .orElse(None) + ), + IndexedSeq() + ) + val encodedTx = + ByteVector( + AssetTransferSerializer.toBytes(mintingRequest: AssetTransfer[PublicKeyPropositionCurve25519]) + ).toBase58 + logger.info("Successfully generated raw transaction for contract {}.", mintingRequestContract.contractId) + import io.circe.syntax._ + logger.info("The returned json: {}", mintingRequest.asJson) + logger.debug( + "Encoded transaction: {}", + encodedTx + ) + + stream.Stream.of( + mintingRequestContract + .exerciseMintingRequest_Accept( + encodedTx + ) + ) + } + ), + 3 second + ) + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala new file mode 100644 index 0000000..3f36c8a --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala @@ -0,0 +1,145 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.implicits._ +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.akkahttprpc.implicits.client._ +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.transfer.SignedTransfer +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.api.model.topl.utils.SendStatus +import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend +import co.topl.daml.api.model.topl.utils.sendstatus.Pending +import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import co.topl.daml.processEventAux +import co.topl.modifier.transaction.PolyTransfer +import co.topl.modifier.transaction.serialization.PolyTransferSerializer +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse +import io.circe.syntax._ +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.time.Instant +import java.util.Optional +import java.util.stream +import scala.concurrent.Await +import scala.concurrent.Future +import scala.io.Source +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.daml.api.model.topl.asset.SignedAssetMinting + +class SignedMintingRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + implicit val jsonDecoder = co.topl.modifier.transaction.Transaction.jsonDecoder + + val logger = LoggerFactory.getLogger(classOf[SignedMintingRequestProcessor]) + import toplContext.provider._ + + private def lift[E, A](a: Either[E, A]) = EitherT[Future, E, A](Future(a)) + + private def handlePending( + signedMintingRequest: SignedAssetMinting, + signedMintingRequestContract: SignedAssetMinting.ContractId + ): stream.Stream[Command] = { + val result = for { + transactionAsBytes <- + lift( + ByteVector + .fromBase58(signedMintingRequest.signedMintTx) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid signed tx from base 58", Nil)))) + ) + _ = logger.debug("transactionAsBytes = {}", transactionAsBytes) + signedTx <- lift( + AssetTransferSerializer + .parseBytes(transactionAsBytes) + .toEither + .left + .map(_ => RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid bytes for transaction", Nil)))) + ) + _ = logger.debug("from address = {}", signedTx.from.head._1.toString()) + broadcastResult <- ToplRpc.Transaction.BroadcastTx.rpc(ToplRpc.Transaction.BroadcastTx.Params(signedTx)) + } yield broadcastResult + import scala.concurrent.duration._ + import scala.language.postfixOps + Await + .result(result.value, 3 second) + .fold( + failure => { + logger.info("Failed to broadcast transaction to server.") + logger.debug("Error: {}", failure) + // FIXME error handling + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Fail("Failed broadcast to server") + ) + }, + success => { + logger.info("Successfully broadcasted transaction to network.") + logger.debug( + "Server answer: {}", + success.asJson + ) + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Sent(new Sent(Instant.now(), damlAppContext.appId, Optional.empty())) + ) + } + ) + } + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(SignedAssetMinting.TEMPLATE_ID, event) { + val signedMintingRequestContract = + SignedAssetMinting.Contract.fromCreatedEvent(event).id + val signedMintingRequest = + SignedAssetMinting.fromValue( + event.getArguments() + ) + if (signedMintingRequest.sendStatus.isInstanceOf[Pending]) { + handlePending(signedMintingRequest, signedMintingRequestContract) + } else if (signedMintingRequest.sendStatus.isInstanceOf[FailedToSend]) { + logger.error("Failed to send contract.") + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Archive() + ) + } else if (signedMintingRequest.sendStatus.isInstanceOf[Sent]) { + logger.info("Successfully sent.") + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Archive() + ) + } else { + stream.Stream.of( + signedMintingRequestContract + .exerciseSignedAssetMinting_Archive() + ) + } + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala new file mode 100644 index 0000000..ff21032 --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala @@ -0,0 +1,104 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.asset.UnsignedAssetMinting +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.processEventAux +import co.topl.modifier.transaction.PolyTransfer +import co.topl.modifier.transaction.serialization.PolyTransferSerializer +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.util.stream +import scala.concurrent.Future +import scala.io.Source +import co.topl.modifier.transaction.serialization.AssetTransferSerializer + +class UnsignedMintingRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext, + fileName: String, + password: String +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + + val logger = LoggerFactory.getLogger(classOf[UnsignedMintingRequestProcessor]) + + val keyRing: KeyRing[PrivateKeyCurve25519, KeyfileCurve25519] = + KeyRing.empty[PrivateKeyCurve25519, KeyfileCurve25519]()( + toplContext.provider.networkPrefix, + PrivateKeyCurve25519.secretGenerator, + KeyfileCurve25519Companion + ) + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(UnsignedAssetMinting.TEMPLATE_ID, event) { + val unsidgnedMintingRequestContract = + UnsignedAssetMinting.Contract.fromCreatedEvent(event).id + val unsidgnedMintingRequest = + UnsignedAssetMinting.fromValue( + event.getArguments() + ) + val keyfile = Source.fromFile(new File(fileName)).getLines().mkString("").mkString + (for { + jsonKey <- parse(keyfile) + address <- Brambl.importCurve25519JsonToKeyRing(jsonKey, password, keyRing) + msg2Sign <- ByteVector + .fromBase58(unsidgnedMintingRequest.mintTxToSign) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid contract", Nil)))) + rawTx <- AssetTransferSerializer.parseBytes(msg2Sign).toEither + signedTx <- Right { + val signFunc = (addr: Address) => keyRing.generateAttestation(addr)(rawTx.messageToSign) + logger.debug("listOfAddresses = {}", keyRing.addresses) + val signatures = keyRing.addresses.map(signFunc).reduce(_ ++ _) + rawTx.copy(attestation = signatures) + } + } yield signedTx).fold( + failure => { + logger.info("Failed to sign transaction.") + logger.debug("Error: {}", failure) + stream.Stream.of( + unsidgnedMintingRequestContract + .exerciseUnsignedMinting_Archive() + ) + }, + signedTx => { + val signedTxString = ByteVector(AssetTransferSerializer.toBytes(signedTx)).toBase58 + logger.info("Successfully signed transaction for contract {}.", unsidgnedMintingRequestContract.contractId) + logger.debug("signedTx = {}", signedTx) + logger.debug( + "Encoded transaction: {}", + signedTxString + ) + stream.Stream.of( + unsidgnedMintingRequestContract + .exerciseUnsignedMinting_Sign(signedTxString) + ) + } + ) + } + +} diff --git a/src/main/scala/co/topl/daml/processors/package.scala b/src/main/scala/co/topl/daml/package.scala similarity index 90% rename from src/main/scala/co/topl/daml/processors/package.scala rename to src/main/scala/co/topl/daml/package.scala index 990af84..e3c0f97 100644 --- a/src/main/scala/co/topl/daml/processors/package.scala +++ b/src/main/scala/co/topl/daml/package.scala @@ -1,4 +1,4 @@ -package co.topl.daml +package co.topl import cats.data.EitherT import scala.concurrent.Future @@ -14,7 +14,7 @@ import com.daml.ledger.rxjava.LedgerClient import java.util.UUID import io.reactivex.subjects.SingleSubject -package object processors { +package object daml { type RpcErrorOr[T] = EitherT[Future, RpcClientFailure, T] @@ -49,4 +49,9 @@ package object processors { ) } else return SingleSubject.create() } + + def utf8StringToLatin1ByteArray(str: String) = str.zipWithIndex + .map(e => str.codePointAt(e._2).toByte) + .toArray + } diff --git a/src/main/scala/co/topl/daml/processors/SignedTransferProcessor.scala b/src/main/scala/co/topl/daml/polys/processors/SignedTransferProcessor.scala similarity index 91% rename from src/main/scala/co/topl/daml/processors/SignedTransferProcessor.scala rename to src/main/scala/co/topl/daml/polys/processors/SignedTransferProcessor.scala index 3ff4733..aff89c2 100644 --- a/src/main/scala/co/topl/daml/processors/SignedTransferProcessor.scala +++ b/src/main/scala/co/topl/daml/polys/processors/SignedTransferProcessor.scala @@ -1,41 +1,48 @@ -package co.topl.daml.processors +package co.topl.daml.polys.processors -import java.util.stream -import com.daml.ledger.javaapi.data.Command -import com.daml.ledger.javaapi.data.CreatedEvent -import co.topl.daml.api.model.topl.transfer.UnsignedTransfer -import co.topl.attestation.keyManagement.{KeyRing, KeyfileCurve25519, KeyfileCurve25519Companion, PrivateKeyCurve25519} -import co.topl.client.Brambl +import cats.data.EitherT +import cats.implicits._ +import co.topl.akkahttprpc.InvalidParametersError import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.akkahttprpc.implicits.client._ import co.topl.attestation.Address -import io.circe.parser.parse -import scala.io.Source -import java.io.File -import cats.data.EitherT -import co.topl.utils.StringDataTypes import co.topl.attestation.AddressCodec.implicits._ -import scodec.bits._ -import co.topl.akkahttprpc.RpcErrorFailure -import co.topl.akkahttprpc.InvalidParametersError -import io.circe.DecodingFailure -import scala.concurrent.Future +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.transfer.SignedTransfer +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.api.model.topl.utils.SendStatus +import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend +import co.topl.daml.api.model.topl.utils.sendstatus.Pending +import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import co.topl.daml.processEventAux import co.topl.modifier.transaction.PolyTransfer import co.topl.modifier.transaction.serialization.PolyTransferSerializer -import org.slf4j.LoggerFactory -import co.topl.daml.api.model.topl.transfer.SignedTransfer import co.topl.rpc.ToplRpc - -import co.topl.akkahttprpc.implicits.client._ import co.topl.rpc.implicits.client._ +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse import io.circe.syntax._ -import cats.implicits._ -import scala.concurrent.Await -import co.topl.daml.api.model.topl.utils.sendstatus.Pending -import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend -import co.topl.daml.api.model.topl.utils.SendStatus -import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File import java.time.Instant import java.util.Optional +import java.util.stream +import scala.concurrent.Await +import scala.concurrent.Future +import scala.io.Source class SignedTransferProcessor( damlAppContext: DamlAppContext, diff --git a/src/main/scala/co/topl/daml/processors/TransferProcessor.scala b/src/main/scala/co/topl/daml/polys/processors/TransferRequestProcessor.scala similarity index 94% rename from src/main/scala/co/topl/daml/processors/TransferProcessor.scala rename to src/main/scala/co/topl/daml/polys/processors/TransferRequestProcessor.scala index 73a2aea..d810e85 100644 --- a/src/main/scala/co/topl/daml/processors/TransferProcessor.scala +++ b/src/main/scala/co/topl/daml/polys/processors/TransferRequestProcessor.scala @@ -1,4 +1,4 @@ -package co.topl.daml.processors +package co.topl.daml.polys.processors import com.daml.ledger.rxjava.DamlLedgerClient import io.reactivex.Single @@ -40,13 +40,17 @@ import io.reactivex.subjects.SingleSubject import com.google.protobuf.Empty import org.slf4j.LoggerFactory import co.topl.modifier.transaction.serialization.PolyTransferSerializer +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import co.topl.daml.processEventAux -class TransferProcessor( +class TransferRequestProcessor( damlAppContext: DamlAppContext, toplContext: ToplContext ) extends AbstractProcessor(damlAppContext, toplContext) { - val logger = LoggerFactory.getLogger(classOf[TransferProcessor]) + val logger = LoggerFactory.getLogger(classOf[TransferRequestProcessor]) import toplContext.provider._ diff --git a/src/main/scala/co/topl/daml/processors/UnsignedTransferProcessor.scala b/src/main/scala/co/topl/daml/polys/processors/UnsignedTransferProcessor.scala similarity index 89% rename from src/main/scala/co/topl/daml/processors/UnsignedTransferProcessor.scala rename to src/main/scala/co/topl/daml/polys/processors/UnsignedTransferProcessor.scala index a2388c0..ac7c0e5 100644 --- a/src/main/scala/co/topl/daml/processors/UnsignedTransferProcessor.scala +++ b/src/main/scala/co/topl/daml/polys/processors/UnsignedTransferProcessor.scala @@ -1,27 +1,35 @@ -package co.topl.daml.processors +package co.topl.daml.polys.processors -import java.util.stream -import com.daml.ledger.javaapi.data.Command -import com.daml.ledger.javaapi.data.CreatedEvent -import co.topl.daml.api.model.topl.transfer.UnsignedTransfer -import co.topl.attestation.keyManagement.{KeyRing, KeyfileCurve25519, KeyfileCurve25519Companion, PrivateKeyCurve25519} -import co.topl.client.Brambl +import cats.data.EitherT +import co.topl.akkahttprpc.InvalidParametersError import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure import co.topl.attestation.Address -import io.circe.parser.parse -import scala.io.Source -import java.io.File -import cats.data.EitherT -import co.topl.utils.StringDataTypes import co.topl.attestation.AddressCodec.implicits._ -import scodec.bits._ -import co.topl.akkahttprpc.RpcErrorFailure -import co.topl.akkahttprpc.InvalidParametersError -import io.circe.DecodingFailure -import scala.concurrent.Future +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.processEventAux import co.topl.modifier.transaction.PolyTransfer import co.topl.modifier.transaction.serialization.PolyTransferSerializer +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.util.stream +import scala.concurrent.Future +import scala.io.Source class UnsignedTransferProcessor( damlAppContext: DamlAppContext, From 7528e62fa92ecdb7653350e9feecb0de6cbc60f0 Mon Sep 17 00:00:00 2001 From: Edmundo Lopez Bobeda Date: Wed, 7 Sep 2022 13:33:21 -0400 Subject: [PATCH 2/5] Add support for minting assets (Java/Scala Code). This version works but does minimal validations. One could say that it is in POC level. --- .../BoxPreservingAssetTransaction.scala | 103 -------------- .../AssetMintingRequestProcessor.scala | 128 +++++++++++------- .../SignedMintingRequestProcessor.scala | 6 +- .../UnsignedMintingRequestProcessor.scala | 3 +- 4 files changed, 84 insertions(+), 156 deletions(-) delete mode 100644 src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala diff --git a/src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala b/src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala deleted file mode 100644 index add8a7a..0000000 --- a/src/main/scala/co/topl/daml/assets/BoxPreservingAssetTransaction.scala +++ /dev/null @@ -1,103 +0,0 @@ -package co.topl.daml.assets - -import co.topl.attestation.Address -import co.topl.attestation.EvidenceProducer -import co.topl.attestation.Proof -import co.topl.attestation.Proposition -import co.topl.crypto.hash.blake2b256 -import co.topl.modifier.box.Box -import co.topl.modifier.box.SimpleValue -import co.topl.modifier.box.TokenBox -import co.topl.modifier.box.TokenValueHolder -import co.topl.modifier.transaction.Transaction -import co.topl.modifier.transaction.TransferTransaction -import co.topl.utils.Identifiable -import co.topl.utils.Int128 -import co.topl.utils.StringDataTypes.Latin1Data -import com.google.common.primitives.Ints -import com.google.common.primitives.Longs - -import scala.collection.immutable.ListMap -import co.topl.modifier.box.AssetValue -import co.topl.modifier.transaction.AssetTransfer -import co.topl.modifier.box.AssetBox -import co.topl.modifier.box.PolyBox - -class BoxPreservingAssetTransaction[ - P <: Proposition: EvidenceProducer: Identifiable -]( - override val from: IndexedSeq[(Address, Box.Nonce)], - override val to: IndexedSeq[(Address, TokenValueHolder)], - override val attestation: ListMap[P, Proof[P]], - override val fee: Int128, - override val timestamp: Long, - override val data: Option[Latin1Data] = None, - override val minting: Boolean = false, - val mintedAsset: AssetValue, - val previousAssets: IndexedSeq[(Address, TokenValueHolder)] -) extends AssetTransfer(from, to, attestation, fee, timestamp, data, minting) { - - val (asseetFeeOutputParams, assetCoinOutputParams) = - calculateBoxNonce[TokenValueHolder](mintedAsset, previousAssets) - - val assetCoinOutput: Iterable[AssetBox] = - assetCoinOutputParams.map { - case TransferTransaction.BoxParams(evi, nonce, value: AssetValue) => - AssetBox(evi, nonce, value) - case TransferTransaction.BoxParams(_, _, value) => - throw new IllegalArgumentException(s"AssetTransfer Coin output params contained invalid value=$value") - } - - val assetFeeChangeOutput: PolyBox = - PolyBox(asseetFeeOutputParams.evidence, asseetFeeOutputParams.nonce, asseetFeeOutputParams.value) - - override val newBoxes: Iterable[TokenBox[TokenValueHolder]] = { - // this only creates an output if the value of the output boxes is non-zero - val recipientCoinOutput: Iterable[AssetBox] = assetCoinOutput.filter(_.value.quantity > 0) - val hasRecipientOutput: Boolean = recipientCoinOutput.nonEmpty - val hasFeeChangeOutput: Boolean = assetFeeChangeOutput.value.quantity > 0 - - (hasRecipientOutput, hasFeeChangeOutput) match { - case (false, _) => Iterable() - case (true, false) => recipientCoinOutput - case (true, true) => Iterable(assetFeeChangeOutput) ++ recipientCoinOutput - } - } - - /** - * Computes a unique nonce value based on the transaction type and - * inputs and returns the details needed to create the output boxes for the transaction - */ - def calculateBoxNonce[T <: TokenValueHolder]( - pMintedAsset: T, - to: IndexedSeq[(Address, T)] - ): (TransferTransaction.BoxParams[SimpleValue], Iterable[TransferTransaction.BoxParams[T]]) = { - - // known input data (similar to messageToSign but without newBoxes since they aren't known yet) - val txIdPrefix = Transaction.identifier(this).typePrefix - val boxIdsToOpenAccumulator = this.boxIdsToOpen.foldLeft(Array[Byte]())((acc, x) => acc ++ x.hash.value) - val timestampBytes = Longs.toByteArray(this.timestamp) - val feeBytes = this.fee.toByteArray - - val inputBytes = - Array(txIdPrefix) ++ boxIdsToOpenAccumulator ++ timestampBytes ++ feeBytes - - val calcNonce: Int => Box.Nonce = (index: Int) => { - val digest = blake2b256.hash(inputBytes ++ Ints.toByteArray(index)) - Transaction.nonceFromDigest(digest) - } - - val feeChangeParams = - TransferTransaction.BoxParams(this.to.head._1.evidence, calcNonce(0), SimpleValue(this.to.head._2.quantity)) - - val coinOutputParams: IndexedSeq[TransferTransaction.BoxParams[T]] = - (to - .appended((this.to(0)._1, (pMintedAsset)))) - .zipWithIndex - .map { case ((addr, value), idx) => - TransferTransaction.BoxParams(addr.evidence, calcNonce(idx + 1), value) - } - (feeChangeParams, coinOutputParams) - } - -} diff --git a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala index bbf8b0a..b48367f 100644 --- a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala +++ b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala @@ -3,10 +3,9 @@ package co.topl.daml.assets.processors import cats.data.EitherT import cats.data.NonEmptyChain import co.topl.akkahttprpc.implicits.client.rpcToClient -import co.topl.attestation.Address import co.topl.attestation.AddressCodec.implicits._ -import co.topl.attestation.Proposition -import co.topl.attestation.PublicKeyPropositionCurve25519 +import co.topl.attestation.PublicKeyPropositionCurve25519._ +import co.topl.attestation._ import co.topl.daml.AbstractProcessor import co.topl.daml.DamlAppContext import co.topl.daml.ToplContext @@ -18,8 +17,6 @@ import co.topl.modifier.box.AssetValue import co.topl.modifier.box.SecurityRoot import co.topl.modifier.transaction.AssetTransfer import co.topl.modifier.transaction.builder.BoxSelectionAlgorithms -import co.topl.modifier.transaction.serialization.AssetTransferSerializer -import co.topl.modifier.transaction.serialization.PolyTransferSerializer import co.topl.rpc.ToplRpc import co.topl.rpc.implicits.client._ import co.topl.utils.IdiomaticScalaTransition.implicits.toValidatedOps @@ -31,15 +28,15 @@ import com.daml.ledger.javaapi.data.Command import com.daml.ledger.javaapi.data.CreatedEvent import org.slf4j.LoggerFactory import scodec.bits.ByteVector -import co.topl.attestation._ import java.util.stream import scala.collection.JavaConverters._ +import scala.collection.immutable.ListMap import scala.concurrent.Await import ToplRpc.Transaction.RawAssetTransfer -import co.topl.daml.assets.BoxPreservingAssetTransaction -import scala.collection.immutable.ListMap +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.modifier.box.SimpleValue class AssetMintingRequestProcessor( damlAppContext: DamlAppContext, @@ -121,15 +118,25 @@ class AssetMintingRequestProcessor( AssetMintingRequest.fromValue( event.getArguments() ) - val params = createParams(assetMintingRequest) - val rawTransaction = - RawAssetTransfer.rpc(params).map { x => - x.rawTx - } + // val params = createParams(assetMintingRequest) + val address = assetMintingRequest.from.asScala.toSeq + .map(Base58Data.unsafe) + .map(_.decodeAddress.getOrThrow()) + val params = + ToplRpc.NodeView.Balances + .Params( + address.toList + ) + val balances = ToplRpc.NodeView.Balances + .rpc(params) + // .map { x => + // x.rawTx + // } + // val xxx: AssetTransfer = ??? import scala.concurrent.duration._ import scala.language.postfixOps Await.result( - rawTransaction.fold( + balances.fold( failure => { logger.info("Failed to obtain raw transaction from server.") logger.debug("Error: {}", failure) @@ -138,46 +145,73 @@ class AssetMintingRequestProcessor( .exerciseTransferRequest_Reject() ) }, - rawMintingRequest => { - val mintingRequest: AssetTransfer[PublicKeyPropositionCurve25519] = - new BoxPreservingAssetTransaction[PublicKeyPropositionCurve25519]( - rawMintingRequest.from, - rawMintingRequest.to, - ListMap(), - rawMintingRequest.fee, - rawMintingRequest.timestamp, - rawMintingRequest.data, - rawMintingRequest.minting, - AssetValue( - Int128(assetMintingRequest.to.asScala.head._2.intValue()), - AssetCode( - assetMintingRequest.assetCode.version.toByte, - Base58Data - .unsafe(assetMintingRequest.from.get(0)) - .decodeAddress - .getOrThrow(), - Latin1Data.fromData( - utf8StringToLatin1ByteArray(assetMintingRequest.assetCode.shortName) - ) - ), - assetMintingRequest.someCommitRoot - .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) - .orElse(SecurityRoot.empty), - assetMintingRequest.someMetadata - .map(x => - Option( + balance => { + // println("rawMintingRequest.to = " + rawMintingRequest.to) + // val mintingRequest: BoxPreservingAssetTransfer[PublicKeyPropositionCurve25519] = + // new BoxPreservingAssetTransfer[PublicKeyPropositionCurve25519]( + // rawMintingRequest.from, + // rawMintingRequest.to, + // ListMap(), + // rawMintingRequest.fee, + // rawMintingRequest.timestamp, + // rawMintingRequest.data, + // rawMintingRequest.minting + // ) + val to = + ( + Base58Data.unsafe(assetMintingRequest.to.get(0)._1).decodeAddress.getOrThrow(), + SimpleValue( + balance.values.map(_.Boxes.PolyBox.head.value.quantity).head - Int128(assetMintingRequest.fee) + ) + ) :: assetMintingRequest.to.asScala.toSeq + .map(x => + ( + Base58Data.unsafe(x._1).decodeAddress.getOrThrow(), + AssetValue( + Int128(x._2.intValue()), + AssetCode( + assetMintingRequest.assetCode.version.toByte, + Base58Data + .unsafe(assetMintingRequest.from.get(0)) + .decodeAddress + .getOrThrow(), Latin1Data.fromData( - utf8StringToLatin1ByteArray(x) + utf8StringToLatin1ByteArray(assetMintingRequest.assetCode.shortName) ) - ) + ), + assetMintingRequest.someCommitRoot + .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) + .orElse(SecurityRoot.empty), + assetMintingRequest.someMetadata + .map(x => + Option( + Latin1Data.fromData( + utf8StringToLatin1ByteArray(x) + ) + ) + ) + .orElse(None) ) - .orElse(None) + ) + ) + .toList + + val mintingRequest = AssetTransferSerializer.toBytes( + AssetTransfer( + balance.values.toList.flatMap(_.Boxes.PolyBox).map(x => (address.head, x.nonce)).toIndexedSeq, + to.toIndexedSeq, + ListMap(), + Int128( + assetMintingRequest.fee ), - IndexedSeq() + System.currentTimeMillis(), + None, + true ) + ) val encodedTx = ByteVector( - AssetTransferSerializer.toBytes(mintingRequest: AssetTransfer[PublicKeyPropositionCurve25519]) + mintingRequest ).toBase58 logger.info("Successfully generated raw transaction for contract {}.", mintingRequestContract.contractId) import io.circe.syntax._ diff --git a/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala index 3f36c8a..b88877e 100644 --- a/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala +++ b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala @@ -16,6 +16,7 @@ import co.topl.client.Brambl import co.topl.daml.AbstractProcessor import co.topl.daml.DamlAppContext import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.asset.SignedAssetMinting import co.topl.daml.api.model.topl.transfer.SignedTransfer import co.topl.daml.api.model.topl.transfer.UnsignedTransfer import co.topl.daml.api.model.topl.utils.SendStatus @@ -23,8 +24,6 @@ import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend import co.topl.daml.api.model.topl.utils.sendstatus.Pending import co.topl.daml.api.model.topl.utils.sendstatus.Sent import co.topl.daml.processEventAux -import co.topl.modifier.transaction.PolyTransfer -import co.topl.modifier.transaction.serialization.PolyTransferSerializer import co.topl.rpc.ToplRpc import co.topl.rpc.implicits.client._ import co.topl.utils.StringDataTypes @@ -44,7 +43,6 @@ import scala.concurrent.Await import scala.concurrent.Future import scala.io.Source import co.topl.modifier.transaction.serialization.AssetTransferSerializer -import co.topl.daml.api.model.topl.asset.SignedAssetMinting class SignedMintingRequestProcessor( damlAppContext: DamlAppContext, @@ -90,7 +88,7 @@ class SignedMintingRequestProcessor( failure => { logger.info("Failed to broadcast transaction to server.") logger.debug("Error: {}", failure) - // FIXME error handling + // FIXME: error handling stream.Stream.of( signedMintingRequestContract .exerciseSignedAssetMinting_Fail("Failed broadcast to server") diff --git a/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala index ff21032..18456b5 100644 --- a/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala +++ b/src/main/scala/co/topl/daml/assets/processors/UnsignedMintingRequestProcessor.scala @@ -16,9 +16,8 @@ import co.topl.daml.DamlAppContext import co.topl.daml.ToplContext import co.topl.daml.api.model.topl.asset.UnsignedAssetMinting import co.topl.daml.api.model.topl.transfer.UnsignedTransfer + import co.topl.daml.processEventAux -import co.topl.modifier.transaction.PolyTransfer -import co.topl.modifier.transaction.serialization.PolyTransferSerializer import co.topl.utils.StringDataTypes import com.daml.ledger.javaapi.data.Command import com.daml.ledger.javaapi.data.CreatedEvent From 9cff538751c4e99f4d62a48c3ea8298a67a22490 Mon Sep 17 00:00:00 2001 From: Edmundo Lopez Bobeda Date: Thu, 8 Sep 2022 10:51:04 -0400 Subject: [PATCH 3/5] Add minting and transfer processors for assets --- daml/Tests/Asset.daml | 2 + daml/Topl/Asset.daml | 8 + daml/Topl/Organization.daml | 5 +- .../java/co/topl/daml/AssetOperatorMain.java | 13 ++ .../AssetMintingRequestProcessor.scala | 106 ++--------- .../AssetTransferRequestProcessor.scala | 171 ++++++++++++++++++ .../SignedAssetTransferRequestProcessor.scala | 161 +++++++++++++++++ .../SignedMintingRequestProcessor.scala | 29 ++- ...nsignedAssetTransferRequestProcessor.scala | 104 +++++++++++ 9 files changed, 504 insertions(+), 95 deletions(-) create mode 100644 src/main/scala/co/topl/daml/assets/processors/AssetTransferRequestProcessor.scala create mode 100644 src/main/scala/co/topl/daml/assets/processors/SignedAssetTransferRequestProcessor.scala create mode 100644 src/main/scala/co/topl/daml/assets/processors/UnsignedAssetTransferRequestProcessor.scala diff --git a/daml/Tests/Asset.daml b/daml/Tests/Asset.daml index 8d3ae10..62a3bd4 100644 --- a/daml/Tests/Asset.daml +++ b/daml/Tests/Asset.daml @@ -39,6 +39,7 @@ module Tests.Asset where unsignedAssetMintingCid <- submit operator do exerciseCmd assetMintingRequestCid MintingRequest_Accept with txToSign = "ZZZZ" + boxNonce = 123 signedAssetMintingCreated <- submit operator do exerciseCmd unsignedAssetMintingCid UnsignedMinting_Sign with signedMintTx = "WWWWW" @@ -74,6 +75,7 @@ module Tests.Asset where unsignedAssetTransferCid <- submit operator do exerciseCmd assetTransferRequestCid AssetTransferRequest_Accept with txToSign = "ZZZZ" + newBoxNonce = 1234 signedAssetTransferCreated <- submit operator do exerciseCmd unsignedAssetTransferCid UnsignedAssetTransfer_Sign with signedTx = "WWWWW" diff --git a/daml/Topl/Asset.daml b/daml/Topl/Asset.daml index 71ad3a6..b439da5 100644 --- a/daml/Topl/Asset.daml +++ b/daml/Topl/Asset.daml @@ -40,6 +40,7 @@ module Topl.Asset where choice MintingRequest_Accept : UnsignedAssetMintingCid with txToSign : Text + boxNonce : Int controller operator do create UnsignedAssetMinting with @@ -66,6 +67,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int mintTxToSign : Text where @@ -99,6 +101,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int mintTxToSign : Text signedMintTx : Text @@ -155,15 +158,18 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int where signatory operator, requestor choice AssetTransferRequest_Accept : UnsignedAssetTransferRequestCid with txToSign : Text + newBoxNonce : Int controller operator do create UnsignedAssetTransferRequest with txToSign = txToSign + boxNonce = newBoxNonce .. choice AssetTransferRequest_Reject : () controller operator @@ -187,6 +193,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int txToSign : Text where @@ -222,6 +229,7 @@ module Topl.Asset where quantity : Int someCommitRoot : Optional Text someMetadata : Optional Text + boxNonce : Int fee : Int txToSign : Text signedTx : Text diff --git a/daml/Topl/Organization.daml b/daml/Topl/Organization.daml index 85572a9..bfcdd53 100644 --- a/daml/Topl/Organization.daml +++ b/daml/Topl/Organization.daml @@ -106,6 +106,7 @@ module Topl.Organization where quantity = signedAssetMinting.quantity someMetadata = signedAssetMinting.someMetadata someCommitRoot = signedAssetMinting.someCommitRoot + boxNonce = signedAssetMinting.boxNonce .. nonconsuming choice Organization_AddSignedAssetTransfer: AssetIouCid with @@ -124,6 +125,7 @@ module Topl.Organization where quantity = signedAssetTransfer.quantity someMetadata = signedAssetTransfer.someMetadata someCommitRoot = signedAssetTransfer.someCommitRoot + boxNonce = signedAssetTransfer.boxNonce .. @@ -239,7 +241,8 @@ module Topl.Organization where quantity : Int someMetadata : Optional Text assetCode : AssetCode - someCommitRoot : Optional Text + someCommitRoot : Optional Text + boxNonce : Int where signatory operator, organization.members diff --git a/src/main/java/co/topl/daml/AssetOperatorMain.java b/src/main/java/co/topl/daml/AssetOperatorMain.java index 4d7641a..a60b822 100644 --- a/src/main/java/co/topl/daml/AssetOperatorMain.java +++ b/src/main/java/co/topl/daml/AssetOperatorMain.java @@ -15,6 +15,9 @@ import co.topl.daml.assets.processors.AssetMintingRequestProcessor; import co.topl.daml.assets.processors.UnsignedMintingRequestProcessor; import co.topl.daml.assets.processors.SignedMintingRequestProcessor; +import co.topl.daml.assets.processors.AssetTransferRequestProcessor; +import co.topl.daml.assets.processors.UnsignedAssetTransferRequestProcessor; +import co.topl.daml.assets.processors.SignedAssetTransferRequestProcessor; import akka.actor.ActorSystem; import co.topl.client.Provider; import akka.http.javadsl.model.Uri; @@ -65,5 +68,15 @@ public static void main(String[] args) { SignedMintingRequestProcessor signedMintingRequestProcessor = new SignedMintingRequestProcessor(damlAppContext, toplContext); transactions.forEach(signedMintingRequestProcessor::processTransaction); + + AssetTransferRequestProcessor assetTransferRequestProcessor = new AssetTransferRequestProcessor(damlAppContext, + toplContext); + transactions.forEach(assetTransferRequestProcessor::processTransaction); + UnsignedAssetTransferRequestProcessor unsignedTransferRequestProcessor = new UnsignedAssetTransferRequestProcessor( + damlAppContext, toplContext, keyfile, password); + transactions.forEach(unsignedTransferRequestProcessor::processTransaction); + SignedAssetTransferRequestProcessor signedTransferRequestProcessor = new SignedAssetTransferRequestProcessor( + damlAppContext, toplContext); + transactions.forEach(signedTransferRequestProcessor::processTransaction); } } diff --git a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala index b48367f..c655eb2 100644 --- a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala +++ b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala @@ -47,67 +47,6 @@ class AssetMintingRequestProcessor( import toplContext.provider._ - def createParams(assetMintingRequest: AssetMintingRequest): RawAssetTransfer.Params = - RawAssetTransfer.Params( - propositionType = - PublicKeyPropositionCurve25519.typeString, // required fixed string for now, exciting possibilities in the future! - sender = NonEmptyChain - .fromSeq( - assetMintingRequest.from.asScala.toSeq - .map(Base58Data.unsafe) - .map(_.decodeAddress.getOrThrow()) - ) - .get, // Set of addresses whose state you want to use for the transaction - recipients = NonEmptyChain - .fromSeq( - assetMintingRequest.to.asScala.toSeq.map(x => - ( - Base58Data.unsafe(x._1).decodeAddress.getOrThrow(), - AssetValue( - Int128(x._2.intValue()), - AssetCode( - assetMintingRequest.assetCode.version.toByte, - Base58Data - .unsafe(assetMintingRequest.from.get(0)) - .decodeAddress - .getOrThrow(), - Latin1Data.fromData( - utf8StringToLatin1ByteArray(assetMintingRequest.assetCode.shortName) - ) - ), - assetMintingRequest.someCommitRoot - .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) - .orElse(SecurityRoot.empty), - assetMintingRequest.someMetadata - .map(x => - Option( - Latin1Data.fromData( - utf8StringToLatin1ByteArray(x) - ) - ) - ) - .orElse(None) - ) - ) - ) - ) - .get, // Chain of (Recipients, Value) tuples that represent the output boxes - fee = Int128( - assetMintingRequest.fee - ), // fee to be paid to the network for the transaction (unit is nanoPoly) - changeAddress = Base58Data - .unsafe(assetMintingRequest.changeAddress) - .decodeAddress - .getOrThrow(), // who will get ALL the change from the transaction? - consolidationAddress = Base58Data - .unsafe(assetMintingRequest.changeAddress) - .decodeAddress - .getOrThrow(), // who will get ALL the change from the transaction? - minting = true, - data = None, // upto 128 Latin-1 encoded characters of optional data, - boxSelectionAlgorithm = BoxSelectionAlgorithms.All - ) - def processEvent( workflowsId: String, event: CreatedEvent @@ -118,7 +57,6 @@ class AssetMintingRequestProcessor( AssetMintingRequest.fromValue( event.getArguments() ) - // val params = createParams(assetMintingRequest) val address = assetMintingRequest.from.asScala.toSeq .map(Base58Data.unsafe) .map(_.decodeAddress.getOrThrow()) @@ -127,12 +65,7 @@ class AssetMintingRequestProcessor( .Params( address.toList ) - val balances = ToplRpc.NodeView.Balances - .rpc(params) - // .map { x => - // x.rawTx - // } - // val xxx: AssetTransfer = ??? + val balances = ToplRpc.NodeView.Balances.rpc(params) import scala.concurrent.duration._ import scala.language.postfixOps Await.result( @@ -146,17 +79,6 @@ class AssetMintingRequestProcessor( ) }, balance => { - // println("rawMintingRequest.to = " + rawMintingRequest.to) - // val mintingRequest: BoxPreservingAssetTransfer[PublicKeyPropositionCurve25519] = - // new BoxPreservingAssetTransfer[PublicKeyPropositionCurve25519]( - // rawMintingRequest.from, - // rawMintingRequest.to, - // ListMap(), - // rawMintingRequest.fee, - // rawMintingRequest.timestamp, - // rawMintingRequest.data, - // rawMintingRequest.minting - // ) val to = ( Base58Data.unsafe(assetMintingRequest.to.get(0)._1).decodeAddress.getOrThrow(), @@ -196,18 +118,19 @@ class AssetMintingRequestProcessor( ) .toList + val assetTransfer = AssetTransfer( + balance.values.toList.flatMap(_.Boxes.PolyBox).map(x => (address.head, x.nonce)).toIndexedSeq, + to.toIndexedSeq, + ListMap(), + Int128( + assetMintingRequest.fee + ), + System.currentTimeMillis(), + None, + true + ) val mintingRequest = AssetTransferSerializer.toBytes( - AssetTransfer( - balance.values.toList.flatMap(_.Boxes.PolyBox).map(x => (address.head, x.nonce)).toIndexedSeq, - to.toIndexedSeq, - ListMap(), - Int128( - assetMintingRequest.fee - ), - System.currentTimeMillis(), - None, - true - ) + assetTransfer ) val encodedTx = ByteVector( @@ -224,7 +147,8 @@ class AssetMintingRequestProcessor( stream.Stream.of( mintingRequestContract .exerciseMintingRequest_Accept( - encodedTx + encodedTx, + assetTransfer.newBoxes.toList.reverse.head.nonce ) ) } diff --git a/src/main/scala/co/topl/daml/assets/processors/AssetTransferRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/AssetTransferRequestProcessor.scala new file mode 100644 index 0000000..d2dcd4d --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/AssetTransferRequestProcessor.scala @@ -0,0 +1,171 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.data.NonEmptyChain +import co.topl.akkahttprpc.implicits.client.rpcToClient +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.PublicKeyPropositionCurve25519._ +import co.topl.attestation._ +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.processEventAux +import co.topl.daml.utf8StringToLatin1ByteArray +import co.topl.modifier.box.AssetCode +import co.topl.modifier.box.AssetValue +import co.topl.modifier.box.SecurityRoot +import co.topl.modifier.transaction.AssetTransfer +import co.topl.modifier.transaction.builder.BoxSelectionAlgorithms +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.IdiomaticScalaTransition.implicits.toValidatedOps +import co.topl.utils.Int128 +import co.topl.utils.StringDataTypes +import co.topl.utils.StringDataTypes.Base58Data +import co.topl.utils.StringDataTypes.Latin1Data +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import org.slf4j.LoggerFactory +import scodec.bits.ByteVector + +import java.util.stream +import scala.collection.JavaConverters._ +import scala.collection.immutable.ListMap +import scala.concurrent.Await + +import ToplRpc.Transaction.RawAssetTransfer +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.modifier.box.SimpleValue +import co.topl.daml.api.model.topl.asset.AssetTransferRequest + +class AssetTransferRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + val logger = LoggerFactory.getLogger(classOf[AssetTransferRequestProcessor]) + + import toplContext.provider._ + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(AssetTransferRequest.TEMPLATE_ID, event) { + val transferRequestContract = + AssetTransferRequest.Contract.fromCreatedEvent(event).id + val assetTransferRequest = + AssetTransferRequest.fromValue( + event.getArguments() + ) + val address = assetTransferRequest.from.asScala.toSeq + .map(Base58Data.unsafe) + .map(_.decodeAddress.getOrThrow()) + val params = + ToplRpc.NodeView.Balances + .Params( + address.toList + ) + val balances = ToplRpc.NodeView.Balances.rpc(params) + + import scala.concurrent.duration._ + import scala.language.postfixOps + Await.result( + balances.fold( + failure => { + logger.info("Failed to obtain raw transaction from server.") + logger.debug("Error: {}", failure) + stream.Stream.of( + transferRequestContract + .exerciseAssetTransferRequest_Reject() + ) + }, + balance => { + val to = + ( + Base58Data.unsafe(assetTransferRequest.to.get(0)._1).decodeAddress.getOrThrow(), + SimpleValue( + balance.values.map(_.Boxes.PolyBox.head.value.quantity).head - Int128(assetTransferRequest.fee) + ) + ) :: assetTransferRequest.to.asScala.toSeq + .map(x => + ( + Base58Data.unsafe(x._1).decodeAddress.getOrThrow(), + AssetValue( + Int128(x._2.intValue()), + AssetCode( + assetTransferRequest.assetCode.version.toByte, + Base58Data + .unsafe(assetTransferRequest.from.get(0)) + .decodeAddress + .getOrThrow(), + Latin1Data.fromData( + utf8StringToLatin1ByteArray(assetTransferRequest.assetCode.shortName) + ) + ), + assetTransferRequest.someCommitRoot + .map(x => SecurityRoot.fromBase58(Base58Data.unsafe(x))) + .orElse(SecurityRoot.empty), + assetTransferRequest.someMetadata + .map(x => + Option( + Latin1Data.fromData( + utf8StringToLatin1ByteArray(x) + ) + ) + ) + .orElse(None) + ) + ) + ) + .toList + + val assetTransfer = AssetTransfer( + balance.values.toList + .flatMap(_.Boxes.PolyBox) + .map(x => (address.head, x.nonce)) + .toIndexedSeq + .++( + balance.values.toList + .flatMap(_.Boxes.AssetBox) + .filter(_.nonce == assetTransferRequest.boxNonce) + .map(x => (address.head, x.nonce)) + .toIndexedSeq + ), + to.toIndexedSeq, + ListMap(), + Int128( + assetTransferRequest.fee + ), + System.currentTimeMillis(), + None, + false + ) + val transferRequest = AssetTransferSerializer.toBytes( + assetTransfer + ) + val encodedTx = + ByteVector( + transferRequest + ).toBase58 + logger.info("Successfully generated raw transaction for contract {}.", transferRequestContract.contractId) + import io.circe.syntax._ + logger.info("The returned json: {}", transferRequest.asJson) + logger.debug( + "Encoded transaction: {}", + encodedTx + ) + + stream.Stream.of( + transferRequestContract + .exerciseAssetTransferRequest_Accept( + encodedTx, + assetTransfer.newBoxes.toList.reverse.head.nonce + ) + ) + } + ), + 3 second + ) + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/SignedAssetTransferRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/SignedAssetTransferRequestProcessor.scala new file mode 100644 index 0000000..9574018 --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/SignedAssetTransferRequestProcessor.scala @@ -0,0 +1,161 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import cats.implicits._ +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.akkahttprpc.implicits.client._ +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.da.types +import co.topl.daml.api.model.topl.asset.SignedAssetMinting +import co.topl.daml.api.model.topl.asset.SignedAssetTransfer +import co.topl.daml.api.model.topl.asset.SignedAssetTransfer_Confirm +import co.topl.daml.api.model.topl.organization.Organization +import co.topl.daml.api.model.topl.transfer.SignedTransfer +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer +import co.topl.daml.api.model.topl.utils.SendStatus +import co.topl.daml.api.model.topl.utils.sendstatus.Confirmed +import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend +import co.topl.daml.api.model.topl.utils.sendstatus.Pending +import co.topl.daml.api.model.topl.utils.sendstatus.Sent +import co.topl.daml.processEventAux +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.rpc.ToplRpc +import co.topl.rpc.implicits.client._ +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse +import io.circe.syntax._ +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.time.Instant +import java.util.Optional +import java.util.stream +import scala.concurrent.Await +import scala.concurrent.Future +import scala.io.Source + +class SignedAssetTransferRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + implicit val jsonDecoder = co.topl.modifier.transaction.Transaction.jsonDecoder + + val logger = LoggerFactory.getLogger(classOf[SignedAssetTransferRequestProcessor]) + import toplContext.provider._ + + private def lift[E, A](a: Either[E, A]) = EitherT[Future, E, A](Future(a)) + + private def handlePending( + signedTransferRequest: SignedAssetTransfer, + signedTransferRequestContract: SignedAssetTransfer.ContractId + ): stream.Stream[Command] = { + val result = for { + transactionAsBytes <- + lift( + ByteVector + .fromBase58(signedTransferRequest.signedTx) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid signed tx from base 58", Nil)))) + ) + _ = logger.debug("transactionAsBytes = {}", transactionAsBytes) + signedTx <- lift( + AssetTransferSerializer + .parseBytes(transactionAsBytes) + .toEither + .left + .map(_ => RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid bytes for transaction", Nil)))) + ) + _ = logger.debug("from address = {}", signedTx.from.head._1.toString()) + broadcastResult <- ToplRpc.Transaction.BroadcastTx.rpc(ToplRpc.Transaction.BroadcastTx.Params(signedTx)) + } yield broadcastResult + import scala.concurrent.duration._ + import scala.language.postfixOps + Await + .result(result.value, 3 second) + .fold( + failure => { + logger.info("Failed to broadcast transaction to server.") + logger.debug("Error: {}", failure) + // FIXME: error handling + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Fail("Failed broadcast to server") + ) + }, + success => { + logger.info("Successfully broadcasted transaction to network.") + logger.debug( + "Server answer: {}", + success.asJson + ) + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Sent(new Sent(Instant.now(), damlAppContext.appId, Optional.empty())) + ) + } + ) + } + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(SignedAssetTransfer.TEMPLATE_ID, event) { + val signedTransferRequestContract = + SignedAssetTransfer.Contract.fromCreatedEvent(event).id + val signedTransferRequest = + SignedAssetTransfer.fromValue( + event.getArguments() + ) + if (signedTransferRequest.sendStatus.isInstanceOf[Pending]) { + handlePending(signedTransferRequest, signedTransferRequestContract) + } else if (signedTransferRequest.sendStatus.isInstanceOf[FailedToSend]) { + logger.error("Failed to send contract.") + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Archive() + ) + } else if (signedTransferRequest.sendStatus.isInstanceOf[Sent]) { + logger.info("Successfully sent.") + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Confirm( + new SignedAssetTransfer_Confirm( + (signedTransferRequest.sendStatus.asInstanceOf[Sent]).txHash.orElseGet(() => ""), + 1 + ) + ) + ) + } else if (signedTransferRequest.sendStatus.isInstanceOf[Confirmed]) { + logger.info("Successfully confirmed.") + + stream.Stream.of( + Organization + .byKey(new types.Tuple2(signedTransferRequest.operator, signedTransferRequest.someOrgName.get())) + .exerciseOrganization_AddSignedAssetTransfer(signedTransferRequestContract) + ) + } else { + stream.Stream.of( + signedTransferRequestContract + .exerciseSignedAssetTransfer_Archive() + ) + } + } + +} diff --git a/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala index b88877e..5506223 100644 --- a/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala +++ b/src/main/scala/co/topl/daml/assets/processors/SignedMintingRequestProcessor.scala @@ -16,19 +16,28 @@ import co.topl.client.Brambl import co.topl.daml.AbstractProcessor import co.topl.daml.DamlAppContext import co.topl.daml.ToplContext +import co.topl.daml.api.model.da.types import co.topl.daml.api.model.topl.asset.SignedAssetMinting +import co.topl.daml.api.model.topl.asset.SignedAssetMinting_Confirm +import co.topl.daml.api.model.topl.organization.Organization import co.topl.daml.api.model.topl.transfer.SignedTransfer import co.topl.daml.api.model.topl.transfer.UnsignedTransfer import co.topl.daml.api.model.topl.utils.SendStatus +import co.topl.daml.api.model.topl.utils.sendstatus.Confirmed import co.topl.daml.api.model.topl.utils.sendstatus.FailedToSend import co.topl.daml.api.model.topl.utils.sendstatus.Pending import co.topl.daml.api.model.topl.utils.sendstatus.Sent import co.topl.daml.processEventAux +import co.topl.modifier.transaction.serialization.AssetTransferSerializer import co.topl.rpc.ToplRpc import co.topl.rpc.implicits.client._ import co.topl.utils.StringDataTypes +import com.daml.ledger.api.v1.CommandsOuterClass +import com.daml.ledger.api.v1.TransactionFilterOuterClass +import com.daml.ledger.api.v1.ValueOuterClass.Identifier import com.daml.ledger.javaapi.data.Command import com.daml.ledger.javaapi.data.CreatedEvent +import com.daml.ledger.javaapi.data.TransactionFilter import io.circe.DecodingFailure import io.circe.parser.parse import io.circe.syntax._ @@ -42,7 +51,6 @@ import java.util.stream import scala.concurrent.Await import scala.concurrent.Future import scala.io.Source -import co.topl.modifier.transaction.serialization.AssetTransferSerializer class SignedMintingRequestProcessor( damlAppContext: DamlAppContext, @@ -102,7 +110,9 @@ class SignedMintingRequestProcessor( ) stream.Stream.of( signedMintingRequestContract - .exerciseSignedAssetMinting_Sent(new Sent(Instant.now(), damlAppContext.appId, Optional.empty())) + .exerciseSignedAssetMinting_Sent( + new Sent(Instant.now(), damlAppContext.appId, Optional.of(success.id.toString())) + ) ) } ) @@ -130,7 +140,20 @@ class SignedMintingRequestProcessor( logger.info("Successfully sent.") stream.Stream.of( signedMintingRequestContract - .exerciseSignedAssetMinting_Archive() + .exerciseSignedAssetMinting_Confirm( + new SignedAssetMinting_Confirm( + (signedMintingRequest.sendStatus.asInstanceOf[Sent]).txHash.orElseGet(() => ""), + 1 + ) + ) + ) + } else if (signedMintingRequest.sendStatus.isInstanceOf[Confirmed]) { + logger.info("Successfully confirmed.") + + stream.Stream.of( + Organization + .byKey(new types.Tuple2(signedMintingRequest.operator, signedMintingRequest.someOrgName.get())) + .exerciseOrganization_AddSignedAssetMinting(signedMintingRequestContract) ) } else { stream.Stream.of( diff --git a/src/main/scala/co/topl/daml/assets/processors/UnsignedAssetTransferRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/UnsignedAssetTransferRequestProcessor.scala new file mode 100644 index 0000000..4951a7a --- /dev/null +++ b/src/main/scala/co/topl/daml/assets/processors/UnsignedAssetTransferRequestProcessor.scala @@ -0,0 +1,104 @@ +package co.topl.daml.assets.processors + +import cats.data.EitherT +import co.topl.akkahttprpc.InvalidParametersError +import co.topl.akkahttprpc.RpcClientFailure +import co.topl.akkahttprpc.RpcErrorFailure +import co.topl.attestation.Address +import co.topl.attestation.AddressCodec.implicits._ +import co.topl.attestation.keyManagement.KeyRing +import co.topl.attestation.keyManagement.KeyfileCurve25519 +import co.topl.attestation.keyManagement.KeyfileCurve25519Companion +import co.topl.attestation.keyManagement.PrivateKeyCurve25519 +import co.topl.client.Brambl +import co.topl.daml.AbstractProcessor +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.api.model.topl.asset.UnsignedAssetMinting +import co.topl.daml.api.model.topl.transfer.UnsignedTransfer + +import co.topl.daml.processEventAux +import co.topl.utils.StringDataTypes +import com.daml.ledger.javaapi.data.Command +import com.daml.ledger.javaapi.data.CreatedEvent +import io.circe.DecodingFailure +import io.circe.parser.parse +import org.slf4j.LoggerFactory +import scodec.bits._ + +import java.io.File +import java.util.stream +import scala.concurrent.Future +import scala.io.Source +import co.topl.modifier.transaction.serialization.AssetTransferSerializer +import co.topl.daml.api.model.topl.asset.UnsignedAssetTransferRequest + +class UnsignedAssetTransferRequestProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext, + fileName: String, + password: String +) extends AbstractProcessor(damlAppContext, toplContext) { + + implicit val networkPrefix = toplContext.provider.networkPrefix + + val logger = LoggerFactory.getLogger(classOf[UnsignedAssetTransferRequestProcessor]) + + val keyRing: KeyRing[PrivateKeyCurve25519, KeyfileCurve25519] = + KeyRing.empty[PrivateKeyCurve25519, KeyfileCurve25519]()( + toplContext.provider.networkPrefix, + PrivateKeyCurve25519.secretGenerator, + KeyfileCurve25519Companion + ) + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(UnsignedAssetTransferRequest.TEMPLATE_ID, event) { + val unsidgnedTransferRequestContract = + UnsignedAssetTransferRequest.Contract.fromCreatedEvent(event).id + val unsidgnedTransferRequest = + UnsignedAssetTransferRequest.fromValue( + event.getArguments() + ) + val keyfile = Source.fromFile(new File(fileName)).getLines().mkString("").mkString + (for { + jsonKey <- parse(keyfile) + address <- Brambl.importCurve25519JsonToKeyRing(jsonKey, password, keyRing) + msg2Sign <- ByteVector + .fromBase58(unsidgnedTransferRequest.txToSign) + .map(_.toArray) + .toRight(RpcErrorFailure(InvalidParametersError(DecodingFailure("Invalid contract", Nil)))) + rawTx <- AssetTransferSerializer.parseBytes(msg2Sign).toEither + signedTx <- Right { + val signFunc = (addr: Address) => keyRing.generateAttestation(addr)(rawTx.messageToSign) + logger.debug("listOfAddresses = {}", keyRing.addresses) + val signatures = keyRing.addresses.map(signFunc).reduce(_ ++ _) + rawTx.copy(attestation = signatures) + } + } yield signedTx).fold( + failure => { + logger.info("Failed to sign transaction.") + logger.debug("Error: {}", failure) + stream.Stream.of( + unsidgnedTransferRequestContract + .exerciseUnsignedAssetTransfer_Archive() + ) + }, + signedTx => { + val signedTxString = ByteVector(AssetTransferSerializer.toBytes(signedTx)).toBase58 + logger.info("Successfully signed transaction for contract {}.", unsidgnedTransferRequestContract.contractId) + logger.debug("signedTx = {}", signedTx) + logger.debug( + "Encoded transaction: {}", + signedTxString + ) + stream.Stream.of( + unsidgnedTransferRequestContract + .exerciseUnsignedAssetTransfer_Sign(signedTxString) + ) + } + ) + } + +} From e5f83a76d17a5d83ad3aa28fd72b0b2d7592f9f9 Mon Sep 17 00:00:00 2001 From: Edmundo Lopez Bobeda Date: Tue, 13 Sep 2022 16:54:51 -0400 Subject: [PATCH 4/5] Add processors needed for demo app --- daml/Main.daml | 78 +++++++++---------- pom.xml | 2 +- src/main/scala/co/topl/daml/Test.worksheet.sc | 44 ++++++++++- .../daml/operator/AssetIouProcessor.scala | 34 ++++++++ .../MembershipAcceptanceProcessor.scala | 31 ++++++++ .../operator/MembershipOfferProcessor.scala | 32 ++++++++ 6 files changed, 179 insertions(+), 42 deletions(-) create mode 100644 src/main/scala/co/topl/daml/operator/AssetIouProcessor.scala create mode 100644 src/main/scala/co/topl/daml/operator/MembershipAcceptanceProcessor.scala create mode 100644 src/main/scala/co/topl/daml/operator/MembershipOfferProcessor.scala diff --git a/daml/Main.daml b/daml/Main.daml index 1d5b8a6..8a13296 100644 --- a/daml/Main.daml +++ b/daml/Main.daml @@ -11,42 +11,42 @@ import DA.Optional initialize : Script [Party] initialize = do - operator <- allocateParty "Operator" - operatorId <- validateUserId "operator" - alice <- allocateParty "Alice" - aliceId <- validateUserId "alice" - bob <- allocateParty "Bob" - bobId <- validateUserId "bob" - eve <- allocateParty "Eve" - eveId <- validateUserId "eve" - operatorCid <- submit operator do - createCmd Operator with operator = operator, address = "AUANVY6RqbJtTnQS1AFTQBjXMFYDknhV8NEixHFLmeZynMxVbp64" - orgCid <- submit operator do - exerciseCmd operatorCid Operator_CreateOrganization - with orgName = "Topl" - membershipOfferCid <- submit operator do - exerciseCmd orgCid Organization_InviteMember - with - invitee = alice - membershipAcceptance <- submit alice do - exerciseCmd membershipOfferCid Membershp_Accept - submit operator do - exerciseCmd membershipAcceptance AddUserToOrganization - someOrg <- queryContractKey @Organization operator (operator, "Topl") - membershipOfferCid <- submit operator do - exerciseCmd (fromSome someOrg)._1 Organization_InviteMember - with invitee = bob - membershipAcceptance <- submit bob do - exerciseCmd membershipOfferCid Membershp_Accept - submit operator do - exerciseCmd membershipAcceptance AddUserToOrganization - userInvitationCid <- submit operator do - exerciseCmd operatorCid Operator_InviteUser - with user = alice - aliceUserCid <- submit alice do - exerciseCmd userInvitationCid UserInvitation_Accept - createUser (Daml.Script.User aliceId (Some alice)) [CanActAs alice] - createUser (Daml.Script.User operatorId (Some operator)) [CanActAs operator] - createUser (Daml.Script.User bobId (Some bob)) [CanActAs bob] - createUser (Daml.Script.User eveId (Some eve)) [CanActAs eve] - pure [alice] \ No newline at end of file + -- operator <- allocateParty "operator" + -- operatorId <- validateUserId "operator" + -- alice <- allocateParty "Alice" + -- aliceId <- validateUserId "alice" + -- bob <- allocateParty "Bob" + -- bobId <- validateUserId "bob" + -- eve <- allocateParty "Eve" + -- eveId <- validateUserId "eve" + -- operatorCid <- submit operator do + -- createCmd Operator with operator = operator, address = "AUANVY6RqbJtTnQS1AFTQBjXMFYDknhV8NEixHFLmeZynMxVbp64" + -- orgCid <- submit operator do + -- exerciseCmd operatorCid Operator_CreateOrganization + -- with orgName = "Topl" + -- membershipOfferCid <- submit operator do + -- exerciseCmd orgCid Organization_InviteMember + -- with + -- invitee = alice + -- membershipAcceptance <- submit alice do + -- exerciseCmd membershipOfferCid Membershp_Accept + -- submit operator do + -- exerciseCmd membershipAcceptance AddUserToOrganization + -- someOrg <- queryContractKey @Organization operator (operator, "Topl") + -- membershipOfferCid <- submit operator do + -- exerciseCmd (fromSome someOrg)._1 Organization_InviteMember + -- with invitee = bob + -- membershipAcceptance <- submit bob do + -- exerciseCmd membershipOfferCid Membershp_Accept + -- submit operator do + -- exerciseCmd membershipAcceptance AddUserToOrganization + -- userInvitationCid <- submit operator do + -- exerciseCmd operatorCid Operator_InviteUser + -- with user = alice + -- aliceUserCid <- submit alice do + -- exerciseCmd userInvitationCid UserInvitation_Accept + -- createUser (Daml.Script.User aliceId (Some alice)) [CanActAs alice] + -- createUser (Daml.Script.User operatorId (Some operator)) [CanActAs operator] + -- createUser (Daml.Script.User bobId (Some bob)) [CanActAs bob] + -- createUser (Daml.Script.User eveId (Some eve)) [CanActAs eve] + pure [] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 440c9cb..a9ed3db 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ co.topl.daml topl-daml-api jar - 1.0.0 + 1.0.0-SNAPSHOT UTF-8 diff --git a/src/main/scala/co/topl/daml/Test.worksheet.sc b/src/main/scala/co/topl/daml/Test.worksheet.sc index 671bfdb..8697f92 100644 --- a/src/main/scala/co/topl/daml/Test.worksheet.sc +++ b/src/main/scala/co/topl/daml/Test.worksheet.sc @@ -1,3 +1,43 @@ -val x = List(5) +import scala.concurrent.Await +import akka.actor.ActorSystem +import akka.http.scaladsl.model.Uri +import co.topl.client.Provider +import co.topl.utils.StringDataTypes +import co.topl.attestation.Address +import co.topl.rpc.ToplRpc +import co.topl.attestation.AddressCodec.implicits._ +import cats.implicits._ +import co.topl.akkahttprpc.implicits.client._ +import co.topl.rpc.implicits.client._ -x.tail +import io.circe.syntax._ + +val uri = Uri("http://localhost:9085"); + +val provider = new Provider.PrivateTestNet(uri, "") + +import provider._ + +implicit val actorSystem = ActorSystem() +implicit val ec = actorSystem.dispatcher + +val params = ToplRpc.NodeView.Balances.Params( + List( + StringDataTypes.Base58Data + .unsafe("AUANVY6RqbJtTnQS1AFTQBjXMFYDknhV8NEixHFLmeZynMxVbp64") + .decodeAddress + .getOrElse(throw new IllegalArgumentException()) + ) +) + +val rpcCall = ToplRpc.NodeView.Balances.rpc(params) + +import scala.concurrent.duration._ +val res = Await.result(rpcCall.value, 3.second) + +res.fold( + fa => println("Failure"), + fb => + // println("Success!") + fb.values.map(_.Boxes.AssetBox.map(_.nonce)) +) diff --git a/src/main/scala/co/topl/daml/operator/AssetIouProcessor.scala b/src/main/scala/co/topl/daml/operator/AssetIouProcessor.scala new file mode 100644 index 0000000..003a7f8 --- /dev/null +++ b/src/main/scala/co/topl/daml/operator/AssetIouProcessor.scala @@ -0,0 +1,34 @@ +package co.topl.daml.operator + +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import com.daml.ledger.javaapi.data.CreatedEvent + +import java.util.stream +import com.daml.ledger.javaapi.data.Command +import co.topl.daml.processEventAux +import co.topl.daml.api.model.topl.organization.AssetIou +import co.topl.daml.api.model.da.types + +class AssetIouProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext, + lambda: java.util.function.BiConsumer[AssetIou, AssetIou.ContractId] +) extends AbstractProcessor(damlAppContext, toplContext) { + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(AssetIou.TEMPLATE_ID, event) { + val assetIouContract = + AssetIou.Contract.fromCreatedEvent(event).id + val assetIou = + AssetIou.fromValue( + event.getArguments() + ) + lambda.accept(assetIou, assetIouContract) + stream.Stream.empty() + } + +} diff --git a/src/main/scala/co/topl/daml/operator/MembershipAcceptanceProcessor.scala b/src/main/scala/co/topl/daml/operator/MembershipAcceptanceProcessor.scala new file mode 100644 index 0000000..851de47 --- /dev/null +++ b/src/main/scala/co/topl/daml/operator/MembershipAcceptanceProcessor.scala @@ -0,0 +1,31 @@ +package co.topl.daml.operator + +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import com.daml.ledger.javaapi.data.CreatedEvent + +import java.util.stream +import com.daml.ledger.javaapi.data.Command +import co.topl.daml.processEventAux +import co.topl.daml.api.model.topl.organization.MembershipAcceptance + +class MembershipAcceptanceProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(MembershipAcceptance.TEMPLATE_ID, event) { + val membershipAcceptanceContract = + MembershipAcceptance.Contract.fromCreatedEvent(event).id + + stream.Stream.of( + membershipAcceptanceContract + .exerciseAddUserToOrganization() + ) + } + +} diff --git a/src/main/scala/co/topl/daml/operator/MembershipOfferProcessor.scala b/src/main/scala/co/topl/daml/operator/MembershipOfferProcessor.scala new file mode 100644 index 0000000..b44778c --- /dev/null +++ b/src/main/scala/co/topl/daml/operator/MembershipOfferProcessor.scala @@ -0,0 +1,32 @@ +package co.topl.daml.operator + +import co.topl.daml.DamlAppContext +import co.topl.daml.ToplContext +import co.topl.daml.AbstractProcessor +import com.daml.ledger.javaapi.data.CreatedEvent + +import java.util.stream +import com.daml.ledger.javaapi.data.Command +import co.topl.daml.processEventAux +import co.topl.daml.api.model.topl.organization.MembershipAcceptance +import co.topl.daml.api.model.topl.organization.MembershipOffer + +class MembershipOfferProcessor( + damlAppContext: DamlAppContext, + toplContext: ToplContext +) extends AbstractProcessor(damlAppContext, toplContext) { + + def processEvent( + workflowsId: String, + event: CreatedEvent + ): stream.Stream[Command] = processEventAux(MembershipOffer.TEMPLATE_ID, event) { + val membershipOfferContract = + MembershipOffer.Contract.fromCreatedEvent(event).id + + stream.Stream.of( + membershipOfferContract + .exerciseMembershp_Accept() + ) + } + +} From 1dcb6afbf147ea60d7c04e19c44a0a245f16ab84 Mon Sep 17 00:00:00 2001 From: Edmundo Lopez Bobeda Date: Mon, 26 Sep 2022 17:01:54 -0400 Subject: [PATCH 5/5] Add FIXME to code that requires refactoring --- .../java/co/topl/daml/AssetOperatorMain.java | 3 ++ src/main/scala/co/topl/daml/Test.worksheet.sc | 43 ------------------- .../AssetMintingRequestProcessor.scala | 1 + 3 files changed, 4 insertions(+), 43 deletions(-) delete mode 100644 src/main/scala/co/topl/daml/Test.worksheet.sc diff --git a/src/main/java/co/topl/daml/AssetOperatorMain.java b/src/main/java/co/topl/daml/AssetOperatorMain.java index a60b822..af20956 100644 --- a/src/main/java/co/topl/daml/AssetOperatorMain.java +++ b/src/main/java/co/topl/daml/AssetOperatorMain.java @@ -28,6 +28,8 @@ public class AssetOperatorMain { + // FIXME: Divide into smaller methods. + // constants for referring to users with access to the parties public static final String OPERATOR_USER = "operator"; @@ -37,6 +39,7 @@ public class AssetOperatorMain { private static final Logger logger = LoggerFactory.getLogger(OperatorMain.class); public static void main(String[] args) { + // FIXME: add more robust handling of parameters. if (args.length < 4) { System.err.println("Usage: HOST PORT PROJECTID APIKEY KEYFILENAME KEYFILEPASSWORD"); System.exit(-1); diff --git a/src/main/scala/co/topl/daml/Test.worksheet.sc b/src/main/scala/co/topl/daml/Test.worksheet.sc deleted file mode 100644 index 8697f92..0000000 --- a/src/main/scala/co/topl/daml/Test.worksheet.sc +++ /dev/null @@ -1,43 +0,0 @@ -import scala.concurrent.Await -import akka.actor.ActorSystem -import akka.http.scaladsl.model.Uri -import co.topl.client.Provider -import co.topl.utils.StringDataTypes -import co.topl.attestation.Address -import co.topl.rpc.ToplRpc -import co.topl.attestation.AddressCodec.implicits._ -import cats.implicits._ -import co.topl.akkahttprpc.implicits.client._ -import co.topl.rpc.implicits.client._ - -import io.circe.syntax._ - -val uri = Uri("http://localhost:9085"); - -val provider = new Provider.PrivateTestNet(uri, "") - -import provider._ - -implicit val actorSystem = ActorSystem() -implicit val ec = actorSystem.dispatcher - -val params = ToplRpc.NodeView.Balances.Params( - List( - StringDataTypes.Base58Data - .unsafe("AUANVY6RqbJtTnQS1AFTQBjXMFYDknhV8NEixHFLmeZynMxVbp64") - .decodeAddress - .getOrElse(throw new IllegalArgumentException()) - ) -) - -val rpcCall = ToplRpc.NodeView.Balances.rpc(params) - -import scala.concurrent.duration._ -val res = Await.result(rpcCall.value, 3.second) - -res.fold( - fa => println("Failure"), - fb => - // println("Success!") - fb.values.map(_.Boxes.AssetBox.map(_.nonce)) -) diff --git a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala index c655eb2..ee57393 100644 --- a/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala +++ b/src/main/scala/co/topl/daml/assets/processors/AssetMintingRequestProcessor.scala @@ -47,6 +47,7 @@ class AssetMintingRequestProcessor( import toplContext.provider._ + // FIXME: improve readbility by breaking into smaller methods def processEvent( workflowsId: String, event: CreatedEvent