From 473d8e06491598951633a9628c006ae15b8db5a2 Mon Sep 17 00:00:00 2001 From: anon2020zzz Date: Mon, 19 Sep 2022 08:09:34 -0400 Subject: [PATCH] Add release 4.2.0 --- mixer/app/controllers/AgeUSDController.scala | 151 ++++ mixer/app/controllers/ApiController.scala | 638 +--------------- mixer/app/controllers/CovertController.scala | 275 +++++++ mixer/app/controllers/MixController.scala | 245 ++++++ mixer/app/dao/AllDepositsDAO.scala | 1 - mixer/app/dao/AllMixDAO.scala | 59 +- mixer/app/dao/CovertAddressesDAO.scala | 24 +- mixer/app/dao/CovertDefaultsDAO.scala | 38 +- mixer/app/dao/CovertsDAO.scala | 5 +- mixer/app/dao/DAOUtils.scala | 18 +- mixer/app/dao/DistributeTransactionsDAO.scala | 16 +- mixer/app/dao/EmissionDAO.scala | 8 +- mixer/app/dao/FullMixDAO.scala | 38 +- mixer/app/dao/HalfMixDAO.scala | 32 +- mixer/app/dao/HopMixDAO.scala | 32 +- mixer/app/dao/MixStateDAO.scala | 14 +- mixer/app/dao/MixStateHistoryDAO.scala | 16 +- mixer/app/dao/MixTransactionsDAO.scala | 2 +- mixer/app/dao/MixingCovertRequestDAO.scala | 20 +- mixer/app/dao/MixingGroupRequestDAO.scala | 16 +- mixer/app/dao/MixingRequestsDAO.scala | 38 +- mixer/app/dao/Prune.scala | 70 +- mixer/app/dao/RescanDAO.scala | 18 +- mixer/app/dao/WithdrawCovertTokenDAO.scala | 19 +- mixer/app/dao/WithdrawDAO.scala | 36 +- mixer/app/helpers/ErgoMixerUtils.scala | 36 +- mixer/app/mixer/CovertMixer.scala | 12 +- mixer/app/mixer/Deposits.scala | 2 +- mixer/app/mixer/ErgoMixer.scala | 33 +- mixer/app/mixer/FullMixer.scala | 39 +- mixer/app/mixer/GroupMixer.scala | 8 +- mixer/app/mixer/HalfMixer.scala | 28 +- mixer/app/mixer/HopMixer.scala | 31 +- mixer/app/mixer/MixScanner.scala | 13 +- mixer/app/mixer/NewMixer.scala | 35 +- mixer/app/mixer/Rescan.scala | 98 ++- mixer/app/mixer/WithdrawMixer.scala | 51 +- mixer/app/mixinterface/AliceImpl.scala | 3 +- mixer/app/mixinterface/AliceOrBob.scala | 6 +- mixer/app/mixinterface/BobImpl.scala | 3 +- mixer/app/mixinterface/ErgoMixBase.scala | 4 +- mixer/app/models/Box.scala | 152 ++++ mixer/app/models/Models.scala | 700 +----------------- mixer/app/models/Request.scala | 231 ++++++ mixer/app/models/Rescan.scala | 103 +++ mixer/app/models/Status.scala | 83 +++ mixer/app/models/Transaction.scala | 162 ++++ mixer/app/network/BlockExplorer.scala | 5 +- mixer/app/network/Client.scala | 3 +- mixer/app/network/NetworkUtils.scala | 9 +- mixer/app/wallet/WalletHelper.scala | 13 +- mixer/build.sbt | 2 +- mixer/conf/logback.xml | 12 +- mixer/conf/reference.conf | 6 + mixer/conf/routes | 50 +- mixer/test/dataset/Sample17_FullMixList.json | 19 + mixer/test/dataset/Sample17_HalfMixList.json | 11 + mixer/test/dataset/Sample17_HopMixList.json | 17 + mixer/test/dataset/Sample17_MixRequest.json | 18 + mixer/test/mixer/ErgoMixerSpec.scala | 6 +- mixer/test/mixer/HopMixerSpec.scala | 4 +- mixer/test/mixer/RescanSpec.scala | 120 ++- mixer/test/mixer/WithdrawMixerSpec.scala | 5 +- mixer/test/mocked/MockedAliceOrBob.scala | 4 +- mixer/test/mocked/MockedBlockExplorer.scala | 7 +- .../test/mocked/MockedBlockchainContext.scala | 4 +- mixer/test/mocked/MockedErgoMixer.scala | 4 +- mixer/test/mocked/MockedMixScanner.scala | 25 +- mixer/test/mocked/MockedNetworkUtils.scala | 7 +- mixer/test/testHandlers/DatasetSuite.scala | 7 +- .../test/testHandlers/ErgoMixerDataset.scala | 4 +- mixer/test/testHandlers/HopMixerDataset.scala | 4 +- .../test/testHandlers/MixScannerDataset.scala | 52 +- .../testHandlers/TestSignedTransaction.scala | 2 +- .../testHandlers/WithdrawMixerDataset.scala | 4 +- 75 files changed, 2300 insertions(+), 1786 deletions(-) create mode 100644 mixer/app/controllers/AgeUSDController.scala create mode 100644 mixer/app/controllers/CovertController.scala create mode 100644 mixer/app/controllers/MixController.scala create mode 100644 mixer/app/models/Box.scala create mode 100644 mixer/app/models/Request.scala create mode 100644 mixer/app/models/Rescan.scala create mode 100644 mixer/app/models/Status.scala create mode 100644 mixer/app/models/Transaction.scala create mode 100644 mixer/test/dataset/Sample17_FullMixList.json create mode 100644 mixer/test/dataset/Sample17_HalfMixList.json create mode 100644 mixer/test/dataset/Sample17_HopMixList.json create mode 100644 mixer/test/dataset/Sample17_MixRequest.json diff --git a/mixer/app/controllers/AgeUSDController.scala b/mixer/app/controllers/AgeUSDController.scala new file mode 100644 index 0000000..57970cf --- /dev/null +++ b/mixer/app/controllers/AgeUSDController.scala @@ -0,0 +1,151 @@ +package controllers + +import app.Configs +import dao.{DAOUtils, WithdrawDAO} +import helpers.ErgoMixerUtils +import io.circe.Json +import mixer.ErgoMixer +import mixinterface.AliceOrBob +import models.Status.MixWithdrawStatus.AgeUSDRequested +import models.Transaction.WithdrawTx +import network.{BlockExplorer, NetworkUtils} +import org.ergoplatform.appkit.InputBox +import play.api.Logger +import play.api.mvc._ +import wallet.{Wallet, WalletHelper} + +import javax.inject._ +import scala.collection.JavaConverters._ +import scala.concurrent.ExecutionContext + +/** + * A controller inside of Mixer controller with age usd APIs. + */ +class AgeUSDController @Inject()(controllerComponents: ControllerComponents, ergoMixerUtils: ErgoMixerUtils, + ergoMixer: ErgoMixer, networkUtils: NetworkUtils, explorer: BlockExplorer, aliceOrBob: AliceOrBob, + daoUtils: DAOUtils, withdrawDAO: WithdrawDAO + )(implicit ec: ExecutionContext) extends AbstractController(controllerComponents) { + + private val logger: Logger = Logger(this.getClass) + + /** + * returns current blockchain height + */ + def currentHeight(): Action[AnyContent] = Action { + try { + networkUtils.usingClient(ctx => { + val res = + s"""{ + | "height": ${ctx.getHeight} + |}""".stripMargin + Ok(res).as("application/json") + }) + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * returns tx fee needed for ageUSD minting + */ + def ageusdFee(): Action[AnyContent] = Action { + try { + val res = + s"""{ + | "fee": ${Configs.ageusdFee} + |}""".stripMargin + Ok(res).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A POST Api minting AgeUSD (sigusd, sigrsv) + * + * returns minting tx json + */ + def mint: Action[AnyContent] = Action { implicit request: Request[AnyContent] => + try { + val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) + val prev = js.hcursor.downField("oldTransaction").as[Json].getOrElse(Json.Null) + val req = js.hcursor.downField("request").as[Json].getOrElse(Json.Null) + val mixId = js.hcursor.downField("boxId").as[String].getOrElse(throw new Exception("mixId is required")) + val bankId = req.hcursor.downField("inputs").as[Seq[String]].getOrElse(Seq()).head + + val address = ergoMixer.getWithdrawAddress(mixId) + val boxId = ergoMixer.getFullBoxId(mixId) + val isAlice = ergoMixer.getIsAlice(mixId) + val wallet = new Wallet(ergoMixer.getMasterSecret(mixId)) + val secret = wallet.getSecret(ergoMixer.getRoundNum(mixId)) + + networkUtils.usingClient(ctx => { + var prevBank: InputBox = null + if (!prev.isNull) prevBank = ctx.signedTxFromJson(prev.noSpaces).getOutputsToSpend.get(0) + else prevBank = ctx.getBoxesById(bankId).head + val tx = aliceOrBob.mint(boxId, secret.bigInteger, isAlice, address, req, prevBank, sendTx = true) + + // finding first bank box and tx in the chain + val mintTxsChain = daoUtils.awaitResult(withdrawDAO.getMintings) + var inBank = prevBank.getId.toString + var firstTxId: String = tx.getId + var endOfChain = false + while (!endOfChain) { + val prevTxInChain = mintTxsChain.find(tx => tx.getOutputs.exists(_.equals(inBank))) + if (prevTxInChain.isEmpty) { + endOfChain = true + } else { + inBank = prevTxInChain.get.getInputs.head + firstTxId = prevTxInChain.get.txId + } + } + val additionalInfo = s"$inBank,$firstTxId" + val inps = tx.getSignedInputs.asScala.map(inp => inp.getId.toString).mkString(",") + val txBytes = tx.toJson(false).getBytes("utf-16") + implicit val insertReason: String = "Minting AgeUSD" + val new_withdraw = WithdrawTx(mixId, tx.getId, WalletHelper.now, inps, txBytes, additionalInfo) + withdrawDAO.updateById(new_withdraw, AgeUSDRequested.value) + + Ok(tx.toJson(false)).as("application/json") + }) + + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * returns current oracle box + */ + def oracleBox(tokenId: String): Action[AnyContent] = Action { + try { + val oracle = explorer.getBoxByTokenId(tokenId) + Ok((oracle \\ "items").head.asArray.get(0).noSpaces).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * returns current bank box - unconfirmed if available + */ + def bankBox(tokenId: String): Action[AnyContent] = Action { + try { + val bank = explorer.getBoxByTokenId(tokenId) + Ok((bank \\ "items").head.asArray.get(0).noSpaces).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + +} diff --git a/mixer/app/controllers/ApiController.scala b/mixer/app/controllers/ApiController.scala index 6ade725..4a5a3a1 100644 --- a/mixer/app/controllers/ApiController.scala +++ b/mixer/app/controllers/ApiController.scala @@ -1,39 +1,28 @@ package controllers -import akka.util.ByteString - -import java.nio.file.Paths import app.Configs +import dao.DAOUtils import helpers.ErgoMixerUtils +import helpers.TrayUtils.showNotification import info.BuildInfo import io.circe.Json import io.circe.syntax._ - -import javax.inject._ -import mixer.{CovertMixer, ErgoMixer} -import models.Models.{MixBoxList, WithdrawTx} -import network.{BlockExplorer, NetworkUtils} -import org.ergoplatform.appkit.InputBox +import network.NetworkUtils +import org.apache.commons.lang3._ import play.api.Logger -import play.api.libs.json._ +import play.api.libs.Files import play.api.mvc._ -import org.apache.commons.lang3._ -import helpers.TrayUtils.showNotification -import mixinterface.AliceOrBob -import models.Models.MixWithdrawStatus.AgeUSDRequested -import wallet.{Wallet, WalletHelper} -import scala.collection.JavaConverters._ + +import java.nio.file.Paths +import javax.inject._ import scala.concurrent.ExecutionContext import scala.io.Source -import dao.{DAOUtils, WithdrawDAO} -import play.api.http.HttpEntity /** * A controller inside of Mixer controller. */ class ApiController @Inject()(assets: Assets, controllerComponents: ControllerComponents, ergoMixerUtils: ErgoMixerUtils, - ergoMixer: ErgoMixer, networkUtils: NetworkUtils, explorer: BlockExplorer, aliceOrBob: AliceOrBob, - daoUtils: DAOUtils, withdrawDAO: WithdrawDAO, covertMixer: CovertMixer + networkUtils: NetworkUtils, daoUtils: DAOUtils )(implicit ec: ExecutionContext) extends AbstractController(controllerComponents) { import networkUtils._ @@ -146,7 +135,7 @@ class ApiController @Inject()(assets: Assets, controllerComponents: ControllerCo */ def backup: Action[AnyContent] = Action { implicit request: Request[AnyContent] => try { - if (SystemUtils.IS_OS_WINDOWS) daoUtils.shutdown + if (SystemUtils.IS_OS_WINDOWS) daoUtils.shutdown() val res = ergoMixerUtils.backup() Ok.sendFile(new java.io.File(res)) } catch { @@ -159,12 +148,12 @@ class ApiController @Inject()(assets: Assets, controllerComponents: ControllerCo /** * A post endpoint to upload a backup and restore it */ - def restore = Action(parse.multipartFormData) { request => + def restore: Action[MultipartFormData[Files.TemporaryFile]] = Action(parse.multipartFormData) { request => try { - val path = System.getProperty("user.home") + val (_, baseDbUrl) = daoUtils.getDbUrl request.body.file("myFile").map { backup => - daoUtils.shutdown - backup.ref.copyTo(Paths.get(s"$path/ergoMixer/ergoMixerRestore.zip"), replace = true) + daoUtils.shutdown() + backup.ref.copyTo(Paths.get(s"${baseDbUrl}ergoMixerRestore.zip"), replace = true) ergoMixerUtils.restore() System.exit(0) Ok("Backup restored") @@ -208,413 +197,7 @@ class ApiController @Inject()(assets: Assets, controllerComponents: ControllerCo ).as("application/json") } - /** - * A post controller to create covert address. - */ - def covertRequest: Action[AnyContent] = Action { implicit request: Request[AnyContent] => - val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) - val numRounds: Int = js.hcursor.downField("numRounds").as[Int].getOrElse(-1) - val addresses: Seq[String] = js.hcursor.downField("addresses").as[Seq[String]].getOrElse(Nil).map(_.trim) - val privateKey: String = js.hcursor.downField("privateKey").as[String].getOrElse("") - val nameCovert: String = js.hcursor.downField("nameCovert").as[String].getOrElse("") - if (numRounds == -1) { - BadRequest( - s""" - |{ - | "success": false, - | "message": "all required fields must be present." - |} - |""".stripMargin - ).as("application/json") - } else { - try { - val addr = ergoMixer.newCovertRequest(nameCovert, numRounds, addresses, privateKey) - Ok( - s"""{ - | "success": true, - | "depositAddress": "$addr" - |}""".stripMargin - ).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - - } - } - - /** - * A post endpoint to add or update a covert's assets - * example input: - * { - * "tokenId": "", - * "ring": 1000000000 - * } - * - * @param covertId covert id - */ - def covertAddOrUpdate(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => - val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) - val tokenId: String = js.hcursor.downField("tokenId").as[String].getOrElse(null) - val ring: Long = js.hcursor.downField("ring").as[Long].getOrElse(-1) - if (tokenId == null || ring == -1) { - BadRequest( - s""" - |{ - | "success": false, - | "message": "all required fields must be present." - |} - |""".stripMargin - ).as("application/json") - } else { - try { - ergoMixer.handleCovertSupport(covertId, tokenId, ring) - Ok( - s"""{ - | "success": true - |}""".stripMargin - ).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - } - - /** - * A post endpoint to change a covert's name - * example input: - * { - * "name": "" - * } - * - * @param covertId covert id - */ - def covertChangeName(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => - val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) - val name: String = js.hcursor.downField("nameCovert").as[String].getOrElse("") - try { - ergoMixer.handleNameCovert(covertId, name) - Ok( - s"""{ - | "success": true - |}""".stripMargin - ).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * adds a list of addresses to withdraw addresses of a covert request - * - * @param covertId covert id - * @return whether the processs was successful or not - */ - def setCovertAddresses(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => - val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) - val addresses: Seq[String] = js.hcursor.downField("addresses").as[Seq[String]].getOrElse(Nil).map(_.trim) - try { - ergoMixer.addCovertWithdrawAddress(covertId, addresses) - Ok( - s"""{ - | "success": true - |}""".stripMargin).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - - } - - /** - * adds a list of addresses to withdraw addresses of a covert request - * - * @param covertId covert id - * @return whether the processs was successful or not - */ - def getCovertAddresses(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => - try { - val addresses = ergoMixer.getCovertAddresses(covertId).map(add => s""""$add"""") - Ok(s"[${addresses.mkString(",")}]".stripMargin).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - - } - - /** - * A post controller to create a mix request with/without tokens. - */ - def mixRequest = Action(parse.json) { request => - request.body.validate[MixBoxList] match { - case JsSuccess(value, _) => - try { - val id = ergoMixer.newMixGroupRequest(value.items) - Ok( - s"""{ - | "success": true, - | "mixId": "$id" - |}""".stripMargin).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - case _ => BadRequest("{\"status\": \"error\"}").as("application/json") - } - } - - /** - * A get endpoint which returns list of a covert's assets - */ - def covertAssetList(covertId: String): Action[AnyContent] = Action { - try { - val assets = ergoMixer.getCovertAssets(covertId) - val curMixing = ergoMixer.getCovertCurrentMixing(covertId) - val curRunning = ergoMixer.getCovertRunningMixing(covertId) - Ok( - s""" - |${ergoMixer.getCovertById(covertId).toJson(assets, currentMixing = curMixing, runningMixing = curRunning)} - |""".stripMargin - ).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"status": "error", "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A get endpoint which returns list covet info to be shown, includes covert's supported assets sorted based on latest activity - */ - def covertList: Action[AnyContent] = Action { - try { - val coverts = ergoMixer.getCovertList.map(covert => { - val curMixing = ergoMixer.getCovertCurrentMixing(covert.id) - val curRunning = ergoMixer.getCovertRunningMixing(covert.id) - covert.toJson(ergoMixer.getCovertAssets(covert.id), curMixing, curRunning) - }).mkString(",") - Ok( - s""" - |[$coverts] - |""".stripMargin - ).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A get endpoint which returns list of group mixes - */ - def mixGroupRequestList: Action[AnyContent] = Action { - try { - val res = "[" + (ergoMixer.getMixRequestGroups.map(_.toJson())).mkString(", ") + "]" - Ok(res).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A get endpoint which returns active group mixes. contains more info to be shown about deposits and ... - */ - def mixGroupRequestActiveList = Action { - try { - val mixes = ergoMixer.getMixRequestGroupsActive - val res = "[" + mixes.map(mix => { - val doneMixes = ergoMixer.getFinishedForGroup(mix.id) - val progress = ergoMixer.getProgressForGroup(mix.id) - val stat = - s"""{ - | "numBoxes": ${doneMixes._1}, - | "numComplete": ${doneMixes._2}, - | "numWithdrawn": ${doneMixes._3}, - | "totalMixRound": ${progress._1}, - | "doneMixRound": ${progress._2} - | }""".stripMargin - mix.toJson(stat) - }).mkString(", ") + "]" - Ok(res).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A get endpoint which returns complete list of group mixes - */ - def mixGroupRequestCompleteList: Action[AnyContent] = Action { - try { - val res = "[" + (ergoMixer.getMixRequestGroupsComplete.reverse.map(_.toJson())).mkString(", ") + "]" - Ok(res).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A get endpoint which returns mix boxes of a specific group or covert request - * @param id mix group ID (covert ID in case of covert mixes) - * @param status mix withdraw status (all, active, withdrawn) - */ - def mixRequestList(id: String, status: String): Action[AnyContent] = Action { - try { - val res = "[" + ergoMixer.getMixes(id, status).map(mix => { - var withdrawTxId = "" - if (mix.withdraw.isDefined) { - withdrawTxId = mix.withdraw.get.txId - } - val lastMixTime = { - if (mix.fullMix.isDefined) mix.fullMix.get.createdTime - else if (mix.halfMix.isDefined) mix.halfMix.get.createdTime - else "None" - } - val lastHopRound = ergoMixer.getHopRound(mix.mixRequest.id) - - s""" - |{ - | "id": "${mix.mixRequest.id}", - | "createdDate": ${mix.mixRequest.createdTime}, - | "amount": ${mix.mixRequest.amount}, - | "rounds": ${mix.mixState.map(s => s.round).getOrElse(0)}, - | "status": "${mix.mixRequest.mixStatus.value}", - | "deposit": "${mix.mixRequest.depositAddress}", - | "withdraw": "${mix.mixRequest.withdrawAddress}", - | "boxType": "${ - if (mix.fullMix.isDefined) "Full" else { - if (mix.halfMix.isDefined) "Half" else "None" - } - }", - | "withdrawStatus": "${mix.mixRequest.withdrawStatus}", - | "withdrawTxId": "$withdrawTxId", - | "lastMixTime": "$lastMixTime", - | "mixingTokenId": "${mix.mixRequest.tokenId}", - | "mixingTokenAmount": ${mix.mixRequest.mixingTokenAmount}, - | "hopRounds": ${lastHopRound} - |}""".stripMargin - }).mkString(",") + "]" - Ok(res).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - def supported(): Action[AnyContent] = Action { - try { - val params = Configs.params - if (params.isEmpty) { - BadRequest( - s""" - |{ - | "success": false, - | "message": "params are not ready yet." - |} - |""".stripMargin - ).as("application/json") - } else { - val supported = params.values.toList.sortBy(f => f.id) - Ok( - s""" - |[${supported.map(_.toJson()).mkString(",")}] - |""".stripMargin).as("application/json") - } - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A get endpoint which returns info about current fee parameters - */ - def mixingFee(): Action[AnyContent] = Action { - try { - var res = - s""" - |{ - | "boxInTransaction": ${Configs.maxOuts}, - | "distributeFee": ${Configs.distributeFee}, - | "startFee": ${Configs.startFee},""".stripMargin - val tokenPrices = Configs.tokenPrices.orNull - if (tokenPrices == null) { - BadRequest( - s""" - |{ - | "success": false, - | "message": "token stats are not ready." - |} - |""".stripMargin - ).as("application/json") - } else { - val rate = Configs.entranceFee.getOrElse(1000000) - tokenPrices.foreach { - element => res += s""" "${element._1}": ${element._2},""".stripMargin - } - res += - s""" "rate": $rate - |} - |""".stripMargin - Ok(res).as("application/json") - } - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A POST Api for Add withdraw address to database or change status withdraw - * in route `/mix/withdraw/` - * Input: { - * "nonStayAtMix" : Bool - * "withdrawAddress": String - * "mixId": String - * } - * Output: { - * "success": true or false, - * "message": "" - * } - */ - def withdraw: Action[AnyContent] = Action { implicit request: Request[AnyContent] => - val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) - val withdrawNow = js.hcursor.downField("nonStayAtMix").as[Boolean].getOrElse(false) - val withdrawAddress = js.hcursor.downField("withdrawAddress").as[String].getOrElse("") - val mixId = js.hcursor.downField("mixId").as[String].getOrElse("") - - try { - if (withdrawAddress.nonEmpty) ergoMixer.updateMixWithdrawAddress(mixId, withdrawAddress) - if (withdrawNow) ergoMixer.withdrawMixNow(mixId) - Ok(s"""{"success": true}""".stripMargin).as("application/json") - - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - def index = Action { + def index: Action[AnyContent] = Action { Redirect("/dashboard") } @@ -624,196 +207,5 @@ class ApiController @Inject()(assets: Assets, controllerComponents: ControllerCo if (resource.contains(".")) assets.at(resource) else dashboard } - /** - * returns current blockchain height - */ - def currentHeight(): Action[AnyContent] = Action { - try { - networkUtils.usingClient(ctx => { - val res = - s"""{ - | "height": ${ctx.getHeight} - |}""".stripMargin - Ok(res).as("application/json") - }) - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * returns current oracle box - */ - def oracleBox(tokenId: String): Action[AnyContent] = Action { - try { - val oracle = explorer.getBoxByTokenId(tokenId) - Ok((oracle \\ "items").head.asArray.get(0).noSpaces).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * returns current bank box - unconfirmed if available - */ - def bankBox(tokenId: String): Action[AnyContent] = Action { - try { - val bank = explorer.getBoxByTokenId(tokenId) - Ok((bank \\ "items").head.asArray.get(0).noSpaces).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * returns tx fee needed for ageUSD minting - */ - def ageusdFee(): Action[AnyContent] = Action { - try { - val res = - s"""{ - | "fee": ${Configs.ageusdFee} - |}""".stripMargin - Ok(res).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A POST Api minting AgeUSD (sigusd, sigrsv) - * - * returns minting tx json - */ - def mint: Action[AnyContent] = Action { implicit request: Request[AnyContent] => - try { - val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) - val prev = js.hcursor.downField("oldTransaction").as[Json].getOrElse(Json.Null) - val req = js.hcursor.downField("request").as[Json].getOrElse(Json.Null) - val mixId = js.hcursor.downField("boxId").as[String].getOrElse(throw new Exception("mixId is required")) - val bankId = req.hcursor.downField("inputs").as[Seq[String]].getOrElse(Seq()).head - - val address = ergoMixer.getWithdrawAddress(mixId) - val boxId = ergoMixer.getFullBoxId(mixId) - val isAlice = ergoMixer.getIsAlice(mixId) - val wallet = new Wallet(ergoMixer.getMasterSecret(mixId)) - val secret = wallet.getSecret(ergoMixer.getRoundNum(mixId)) - - networkUtils.usingClient(ctx => { - var prevBank: InputBox = null - if (!prev.isNull) prevBank = ctx.signedTxFromJson(prev.noSpaces).getOutputsToSpend.get(0) - else prevBank = ctx.getBoxesById(bankId).head - val tx = aliceOrBob.mint(boxId, secret.bigInteger, isAlice, address, req, prevBank, true) // TODO send true must be true for releasing - - // finding first bank box and tx in the chain - val mintTxsChain = daoUtils.awaitResult(withdrawDAO.getMintings) - var inBank = prevBank.getId.toString - var firstTxId: String = tx.getId - var endOfChain = false - while (!endOfChain) { - val prevTxInChain = mintTxsChain.find(tx => tx.getOutputs.exists(_.equals(inBank))) - if (prevTxInChain.isEmpty) { - endOfChain = true - } else { - inBank = prevTxInChain.get.getInputs.head - firstTxId = prevTxInChain.get.txId - } - } - val additionalInfo = s"$inBank,$firstTxId" - val inps = tx.getSignedInputs.asScala.map(inp => inp.getId.toString).mkString(",") - val txBytes = tx.toJson(false).getBytes("utf-16") - implicit val insertReason: String = "Minting AgeUSD" - val new_withdraw = WithdrawTx(mixId, tx.getId, WalletHelper.now, inps, txBytes, additionalInfo) - withdrawDAO.updateById(new_withdraw, AgeUSDRequested.value) - - Ok(tx.toJson(false)).as("application/json") - }) - - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * A GET endpoint to download the covert private keys from database - */ - def covertKeys: Action[AnyContent] = Action { implicit request: Request[AnyContent] => - try { - val keys = ergoMixer.covertKeys - Result( - header = ResponseHeader(OK, Map(CONTENT_DISPOSITION → "attachment; filename=covertKeys.csv")), - body = HttpEntity.Strict(ByteString(keys.mkString("\n")), Some("text/csv")) - ) - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - - /** - * returns the private key and address of the corresponding covert - * - * @param covertId covert id - * @return whether the processs was successful or not - */ - def getCovertPrivateKey(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => - try { - val covertInfo = ergoMixer.covertInfoById(covertId) - Ok( - s"""{ - | "address": "${covertInfo._1}", - | "privateKey": "${covertInfo._2.toString(16)}" - |}""".stripMargin - ).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - - } - - /** - * A post endpoint to withdraw a covert's assets - * example input: - * { - * "tokenIds": [ // Seq[String] - * "", - * "" - * ], - * "withdrawAddress": "" // String - * } - * - * @param covertId covert id - */ - def withdrawCovertAsset(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => - try { - val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) - val tokenIds: Seq[String] = js.hcursor.downField("tokenIds").as[Seq[String]].getOrElse(throw new Exception(s"tokenIds is required")) - val withdrawAddress: String = js.hcursor.downField("withdrawAddress").as[String].getOrElse(throw new Exception(s"withdrawAddress is required")) - covertMixer.queueWithdrawAsset(covertId, tokenIds, withdrawAddress) - Ok( - s"""{ - | "success": true - |}""".stripMargin - ).as("application/json") - } catch { - case e: Throwable => - logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") - BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") - } - } - } diff --git a/mixer/app/controllers/CovertController.scala b/mixer/app/controllers/CovertController.scala new file mode 100644 index 0000000..787b788 --- /dev/null +++ b/mixer/app/controllers/CovertController.scala @@ -0,0 +1,275 @@ +package controllers + +import akka.util.ByteString +import helpers.ErgoMixerUtils +import io.circe.Json +import mixer.{CovertMixer, ErgoMixer} +import play.api.Logger +import play.api.http.HttpEntity +import play.api.mvc._ + +import javax.inject._ +import scala.concurrent.ExecutionContext + +/** + * A controller inside of Mixer controller with covert APIs. + */ +class CovertController @Inject()(controllerComponents: ControllerComponents, ergoMixerUtils: ErgoMixerUtils, + ergoMixer: ErgoMixer, covertMixer: CovertMixer + )(implicit ec: ExecutionContext) extends AbstractController(controllerComponents) { + + private val logger: Logger = Logger(this.getClass) + + /** + * A post controller to create covert address. + */ + def covertRequest: Action[AnyContent] = Action { implicit request: Request[AnyContent] => + val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) + val numRounds: Int = js.hcursor.downField("numRounds").as[Int].getOrElse(-1) + val addresses: Seq[String] = js.hcursor.downField("addresses").as[Seq[String]].getOrElse(Nil).map(_.trim) + val privateKey: String = js.hcursor.downField("privateKey").as[String].getOrElse("") + val nameCovert: String = js.hcursor.downField("nameCovert").as[String].getOrElse("") + if (numRounds == -1) { + BadRequest( + s""" + |{ + | "success": false, + | "message": "all required fields must be present." + |} + |""".stripMargin + ).as("application/json") + } else { + try { + val addr = ergoMixer.newCovertRequest(nameCovert, numRounds, addresses, privateKey) + Ok( + s"""{ + | "success": true, + | "depositAddress": "$addr" + |}""".stripMargin + ).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + + } + } + + /** + * A post endpoint to add or update a covert's assets + * example input: + * { + * "tokenId": "", + * "ring": 1000000000 + * } + * + * @param covertId covert id + */ + def covertAddOrUpdate(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => + val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) + val tokenId: String = js.hcursor.downField("tokenId").as[String].getOrElse(null) + val ring: Long = js.hcursor.downField("ring").as[Long].getOrElse(-1) + if (tokenId == null || ring == -1) { + BadRequest( + s""" + |{ + | "success": false, + | "message": "all required fields must be present." + |} + |""".stripMargin + ).as("application/json") + } else { + try { + ergoMixer.handleCovertSupport(covertId, tokenId, ring) + Ok( + s"""{ + | "success": true + |}""".stripMargin + ).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + } + + /** + * A post endpoint to change a covert's name + * example input: + * { + * "name": "" + * } + * + * @param covertId covert id + */ + def covertChangeName(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => + val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) + val name: String = js.hcursor.downField("nameCovert").as[String].getOrElse("") + try { + ergoMixer.handleNameCovert(covertId, name) + Ok( + s"""{ + | "success": true + |}""".stripMargin + ).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A get endpoint which returns list covet info to be shown, includes covert's supported assets sorted based on latest activity + */ + def covertList: Action[AnyContent] = Action { + try { + val coverts = ergoMixer.getCovertList.map(covert => { + val curMixing = ergoMixer.getCovertCurrentMixing(covert.id) + val curRunning = ergoMixer.getCovertRunningMixing(covert.id) + covert.toJson(ergoMixer.getCovertAssets(covert.id), curMixing, curRunning) + }).mkString(",") + Ok( + s""" + |[$coverts] + |""".stripMargin + ).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A get endpoint which returns list of a covert's assets + */ + def covertAssetList(covertId: String): Action[AnyContent] = Action { + try { + val assets = ergoMixer.getCovertAssets(covertId) + val curMixing = ergoMixer.getCovertCurrentMixing(covertId) + val curRunning = ergoMixer.getCovertRunningMixing(covertId) + Ok( + s""" + |${ergoMixer.getCovertById(covertId).toJson(assets, currentMixing = curMixing, runningMixing = curRunning)} + |""".stripMargin + ).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"status": "error", "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * adds a list of addresses to withdraw addresses of a covert request + * + * @param covertId covert id + * @return whether the processs was successful or not + */ + def setCovertAddresses(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => + val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) + val addresses: Seq[String] = js.hcursor.downField("addresses").as[Seq[String]].getOrElse(Nil).map(_.trim) + try { + ergoMixer.addCovertWithdrawAddress(covertId, addresses) + Ok( + s"""{ + | "success": true + |}""".stripMargin).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * adds a list of addresses to withdraw addresses of a covert request + * + * @param covertId covert id + * @return whether the processs was successful or not + */ + def getCovertAddresses(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => + try { + val addresses = ergoMixer.getCovertAddresses(covertId).map(add => s""""$add"""") + Ok(s"[${addresses.mkString(",")}]".stripMargin).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A GET endpoint to download the covert private keys from database + */ + def covertKeys: Action[AnyContent] = Action { implicit request: Request[AnyContent] => + try { + val keys = ergoMixer.covertKeys + Result( + header = ResponseHeader(OK, Map(CONTENT_DISPOSITION → "attachment; filename=covertKeys.csv")), + body = HttpEntity.Strict(ByteString(keys.mkString("\n")), Some("text/csv")) + ) + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * returns the private key and address of the corresponding covert + * + * @param covertId covert id + * @return whether the processs was successful or not + */ + def getCovertPrivateKey(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => + try { + val covertInfo = ergoMixer.covertInfoById(covertId) + Ok( + s"""{ + | "address": "${covertInfo._1}", + | "privateKey": "${covertInfo._2.toString(16)}" + |}""".stripMargin + ).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A post endpoint to withdraw a covert's assets + * example input: + * { + * "tokenIds": [ // Seq[String] + * "", + * "" + * ], + * "withdrawAddress": "" // String + * } + * + * @param covertId covert id + */ + def withdrawCovertAsset(covertId: String): Action[AnyContent] = Action { implicit request: Request[AnyContent] => + try { + val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) + val tokenIds: Seq[String] = js.hcursor.downField("tokenIds").as[Seq[String]].getOrElse(throw new Exception(s"tokenIds is required")) + val withdrawAddress: String = js.hcursor.downField("withdrawAddress").as[String].getOrElse(throw new Exception(s"withdrawAddress is required")) + covertMixer.queueWithdrawAsset(covertId, tokenIds, withdrawAddress) + Ok( + s"""{ + | "success": true + |}""".stripMargin + ).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + +} diff --git a/mixer/app/controllers/MixController.scala b/mixer/app/controllers/MixController.scala new file mode 100644 index 0000000..e37d35f --- /dev/null +++ b/mixer/app/controllers/MixController.scala @@ -0,0 +1,245 @@ +package controllers + +import app.Configs +import helpers.ErgoMixerUtils +import io.circe.Json +import mixer.ErgoMixer +import models.Box.MixBoxList +import play.api.Logger +import play.api.libs.json._ +import play.api.mvc._ + +import javax.inject._ +import scala.concurrent.ExecutionContext + +/** + * A controller inside of Mixer controller with mix and group mix APIs. + */ +class MixController @Inject()(controllerComponents: ControllerComponents, ergoMixerUtils: ErgoMixerUtils, ergoMixer: ErgoMixer, + )(implicit ec: ExecutionContext) extends AbstractController(controllerComponents) { + + private val logger: Logger = Logger(this.getClass) + + /** + * A POST Api for Add withdraw address to database or change status withdraw + * in route `/mix/withdraw/` + * Input: { + * "nonStayAtMix" : Bool + * "withdrawAddress": String + * "mixId": String + * } + * Output: { + * "success": true or false, + * "message": "" + * } + */ + def withdraw: Action[AnyContent] = Action { implicit request: Request[AnyContent] => + val js = io.circe.parser.parse(request.body.asJson.get.toString()).getOrElse(Json.Null) + val withdrawNow = js.hcursor.downField("nonStayAtMix").as[Boolean].getOrElse(false) + val withdrawAddress = js.hcursor.downField("withdrawAddress").as[String].getOrElse("") + val mixId = js.hcursor.downField("mixId").as[String].getOrElse("") + + try { + if (withdrawAddress.nonEmpty) ergoMixer.updateMixWithdrawAddress(mixId, withdrawAddress) + if (withdrawNow) ergoMixer.withdrawMixNow(mixId) + Ok(s"""{"success": true}""".stripMargin).as("application/json") + + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A post controller to create a mix request with/without tokens. + */ + def mixRequest: Action[JsValue] = Action(parse.json) { request => + request.body.validate[MixBoxList] match { + case JsSuccess(value, _) => + try { + val id = ergoMixer.newMixGroupRequest(value.items) + Ok( + s"""{ + | "success": true, + | "mixId": "$id" + |}""".stripMargin).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + case _ => BadRequest("{\"status\": \"error\"}").as("application/json") + } + } + + /** + * A get endpoint which returns list of group mixes + */ + def mixGroupRequestList: Action[AnyContent] = Action { + try { + val res = "[" + ergoMixer.getMixRequestGroups.map(_.toJson()).mkString(", ") + "]" + Ok(res).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A get endpoint which returns active group mixes. contains more info to be shown about deposits and ... + */ + def mixGroupRequestActiveList: Action[AnyContent] = Action { + try { + val mixes = ergoMixer.getMixRequestGroupsActive + val res = "[" + mixes.map(mix => { + val doneMixes = ergoMixer.getFinishedForGroup(mix.id) + val progress = ergoMixer.getProgressForGroup(mix.id) + val stat = + s"""{ + | "numBoxes": ${doneMixes._1}, + | "numComplete": ${doneMixes._2}, + | "numWithdrawn": ${doneMixes._3}, + | "totalMixRound": ${progress._1}, + | "doneMixRound": ${progress._2} + | }""".stripMargin + mix.toJson(stat) + }).mkString(", ") + "]" + Ok(res).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A get endpoint which returns complete list of group mixes + */ + def mixGroupRequestCompleteList: Action[AnyContent] = Action { + try { + val res = "[" + ergoMixer.getMixRequestGroupsComplete.reverse.map(_.toJson()).mkString(", ") + "]" + Ok(res).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A get endpoint which returns mix boxes of a specific group or covert request + * @param id mix group ID (covert ID in case of covert mixes) + * @param status mix withdraw status (all, active, withdrawn) + */ + def mixRequestList(id: String, status: String): Action[AnyContent] = Action { + try { + val res = "[" + ergoMixer.getMixes(id, status).map(mix => { + var withdrawTxId = "" + if (mix.withdraw.isDefined) { + withdrawTxId = mix.withdraw.get.txId + } + val lastMixTime = { + if (mix.fullMix.isDefined) mix.fullMix.get.createdTime + else if (mix.halfMix.isDefined) mix.halfMix.get.createdTime + else "None" + } + val lastHopRound = ergoMixer.getHopRound(mix.mixRequest.id) + + s""" + |{ + | "id": "${mix.mixRequest.id}", + | "createdDate": ${mix.mixRequest.createdTime}, + | "amount": ${mix.mixRequest.amount}, + | "rounds": ${mix.mixState.map(s => s.round).getOrElse(0)}, + | "status": "${mix.mixRequest.mixStatus.value}", + | "deposit": "${mix.mixRequest.depositAddress}", + | "withdraw": "${mix.mixRequest.withdrawAddress}", + | "boxType": "${ + if (mix.fullMix.isDefined) "Full" else { + if (mix.halfMix.isDefined) "Half" else "None" + } + }", + | "withdrawStatus": "${mix.mixRequest.withdrawStatus}", + | "withdrawTxId": "$withdrawTxId", + | "lastMixTime": "$lastMixTime", + | "mixingTokenId": "${mix.mixRequest.tokenId}", + | "mixingTokenAmount": ${mix.mixRequest.mixingTokenAmount}, + | "hopRounds": $lastHopRound + |}""".stripMargin + }).mkString(",") + "]" + Ok(res).as("application/json") + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + /** + * A get endpoint which returns info about current fee parameters + */ + def mixingFee(): Action[AnyContent] = Action { + try { + var res = + s""" + |{ + | "boxInTransaction": ${Configs.maxOuts}, + | "distributeFee": ${Configs.distributeFee}, + | "startFee": ${Configs.startFee},""".stripMargin + val tokenPrices = Configs.tokenPrices.orNull + if (tokenPrices == null) { + BadRequest( + s""" + |{ + | "success": false, + | "message": "token stats are not ready." + |} + |""".stripMargin + ).as("application/json") + } else { + val rate = Configs.entranceFee.getOrElse(1000000) + tokenPrices.foreach { + element => res += s""" "${element._1}": ${element._2},""".stripMargin + } + res += + s""" "rate": $rate + |} + |""".stripMargin + Ok(res).as("application/json") + } + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + + def supported(): Action[AnyContent] = Action { + try { + val params = Configs.params + if (params.isEmpty) { + BadRequest( + s""" + |{ + | "success": false, + | "message": "params are not ready yet." + |} + |""".stripMargin + ).as("application/json") + } else { + val supported = params.values.toList.sortBy(f => f.id) + Ok( + s""" + |[${supported.map(_.toJson).mkString(",")}] + |""".stripMargin).as("application/json") + } + } catch { + case e: Throwable => + logger.error(s"error in controller ${ergoMixerUtils.getStackTraceStr(e)}") + BadRequest(s"""{"success": false, "message": "${e.getMessage}"}""").as("application/json") + } + } + +} diff --git a/mixer/app/dao/AllDepositsDAO.scala b/mixer/app/dao/AllDepositsDAO.scala index b92c78c..8945591 100644 --- a/mixer/app/dao/AllDepositsDAO.scala +++ b/mixer/app/dao/AllDepositsDAO.scala @@ -1,7 +1,6 @@ package dao import javax.inject.{Inject, Singleton} -import models.Models.Deposit import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile diff --git a/mixer/app/dao/AllMixDAO.scala b/mixer/app/dao/AllMixDAO.scala index ad9ad23..6e769d7 100644 --- a/mixer/app/dao/AllMixDAO.scala +++ b/mixer/app/dao/AllMixDAO.scala @@ -5,9 +5,10 @@ import slick.jdbc.JdbcProfile import javax.inject.{Inject, Singleton} import scala.concurrent.{ExecutionContext, Future} -import models.Models.MixStatus.{Queued, Running} -import models.Models.{Deposit, FullMix, HalfMix, HopMix, MixState, MixStatus} -import models.Models.MixWithdrawStatus.{HopRequested, UnderHop, WithdrawRequested} +import models.Status.MixStatus.{Queued, Running} +import models.Status.MixStatus +import models.Models.{Deposit, FullMix, HalfMix, HopMix, MixState} +import models.Status.MixWithdrawStatus.{HopRequested, UnderHop, WithdrawRequested} import network.BlockExplorer import play.api.Logger @@ -58,9 +59,12 @@ class AllMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider def groupRequestsProgress: Future[Seq[(String, Int, String, BigInt, String, String)]] = { val query = for { ((halfMix, state), request) <- halfMixes.filter(mix => mix.isSpent === false) join - mixes on((mix, state) => {mix.id === state.id && mix.round === state.round}) join - mixingRequests.filter(request => request.mixStatus === MixStatus.fromString(Running.value) || (request.withdrawStatus === WithdrawRequested.value || request.withdrawStatus === HopRequested.value)) on(_._1.id === _.id) - } yield (halfMix.id, state.round, halfMix.halfMixBoxId, request.masterKey, request.withdrawStatus, request.withdrawAddress) + mixes on((mix, state) => {mix.mixId === state.mixId && mix.round === state.round}) join + mixingRequests.filter(request => + request.mixStatus === MixStatus.fromString(Running.value) || + (request.withdrawStatus === WithdrawRequested.value || request.withdrawStatus === HopRequested.value) + ) on(_._1.mixId === _.mixId) + } yield (halfMix.mixId, state.round, halfMix.halfMixBoxId, request.masterKey, request.withdrawStatus, request.withdrawAddress) db.run(query.result) } @@ -71,9 +75,12 @@ class AllMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider def groupFullMixesProgress: Future[Seq[(String, Int, String, BigInt, Boolean, String, Int, String, String, String)]] = { val query = for { ((fullMix, state), request) <- fullMixes join - mixes on((mix, state) => {mix.id === state.id && mix.round === state.round}) join - mixingRequests.filter(request => request.mixStatus === MixStatus.fromString(Running.value) || (request.withdrawStatus === WithdrawRequested.value || request.withdrawStatus === HopRequested.value)) on(_._1.id === _.id) - } yield (fullMix.id, request.numRounds, request.withdrawAddress, request.masterKey, state.isAlice, + mixes on((mix, state) => {mix.mixId === state.mixId && mix.round === state.round}) join + mixingRequests.filter(request => + request.mixStatus === MixStatus.fromString(Running.value) || + (request.withdrawStatus === WithdrawRequested.value || request.withdrawStatus === HopRequested.value) + ) on(_._1.mixId === _.mixId) + } yield (fullMix.mixId, request.numRounds, request.withdrawAddress, request.masterKey, state.isAlice, fullMix.fullMixBoxId, state.round, fullMix.halfMixBoxId, request.withdrawStatus, request.tokenId) db.run(query.result) } @@ -83,10 +90,10 @@ class AllMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * */ def groupHopMixesProgress: Future[Seq[HopMix]] = { - val lastHops = hopMixes.groupBy(hop => hop.id).map{ case (id, group) => (id, group.map(_.round).max) } - val lastHopMixes = (hopMixes join lastHops on((hopMix, lastHop) => hopMix.id === lastHop._1 && hopMix.round === lastHop._2)).map{ case(hopMix, lastHop) => hopMix} + val lastHops = hopMixes.groupBy(hop => hop.mixId).map{ case (id, group) => (id, group.map(_.round).max) } + val lastHopMixes = (hopMixes join lastHops on((hopMix, lastHop) => hopMix.mixId === lastHop._1 && hopMix.round === lastHop._2)).map{ case(hopMix, _) => hopMix} val query = for { - (hopMix, request) <- lastHopMixes join mixingRequests.filter(request => request.withdrawStatus === UnderHop.value) on(_.id === _.id) + (hopMix, _) <- lastHopMixes join mixingRequests.filter(request => request.withdrawStatus === UnderHop.value) on(_.mixId === _.mixId) } yield hopMix db.run(query.result) } @@ -120,7 +127,7 @@ class AllMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider */ def undoMixSpends(mixId: String): Unit = { val depositQuery = for { - address <- mixingRequests.filter(req => req.id === mixId).map(_.depositAddress).result.headOption + address <- mixingRequests.filter(req => req.mixId === mixId).map(_.depositAddress).result.headOption deposits <- spentDeposits.filter(deposit => deposit.address === address).map(deposit => (deposit.address, deposit.boxId, deposit.amount, deposit.createdTime, deposit.tokenAmount)).result } yield deposits daoUtils.awaitResult(db.run(depositQuery)).foreach{ @@ -141,14 +148,14 @@ class AllMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * @param boxId String */ def undoMixStep(mixId: String, round: Int, boxId: String, isFullMix: Boolean): Unit = { - val currRound = daoUtils.awaitResult(db.run(mixes.filter(mix => mix.id === mixId).map(_.round).result.headOption)).getOrElse(throw new Exception(s"No entry exists for $mixId in mixStateTable")) + val currRound = daoUtils.awaitResult(db.run(mixes.filter(mix => mix.mixId === mixId).map(_.round).result.headOption)).getOrElse(throw new Exception(s"No entry exists for $mixId in mixStateTable")) if (currRound != round) throw new Exception(s"Current round ($currRound) != undo round ($round)") - val maxRound = daoUtils.awaitResult(db.run(mixHistories.filter(history => history.id === mixId).map(_.round).max.result)).getOrElse(throw new Exception(s"No entry exists for $mixId in mixHistoryTable")) + val maxRound = daoUtils.awaitResult(db.run(mixHistories.filter(history => history.mixId === mixId).map(_.round).max.result)).getOrElse(throw new Exception(s"No entry exists for $mixId in mixHistoryTable")) if (currRound != maxRound) throw new Exception(s"Current round ($currRound) != max round ($maxRound)") db.run(DBIO.seq( - mixHistories.filter(history => history.id === mixId && history.round === round).delete, + mixHistories.filter(history => history.mixId === mixId && history.round === round).delete, mixTransactions.filter(tx => tx.boxId === boxId).delete )) @@ -158,27 +165,27 @@ class AllMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider // set mixRequest as Queued db.run(DBIO.seq( - mixes.filter(mix => mix.id === mixId).delete, + mixes.filter(mix => mix.mixId === mixId).delete, tokenEmissions.filter(box => box.id === mixId).delete, - emissions.filter(box => box.id === mixId && box.round === round).delete, - mixingRequests.filter(req => req.id === mixId).map(_.mixStatus).update(Queued) + emissions.filter(box => box.mixId === mixId && box.round === round).delete, + mixingRequests.filter(req => req.mixId === mixId).map(_.mixStatus).update(Queued) )) undoMixSpends(mixId) } else { val prevRound = round - 1 - val prevMixState = daoUtils.awaitResult(db.run(mixHistories.filter(history => history.id === mixId && history.round === prevRound).result.headOption)).getOrElse(throw new Exception(s"No history found for previous round $prevRound and mixId $mixId")) + val prevMixState = daoUtils.awaitResult(db.run(mixHistories.filter(history => history.mixId === mixId && history.round === prevRound).result.headOption)).getOrElse(throw new Exception(s"No history found for previous round $prevRound and mixId $mixId")) db.run(DBIO.seq( - mixes.filter(mix => mix.id === mixId).map(mix => (mix.round, mix.isAlice)).update(prevRound, prevMixState.isAlice), - emissions.filter(box => box.id === mixId && box.round === round).delete + mixes.filter(mix => mix.mixId === mixId).map(mix => (mix.round, mix.isAlice)).update(prevRound, prevMixState.isAlice), + emissions.filter(box => box.mixId === mixId && box.round === round).delete )) } if (isFullMix) - db.run(fullMixes.filter(mix => mix.id === mixId && mix.round === round).delete) + db.run(fullMixes.filter(mix => mix.mixId === mixId && mix.round === round).delete) else - db.run(halfMixes.filter(mix => mix.id === mixId && mix.round === round).delete) + db.run(halfMixes.filter(mix => mix.mixId === mixId && mix.round === round).delete) } /** @@ -194,8 +201,8 @@ class AllMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider return Future(emptyHalfMix, emptyFullMix) }).round val query = for { - half <- halfMixes.filter(mix => mix.id === mixId && mix.round === round).result.headOption - full <- fullMixes.filter(mix => mix.id === mixId && mix.round === round).result.headOption + half <- halfMixes.filter(mix => mix.mixId === mixId && mix.round === round).result.headOption + full <- fullMixes.filter(mix => mix.mixId === mixId && mix.round === round).result.headOption } yield (half, full) db.run(query) } diff --git a/mixer/app/dao/CovertAddressesDAO.scala b/mixer/app/dao/CovertAddressesDAO.scala index 85bb7f1..9862cb7 100644 --- a/mixer/app/dao/CovertAddressesDAO.scala +++ b/mixer/app/dao/CovertAddressesDAO.scala @@ -12,13 +12,13 @@ trait CovertAddressesComponent { import profile.api._ class CovertAddressesTable(tag: Tag) extends Table[(String, String)](tag, "COVERT_ADDRESSES") { - def covertId = column[String]("MIX_GROUP_ID") + def groupId = column[String]("MIX_GROUP_ID") def address = column[String]("ADDRESS") - def * = (covertId, address) + def * = (groupId, address) - def pk = primaryKey("pk_COVERT_DEFAULTS", (covertId, address)) + def pk = primaryKey("pk_COVERT_DEFAULTS", (groupId, address)) } } @@ -47,26 +47,26 @@ class CovertAddressesDAO @Inject()(protected val dbConfigProvider: DatabaseConfi /** * inserts an address into COVERT_ADDRESSES table * - * @param covertId String + * @param groupId String * @param address String */ - def insert(covertId: String, address: String): Future[Unit] = db.run(covertAddresses += (covertId, address)).map(_ => ()) + def insert(groupId: String, address: String): Future[Unit] = db.run(covertAddresses += (groupId, address)).map(_ => ()) /** - * delete the address from the table by covertId + * delete the address from the table by groupId * - * @param covertId String + * @param groupId String */ - def delete(covertId: String): Future[Unit] = db.run(covertAddresses.filter(address => address.covertId === covertId).delete).map(_ => ()) + def delete(groupId: String): Future[Unit] = db.run(covertAddresses.filter(address => address.groupId === groupId).delete).map(_ => ()) /** - * selects the addresses by covertId + * selects the addresses by groupId * - * @param covertId String + * @param groupId String */ - def selectAllAddressesByCovertId(covertId: String): Future[Seq[String]] = { + def selectAllAddressesByCovertId(groupId: String): Future[Seq[String]] = { val query = for { - address <- covertAddresses if address.covertId === covertId + address <- covertAddresses if address.groupId === groupId } yield address.address db.run(query.result) } diff --git a/mixer/app/dao/CovertDefaultsDAO.scala b/mixer/app/dao/CovertDefaultsDAO.scala index 94bdbe9..54f4ca8 100644 --- a/mixer/app/dao/CovertDefaultsDAO.scala +++ b/mixer/app/dao/CovertDefaultsDAO.scala @@ -13,7 +13,7 @@ trait CovertDefaultsComponent { import profile.api._ class CovertDefaultsTable(tag: Tag) extends Table[CovertAsset](tag, "COVERT_DEFAULTS") { - def covertId = column[String]("MIX_GROUP_ID") + def groupId = column[String]("MIX_GROUP_ID") def tokenId = column[String]("TOKEN_ID") @@ -23,9 +23,9 @@ trait CovertDefaultsComponent { def lastActivity = column[Long]("LAST_ACTIVITY") - def * = (covertId, tokenId, ring, confirmedDeposit, lastActivity) <> (CovertAsset.tupled, CovertAsset.unapply) + def * = (groupId, tokenId, ring, confirmedDeposit, lastActivity) <> (CovertAsset.tupled, CovertAsset.unapply) - def pk = primaryKey("pk_COVERT_DEFAULTS", (covertId, tokenId)) + def pk = primaryKey("pk_COVERT_DEFAULTS", (groupId, tokenId)) } } @@ -67,51 +67,51 @@ class CovertDefaultsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig /** * checks if the pair of mixGroupId and tokenId exists in table or not * - * @param covertId String + * @param groupId String * @param tokenId String */ - def exists(covertId: String, tokenId: String): Future[Boolean] = db.run(covertAssets.filter(asset => asset.covertId === covertId && asset.tokenId === tokenId).exists.result) + def exists(groupId: String, tokenId: String): Future[Boolean] = db.run(covertAssets.filter(asset => asset.groupId === groupId && asset.tokenId === tokenId).exists.result) /** * selects assets by mixGroupId * - * @param covertId String + * @param groupId String */ - def selectAllAssetsByMixGroupId(covertId: String): Future[Seq[CovertAsset]] = db.run(covertAssets.filter(asset => asset.covertId === covertId).result) + def selectAllAssetsByMixGroupId(groupId: String): Future[Seq[CovertAsset]] = db.run(covertAssets.filter(asset => asset.groupId === groupId).result) /** * selects assets by mixGroupId and tokenId * - * @param covertId String + * @param groupId String * @param tokenId String */ - def selectByGroupAndTokenId(covertId: String, tokenId: String): Future[Option[CovertAsset]] = db.run(covertAssets.filter(asset => asset.covertId === covertId && asset.tokenId === tokenId).result.headOption) + def selectByGroupAndTokenId(groupId: String, tokenId: String): Future[Option[CovertAsset]] = db.run(covertAssets.filter(asset => asset.groupId === groupId && asset.tokenId === tokenId).result.headOption) /** - * updates ring by pair of covertId and tokenId + * updates ring by pair of groupId and tokenId * - * @param covertId String + * @param groupId String * @param tokenId String * @param ring Long */ - def updateRing(covertId: String, tokenId: String, ring: Long): Future[Unit] = { + def updateRing(groupId: String, tokenId: String, ring: Long): Future[Unit] = { val query = for { - asset <- covertAssets if asset.covertId === covertId && asset.tokenId === tokenId + asset <- covertAssets if asset.groupId === groupId && asset.tokenId === tokenId } yield asset.ring db.run(query.update(ring)).map(_ => ()) } /** - * updates confirmedDeposit and lastActivity by pair of covertId and tokenId + * updates confirmedDeposit and lastActivity by pair of groupId and tokenId * - * @param covertId String + * @param groupId String * @param tokenId String * @param confirmedDeposit Long * @param lastActivity Long */ - def updateConfirmedDeposit(covertId: String, tokenId: String, confirmedDeposit: Long, lastActivity: Long): Future[Unit] = { + def updateConfirmedDeposit(groupId: String, tokenId: String, confirmedDeposit: Long, lastActivity: Long): Future[Unit] = { val query = for { - asset <- covertAssets if asset.covertId === covertId && asset.tokenId === tokenId + asset <- covertAssets if asset.groupId === groupId && asset.tokenId === tokenId } yield (asset.confirmedDeposit, asset.lastActivity) db.run(query.update(confirmedDeposit, lastActivity)).map(_ => ()) } @@ -119,9 +119,9 @@ class CovertDefaultsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig /** * deletes an asset if no ring has been set for it by the pair of mixGroupId and tokenId * - * @param covertId String + * @param groupId String * @param tokenId String */ - def deleteIfRingIsEmpty(covertId: String, tokenId: String): Future[Unit] = db.run(covertAssets.filter(asset => asset.covertId === covertId && asset.tokenId === tokenId && asset.ring === 0L).delete).map(_ => ()) + def deleteIfRingIsEmpty(groupId: String, tokenId: String): Future[Unit] = db.run(covertAssets.filter(asset => asset.groupId === groupId && asset.tokenId === tokenId && asset.ring === 0L).delete).map(_ => ()) } diff --git a/mixer/app/dao/CovertsDAO.scala b/mixer/app/dao/CovertsDAO.scala index 52663a0..be357e2 100644 --- a/mixer/app/dao/CovertsDAO.scala +++ b/mixer/app/dao/CovertsDAO.scala @@ -1,6 +1,7 @@ package dao -import models.Models.{CovertAsset, MixCovertRequest} +import models.Models.CovertAsset +import models.Request.MixCovertRequest import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile @@ -21,7 +22,7 @@ class CovertsDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide /** * selects unspent and spent deposits by address * - * @param address String + * @param addresses String */ def createCovert(req: MixCovertRequest, addresses: Seq[(String, String)], asset: CovertAsset): Future[Unit] = { val query = for { diff --git a/mixer/app/dao/DAOUtils.scala b/mixer/app/dao/DAOUtils.scala index bb7ee8b..aee6a3b 100644 --- a/mixer/app/dao/DAOUtils.scala +++ b/mixer/app/dao/DAOUtils.scala @@ -7,18 +7,30 @@ import spire.ClassTag import java.util.concurrent.Executors import javax.inject.Inject import scala.concurrent.duration.Duration -import scala.concurrent.{Await, ExecutionContext, Future} +import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor, Future} class DAOUtils @Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] { def awaitResult[T: ClassTag](variable: Future[T]): T = Await.result(variable, Duration.Inf) - def shutdown = { + def shutdown(): Unit = { db.close() - implicit val context = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor()) + implicit val context: ExecutionContextExecutor = ExecutionContext.fromExecutor(Executors.newSingleThreadExecutor()) Future { Thread.sleep(10000) System.exit(0) } } + + /** + * a function for get db url path + * @return (absolute path with db file, db directory path ) + */ + def getDbUrl: (String, String) = { + val mainUrl = dbConfig.config.getString("db.url").split(":").last.replace("~", System.getProperty("user.home")) + val dbNameIndex = mainUrl.lastIndexOf("/") + 1 + + (mainUrl, mainUrl.slice(0, dbNameIndex)) + } + } diff --git a/mixer/app/dao/DistributeTransactionsDAO.scala b/mixer/app/dao/DistributeTransactionsDAO.scala index 368d953..9eb6e35 100644 --- a/mixer/app/dao/DistributeTransactionsDAO.scala +++ b/mixer/app/dao/DistributeTransactionsDAO.scala @@ -1,7 +1,7 @@ package dao import javax.inject.{Inject, Singleton} -import models.Models.DistributeTx +import models.Transaction.DistributeTx import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile @@ -47,23 +47,23 @@ class DistributeTransactionsDAO @Inject()(protected val dbConfigProvider: Databa def insert(tx: DistributeTx): Future[Unit] = db.run(distributeTransactions += tx).map(_ => ()) /** - * selects spent transactions inputs by covertId + * selects spent transactions inputs by groupId * - * @param covertId String + * @param groupId String */ - def selectSpentTransactionsInputs(covertId: String): Future[Seq[String]] = { + def selectSpentTransactionsInputs(groupId: String): Future[Seq[String]] = { val query = for { - tx <- distributeTransactions if tx.mixGroupId === covertId && tx.order > 0 + tx <- distributeTransactions if tx.mixGroupId === groupId && tx.order > 0 } yield tx.inputs db.run(query.result) } /** - * selects spent transactions by covertId + * selects spent transactions by groupId * - * @param covertId String + * @param groupId String */ - def selectSpentTransactions(covertId: String): Future[Seq[DistributeTx]] = db.run(distributeTransactions.filter(tx => tx.mixGroupId === covertId && tx.order > 0).result) + def selectSpentTransactions(groupId: String): Future[Seq[DistributeTx]] = db.run(distributeTransactions.filter(tx => tx.mixGroupId === groupId && tx.order > 0).result) /** * selects transactions by mixGroupId diff --git a/mixer/app/dao/EmissionDAO.scala b/mixer/app/dao/EmissionDAO.scala index d216cc2..8efbac2 100644 --- a/mixer/app/dao/EmissionDAO.scala +++ b/mixer/app/dao/EmissionDAO.scala @@ -12,13 +12,13 @@ trait EmissionComponent { import profile.api._ class EmissionTable(tag: Tag) extends Table[(String, Int, String)](tag, "EMISSION_BOX") { - def id = column[String]("MIX_ID") + def mixId = column[String]("MIX_ID") def round = column[Int]("ROUND") def boxId = column[String]("BOX_ID", O.PrimaryKey) - def * = (id, round, boxId) + def * = (mixId, round, boxId) } } @@ -40,7 +40,7 @@ class EmissionDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid */ def selectBoxId(mixId: String, round: Int): Future[Option[String]] = { val query = for { - box <- emissions if box.id === mixId && box.round === round + box <- emissions if box.mixId === mixId && box.round === round } yield box.boxId db.run(query.result.headOption) } @@ -66,5 +66,5 @@ class EmissionDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * * @param mixId String */ - def delete(mixId: String): Unit = db.run(emissions.filter(box => box.id === mixId).delete).map(_ => ()) + def delete(mixId: String): Unit = db.run(emissions.filter(box => box.mixId === mixId).delete).map(_ => ()) } diff --git a/mixer/app/dao/FullMixDAO.scala b/mixer/app/dao/FullMixDAO.scala index f3fbd27..4fe6394 100644 --- a/mixer/app/dao/FullMixDAO.scala +++ b/mixer/app/dao/FullMixDAO.scala @@ -13,7 +13,7 @@ trait FullMixComponent { import profile.api._ class FullMixTable(tag: Tag) extends Table[FullMix](tag, "FULL_MIX") { - def id = column[String]("MIX_ID") + def mixId = column[String]("MIX_ID") def round = column[Int]("ROUND") @@ -23,13 +23,13 @@ trait FullMixComponent { def fullMixBoxId = column[String]("FULL_MIX_BOX_ID") - def * = (id, round, createdTime, halfMixBoxId, fullMixBoxId) <> (FullMix.tupled, FullMix.unapply) + def * = (mixId, round, createdTime, halfMixBoxId, fullMixBoxId) <> (FullMix.tupled, FullMix.unapply) - def pk = primaryKey("pk_FULL_MIX", (id, round)) + def pk = primaryKey("pk_FULL_MIX", (mixId, round)) } class FullMixArchivedTable(tag: Tag) extends Table[(String, Int, Long, String, String, String)](tag, "FULL_MIX_ARCHIVED") { - def id = column[String]("MIX_ID") + def mixId = column[String]("MIX_ID") def round = column[Int]("ROUND") @@ -41,9 +41,9 @@ trait FullMixComponent { def reason = column[String]("REASON") - def * = (id, round, createdTime, halfMixBoxId, fullMixBoxId, reason) + def * = (mixId, round, createdTime, halfMixBoxId, fullMixBoxId, reason) - def pk = primaryKey("pk_FULL_MIX_ARCHIVED", (id, round)) + def pk = primaryKey("pk_FULL_MIX_ARCHIVED", (mixId, round)) } } @@ -88,7 +88,7 @@ class FullMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide def selectFullBoxIdByMixId(mixId: String): Future[Option[String]] = { val query = for { (req, state) <- fullMix join mixes - if req.id === mixId && state.id === mixId && req.round === state.round + if req.mixId === mixId && state.mixId === mixId && req.round === state.round } yield req.fullMixBoxId db.run(query.result.headOption) } @@ -101,7 +101,7 @@ class FullMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide */ def selectBoxId(mixId: String, round: Int): Future[Option[String]] = { val query = for { - mix <- fullMix if mix.id === mixId && mix.round === round + mix <- fullMix if mix.mixId === mixId && mix.round === round } yield mix.fullMixBoxId db.run(query.result.headOption) } @@ -119,14 +119,14 @@ class FullMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param mixId String * @param round Int */ - def selectOption(mixId: String, round: Int): Future[Option[FullMix]] = db.run(fullMix.filter(mix => mix.id === mixId && mix.round === round).result.headOption) + def selectOption(mixId: String, round: Int): Future[Option[FullMix]] = db.run(fullMix.filter(mix => mix.mixId === mixId && mix.round === round).result.headOption) /** * selects fullMix by mixId * * @param mixId String */ - def boxIdByMixId(mixId: String): Future[Option[String]] = db.run(fullMix.filter(mix => mix.id === mixId).map(_.fullMixBoxId).result.headOption) + def boxIdByMixId(mixId: String): Future[Option[String]] = db.run(fullMix.filter(mix => mix.mixId === mixId).map(_.fullMixBoxId).result.headOption) /** * delete fullMix by mixId @@ -134,8 +134,8 @@ class FullMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param mixId String */ def deleteWithArchive(mixId: String): Future[Unit] = db.run(DBIO.seq( - fullMix.filter(mix => mix.id === mixId).delete, - fullMixArchive.filter(mix => mix.id === mixId).delete + fullMix.filter(mix => mix.mixId === mixId).delete, + fullMixArchive.filter(mix => mix.mixId === mixId).delete )) /** @@ -144,7 +144,7 @@ class FullMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param mixId String * @param round Int */ - def deleteFutureRounds(mixId: String, round: Int): Future[Unit] = db.run(fullMix.filter(mix => mix.id === mixId && mix.round > round).delete).map(_ => ()) + def deleteFutureRounds(mixId: String, round: Int): Future[Unit] = db.run(fullMix.filter(mix => mix.mixId === mixId && mix.round > round).delete).map(_ => ()) /** * updates full mix by mixId @@ -152,7 +152,7 @@ class FullMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param new_mix FullMix */ def updateById(new_mix: FullMix)(implicit insertReason: String): Future[Unit] = db.run(DBIO.seq( - fullMix.filter(mix => mix.id === new_mix.mixId && mix.round === new_mix.round).delete, + fullMix.filter(mix => mix.mixId === new_mix.mixId && mix.round === new_mix.round).delete, fullMix += new_mix, fullMixArchive += (new_mix.mixId, new_mix.round, new_mix.createdTime, new_mix.halfMixBoxId, new_mix.fullMixBoxId, insertReason) )) @@ -166,4 +166,14 @@ class FullMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide fullMix += new_fullMix, fullMixArchive += (new_fullMix.mixId, new_fullMix.round, new_fullMix.createdTime, new_fullMix.halfMixBoxId, new_fullMix.fullMixBoxId, insertReason), )) + + /** + * selects fullMixBox by mixId and round + * + * @param mixId String + * @param round Int + */ + def getMixBoxIdByRound(mixId: String, round: Int): Future[Option[String]] = db.run(fullMix.filter(mix => + mix.mixId === mixId && mix.round === round).map(_.fullMixBoxId).result.headOption) + } diff --git a/mixer/app/dao/HalfMixDAO.scala b/mixer/app/dao/HalfMixDAO.scala index b1b6b48..d91a3d8 100644 --- a/mixer/app/dao/HalfMixDAO.scala +++ b/mixer/app/dao/HalfMixDAO.scala @@ -13,7 +13,7 @@ trait HalfMixComponent { import profile.api._ class HalfMixTable(tag: Tag) extends Table[HalfMix](tag, "HALF_MIX") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def round = column[Int]("ROUND") @@ -23,11 +23,11 @@ trait HalfMixComponent { def isSpent = column[Boolean]("IS_SPENT") - def * = (id, round, createdTime, halfMixBoxId, isSpent) <> (HalfMix.tupled, HalfMix.unapply) + def * = (mixId, round, createdTime, halfMixBoxId, isSpent) <> (HalfMix.tupled, HalfMix.unapply) } class HalfMixArchivedTable(tag: Tag) extends Table[(String, Int, Long, String, Boolean, String)](tag, "HALF_MIX_ARCHIVED") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def round = column[Int]("ROUND") @@ -39,7 +39,7 @@ trait HalfMixComponent { def reason = column[String]("REASON") - def * = (id, round, createdTime, halfMixBoxId, isSpent, reason) + def * = (mixId, round, createdTime, halfMixBoxId, isSpent, reason) } } @@ -80,14 +80,14 @@ class HalfMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param mixId String * @param round Int */ - def selectOption(mixId: String, round: Int): Future[Option[HalfMix]] = db.run(halfMix.filter(mix => mix.id === mixId && mix.round === round).result.headOption) + def selectOption(mixId: String, round: Int): Future[Option[HalfMix]] = db.run(halfMix.filter(mix => mix.mixId === mixId && mix.round === round).result.headOption) /** * selects halfMix by mixId * * @param mixId String */ - def boxIdByMixId(mixId: String): Future[Option[String]] = db.run(halfMix.filter(mix => mix.id === mixId).map(_.halfMixBoxId).result.headOption) + def boxIdByMixId(mixId: String): Future[Option[String]] = db.run(halfMix.filter(mix => mix.mixId === mixId).map(_.halfMixBoxId).result.headOption) /** * delete halfMix by mixId @@ -95,8 +95,8 @@ class HalfMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param mixId String */ def deleteWithArchive(mixId: String): Future[Unit] = db.run(DBIO.seq( - halfMix.filter(mix => mix.id === mixId).delete, - halfMixArchive.filter(mix => mix.id === mixId).delete + halfMix.filter(mix => mix.mixId === mixId).delete, + halfMixArchive.filter(mix => mix.mixId === mixId).delete )) /** @@ -105,7 +105,7 @@ class HalfMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param mixId String * @param round Int */ - def deleteFutureRounds(mixId: String, round: Int): Future[Unit] = db.run(halfMix.filter(mix => mix.id === mixId && mix.round > round).delete).map(_ => ()) + def deleteFutureRounds(mixId: String, round: Int): Future[Unit] = db.run(halfMix.filter(mix => mix.mixId === mixId && mix.round > round).delete).map(_ => ()) /** * updates half mix by id @@ -113,7 +113,7 @@ class HalfMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide * @param new_mix HalfMix */ def updateById(new_mix: HalfMix)(implicit insertReason: String): Future[Unit] = db.run(DBIO.seq( - halfMix.filter(mix => mix.id === new_mix.mixId && mix.round === new_mix.round).delete, + halfMix.filter(mix => mix.mixId === new_mix.mixId && mix.round === new_mix.round).delete, halfMix += new_mix, halfMixArchive += (new_mix.mixId, new_mix.round, new_mix.createdTime, new_mix.halfMixBoxId, new_mix.isSpent, insertReason) )) @@ -126,7 +126,7 @@ class HalfMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide */ def setAsSpent(mixId: String, round: Int): Future[Unit] = { val query = for { - mix <- halfMix if mix.id === mixId && mix.round === round + mix <- halfMix if mix.mixId === mixId && mix.round === round } yield mix.isSpent db.run(query.update(true)).map(_ => ()) } @@ -140,4 +140,14 @@ class HalfMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvide halfMix += new_mix, halfMixArchive += (new_mix.mixId, new_mix.round, new_mix.createdTime, new_mix.halfMixBoxId, new_mix.isSpent, insertReason) )) + + /** + * selects halfMixBox by mixId and round + * + * @param mixId String + * @param round Int + */ + def getMixBoxIdByRound(mixId: String, round: Int): Future[Option[String]] = db.run(halfMix.filter(mix => + mix.mixId === mixId && mix.round === round).map(_.halfMixBoxId).result.headOption) + } diff --git a/mixer/app/dao/HopMixDAO.scala b/mixer/app/dao/HopMixDAO.scala index 7e13717..09c96dd 100644 --- a/mixer/app/dao/HopMixDAO.scala +++ b/mixer/app/dao/HopMixDAO.scala @@ -13,7 +13,7 @@ trait HopMixComponent { import profile.api._ class HopMixTable(tag: Tag) extends Table[HopMix](tag, "HOP_MIX") { - def id = column[String]("MIX_ID") + def mixId = column[String]("MIX_ID") def round = column[Int]("ROUND") @@ -21,9 +21,9 @@ trait HopMixComponent { def boxId = column[String]("BOX_ID") - def * = (id, round, createdTime, boxId) <> (HopMix.tupled, HopMix.unapply) + def * = (mixId, round, createdTime, boxId) <> (HopMix.tupled, HopMix.unapply) - def pk = primaryKey("pk_HOP_MIX", (id, round)) + def pk = primaryKey("pk_HOP_MIX", (mixId, round)) } } @@ -56,6 +56,13 @@ class HopMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider */ def clear: Future[Unit] = db.run(hopMixes.delete).map(_ => ()) + /** + * deletes hops by mixId + * + * @param mixId String + */ + def delete(mixId: String): Future[Unit] = db.run(hopMixes.filter(hop => hop.mixId === mixId).delete).map(_ => ()) + /** * deletes future hops by mixId * @@ -63,7 +70,7 @@ class HopMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * @param round Int */ def deleteFutureRounds(mixId: String, round: Int): Future[Unit] = db.run(hopMixes - .filter(hop => hop.id === mixId && hop.round > round).delete).map(_ => ()) + .filter(hop => hop.mixId === mixId && hop.round > round).delete).map(_ => ()) /** * updates hop by id @@ -71,7 +78,7 @@ class HopMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * @param hopMix HopMix */ def updateById(hopMix: HopMix): Future[Unit] = db.run(DBIO.seq( - hopMixes.filter(hop => hop.id === hopMix.mixId && hop.round === hopMix.round).delete, + hopMixes.filter(hop => hop.mixId === hopMix.mixId && hop.round === hopMix.round).delete, hopMixes += hopMix )) @@ -81,10 +88,19 @@ class HopMixDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * @param mixId String */ def getHopRound(mixId: String): Future[Option[Int]] = db.run(hopMixes - .filter(hop => hop.id === mixId) - .groupBy(hop => hop.id) - .map{ case (id, group) => group.map(_.round).max.get } + .filter(hop => hop.mixId === mixId) + .groupBy(hop => hop.mixId) + .map{ case (_, group) => group.map(_.round).max.get } .result.headOption ) + /** + * selects hopMixBoxId by mixId and round + * + * @param mixId String + * @param round Int + */ + def getMixBoxIdByRound(mixId: String, round: Int): Future[Option[String]] = db.run(hopMixes.filter(mix => + mix.mixId === mixId && mix.round === round).map(_.boxId).result.headOption) + } diff --git a/mixer/app/dao/MixStateDAO.scala b/mixer/app/dao/MixStateDAO.scala index 1ebb823..0a31c2f 100644 --- a/mixer/app/dao/MixStateDAO.scala +++ b/mixer/app/dao/MixStateDAO.scala @@ -13,13 +13,13 @@ trait MixStateComponent { import profile.api._ class MixStateTable(tag: Tag) extends Table[MixState](tag, "MIX_STATE") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def round = column[Int]("ROUND") def isAlice = column[Boolean]("IS_ALICE") - def * = (id, round, isAlice) <> (MixState.tupled, MixState.unapply) + def * = (mixId, round, isAlice) <> (MixState.tupled, MixState.unapply) } } @@ -57,7 +57,7 @@ class MixStateDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * * @param mixID String */ - def selectByMixId(mixID: String): Future[Option[MixState]] = db.run(mixes.filter(state => state.id === mixID).result.headOption) + def selectByMixId(mixID: String): Future[Option[MixState]] = db.run(mixes.filter(state => state.mixId === mixID).result.headOption) /** * returns min of number of rounds in mixingRequest and mixState by mixID @@ -67,7 +67,7 @@ class MixStateDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid */ def minRoundsByMixId(mixID: String, numRounds: Int): Future[Int] = { val query = for { - state <- mixes.filter(state => state.id === mixID).result.headOption + state <- mixes.filter(state => state.mixId === mixID).result.headOption minRounds <- DBIO.successful(Math min(state.getOrElse(throw new Exception("corresponding mixId not found in MixState table")).round, numRounds)) } yield minRounds db.run(query) @@ -78,14 +78,14 @@ class MixStateDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * * @param mixState MixState */ - def updateById(mixState: MixState): Future[Unit] = db.run(mixes.filter(mix => mix.id === mixState.id).update(mixState)).map(_ => ()) + def updateById(mixState: MixState): Future[Unit] = db.run(mixes.filter(mix => mix.mixId === mixState.id).update(mixState)).map(_ => ()) /** * deletes mix state by mixID * * @param mixId String */ - def delete(mixId: String): Future[Unit] = db.run(mixes.filter(mix => mix.id === mixId).delete).map(_ => ()) + def delete(mixId: String): Future[Unit] = db.run(mixes.filter(mix => mix.mixId === mixId).delete).map(_ => ()) /** * updates mix state by mixID, insert new mixStates if not exists (this happen in rescan process) @@ -93,7 +93,7 @@ class MixStateDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * @param mixState MixState */ def updateInRescan(mixState: MixState): Future[Unit] = db.run(DBIO.seq( - mixes.filter(mix => mix.id === mixState.id).delete, + mixes.filter(mix => mix.mixId === mixState.id).delete, mixes += mixState )) } diff --git a/mixer/app/dao/MixStateHistoryDAO.scala b/mixer/app/dao/MixStateHistoryDAO.scala index 9f96284..809a4b7 100644 --- a/mixer/app/dao/MixStateHistoryDAO.scala +++ b/mixer/app/dao/MixStateHistoryDAO.scala @@ -13,7 +13,7 @@ trait MixStateHistoryComponent { import profile.api._ class MixStateHistoryTable(tag: Tag) extends Table[MixHistory](tag, "MIX_STATE_HISTORY") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def round = column[Int]("ROUND") @@ -21,11 +21,11 @@ trait MixStateHistoryComponent { def createdTime = column[Long]("CREATED_TIME") - def * = (id, round, isAlice, createdTime) <> (MixHistory.tupled, MixHistory.unapply) + def * = (mixId, round, isAlice, createdTime) <> (MixHistory.tupled, MixHistory.unapply) } class MixStateHistoryArchivedTable(tag: Tag) extends Table[(String, Int, Boolean, Long, String)](tag, "MIX_STATE_HISTORY_ARCHIVED") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def round = column[Int]("ROUND") @@ -35,7 +35,7 @@ trait MixStateHistoryComponent { def reason = column[String]("REASON") - def * = (id, round, isAlice, createdTime, reason) + def * = (mixId, round, isAlice, createdTime, reason) } } @@ -69,7 +69,7 @@ class MixStateHistoryDAO @Inject()(protected val dbConfigProvider: DatabaseConfi * @param mixID String * @param round Int */ - def deleteFutureRounds(mixID: String, round: Int): Future[Unit] = db.run(mixHistories.filter(mix => mix.id === mixID && mix.round > round).delete).map(_ => ()) + def deleteFutureRounds(mixID: String, round: Int): Future[Unit] = db.run(mixHistories.filter(mix => mix.mixId === mixID && mix.round > round).delete).map(_ => ()) /** * updates mix state history by mixID @@ -77,7 +77,7 @@ class MixStateHistoryDAO @Inject()(protected val dbConfigProvider: DatabaseConfi * @param mixHistory MixHistory */ def updateById(mixHistory: MixHistory)(implicit insertReason: String): Future[Unit] = db.run(DBIO.seq( - mixHistories.filter(mix => mix.id === mixHistory.id && mix.round === mixHistory.round).delete, + mixHistories.filter(mix => mix.mixId === mixHistory.id && mix.round === mixHistory.round).delete, mixHistories += mixHistory, mixStatesArchive += (mixHistory.id, mixHistory.round, mixHistory.isAlice, mixHistory.time, insertReason) )) @@ -98,7 +98,7 @@ class MixStateHistoryDAO @Inject()(protected val dbConfigProvider: DatabaseConfi * @param mixId String */ def deleteWithArchive(mixId: String): Future[Unit] = db.run(DBIO.seq( - mixHistories.filter(mix => mix.id === mixId).delete, - mixStatesArchive.filter(mix => mix.id === mixId).delete + mixHistories.filter(mix => mix.mixId === mixId).delete, + mixStatesArchive.filter(mix => mix.mixId === mixId).delete )) } diff --git a/mixer/app/dao/MixTransactionsDAO.scala b/mixer/app/dao/MixTransactionsDAO.scala index 1ccb1cb..dbf15ac 100644 --- a/mixer/app/dao/MixTransactionsDAO.scala +++ b/mixer/app/dao/MixTransactionsDAO.scala @@ -1,7 +1,7 @@ package dao import javax.inject.{Inject, Singleton} -import models.Models.MixTransaction +import models.Transaction.MixTransaction import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile diff --git a/mixer/app/dao/MixingCovertRequestDAO.scala b/mixer/app/dao/MixingCovertRequestDAO.scala index 50d710b..3c19ee9 100644 --- a/mixer/app/dao/MixingCovertRequestDAO.scala +++ b/mixer/app/dao/MixingCovertRequestDAO.scala @@ -1,7 +1,7 @@ package dao import javax.inject.{Inject, Singleton} -import models.Models.MixCovertRequest +import models.Request.MixCovertRequest import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile @@ -21,7 +21,7 @@ trait MixingCovertRequestComponent { class MixCovertRequestTable(tag: Tag) extends Table[MixCovertRequest](tag, "MIXING_COVERT_REQUEST") { def nameCovert = column[String]("NAME_COVERT") - def id = column[String]("MIX_GROUP_ID", O.PrimaryKey) + def groupId = column[String]("MIX_GROUP_ID", O.PrimaryKey) def createdTime = column[Long]("CREATED_TIME") @@ -33,7 +33,7 @@ trait MixingCovertRequestComponent { def masterKey = column[BigInt]("MASTER_SECRET_GROUP") - def * = (nameCovert, id, createdTime, depositAddress, numRounds, isManualCovert, masterKey) <> (MixCovertRequest.tupled, MixCovertRequest.unapply) + def * = (nameCovert, groupId, createdTime, depositAddress, numRounds, isManualCovert, masterKey) <> (MixCovertRequest.tupled, MixCovertRequest.unapply) } } @@ -70,19 +70,19 @@ class MixingCovertRequestDAO @Inject()(protected val dbConfigProvider: DatabaseC /** * checks if the covertId exists in table or not * - * @param covertId String + * @param groupId String */ - def existsById(covertId: String): Future[Boolean] = db.run(covertRequests.filter(req => req.id === covertId).exists.result) + def existsById(groupId: String): Future[Boolean] = db.run(covertRequests.filter(req => req.groupId === groupId).exists.result) /** * updates nameCovert by covertId * - * @param covertId String + * @param groupId String * @param nameCovert String */ - def updateNameCovert(covertId: String, nameCovert: String): Future[Unit] = { + def updateNameCovert(groupId: String, nameCovert: String): Future[Unit] = { val query = for { - req <- covertRequests if req.id === covertId + req <- covertRequests if req.groupId === groupId } yield req.nameCovert db.run(query.update(nameCovert)).map(_ => ()) } @@ -96,7 +96,7 @@ class MixingCovertRequestDAO @Inject()(protected val dbConfigProvider: DatabaseC /** * selects request by covertId * - * @param covertId String + * @param groupId String */ - def selectCovertRequestByMixGroupId(covertId: String): Future[Option[MixCovertRequest]] = db.run(covertRequests.filter(req => req.id === covertId).result.headOption) + def selectCovertRequestByMixGroupId(groupId: String): Future[Option[MixCovertRequest]] = db.run(covertRequests.filter(req => req.groupId === groupId).result.headOption) } diff --git a/mixer/app/dao/MixingGroupRequestDAO.scala b/mixer/app/dao/MixingGroupRequestDAO.scala index 25b30c7..c6af43b 100644 --- a/mixer/app/dao/MixingGroupRequestDAO.scala +++ b/mixer/app/dao/MixingGroupRequestDAO.scala @@ -1,7 +1,9 @@ package dao +import models.Request.MixGroupRequest +import models.Status.GroupMixStatus + import javax.inject.{Inject, Singleton} -import models.Models.{MixGroupRequest, GroupMixStatus} import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile @@ -19,7 +21,7 @@ trait MixingGroupRequestComponent { ) class MixingGroupRequestTable(tag: Tag) extends Table[MixGroupRequest](tag, "MIXING_GROUP_REQUEST") { - def id = column[String]("MIX_GROUP_ID", O.PrimaryKey) + def groupId = column[String]("MIX_GROUP_ID", O.PrimaryKey) def neededAmount = column[Long]("AMOUNT") @@ -43,7 +45,7 @@ trait MixingGroupRequestComponent { def masterKey = column[BigInt]("MASTER_SECRET_GROUP") - def * = (id, neededAmount, status, createdTime, depositAddress, doneDeposit, tokenDoneDeposit, mixingAmount, mixingTokenAmount, neededTokenAmount, tokenId, masterKey) <> (MixGroupRequest.tupled, MixGroupRequest.unapply) + def * = (groupId, neededAmount, status, createdTime, depositAddress, doneDeposit, tokenDoneDeposit, mixingAmount, mixingTokenAmount, neededTokenAmount, tokenId, masterKey) <> (MixGroupRequest.tupled, MixGroupRequest.unapply) } } @@ -74,7 +76,7 @@ class MixingGroupRequestDAO @Inject()(protected val dbConfigProvider: DatabaseCo * selects id of all groupRequests * */ - def allIds: Future[Seq[String]] = db.run(groupRequests.map(_.id).result) + def allIds: Future[Seq[String]] = db.run(groupRequests.map(_.groupId).result) /** * deletes all of requests @@ -111,7 +113,7 @@ class MixingGroupRequestDAO @Inject()(protected val dbConfigProvider: DatabaseCo * * @param groupId String */ - def delete(groupId: String): Unit = db.run(groupRequests.filter(req => req.id === groupId).delete).map(_ => ()) + def delete(groupId: String): Unit = db.run(groupRequests.filter(req => req.groupId === groupId).delete).map(_ => ()) /** * TODO: Remove this function later, because it's also in refactor-fullMixer @@ -123,7 +125,7 @@ class MixingGroupRequestDAO @Inject()(protected val dbConfigProvider: DatabaseCo */ def updateDepositById(mixGroupId: String, deposit: Long, tokenDeposit: Long): Unit = { val query = for { - request <- groupRequests.filter(req => req.id === mixGroupId) + request <- groupRequests.filter(req => req.groupId === mixGroupId) } yield (request.doneDeposit, request.tokenDoneDeposit) db.run(query.update(deposit, tokenDeposit)) } @@ -136,7 +138,7 @@ class MixingGroupRequestDAO @Inject()(protected val dbConfigProvider: DatabaseCo */ def updateStatusById(mixGroupId: String, status: String): Unit = { val query = for { - request <- groupRequests.filter(req => req.id === mixGroupId) + request <- groupRequests.filter(req => req.groupId === mixGroupId) } yield request.status db.run(query.update(status)) } diff --git a/mixer/app/dao/MixingRequestsDAO.scala b/mixer/app/dao/MixingRequestsDAO.scala index 4b87713..75681f3 100644 --- a/mixer/app/dao/MixingRequestsDAO.scala +++ b/mixer/app/dao/MixingRequestsDAO.scala @@ -1,11 +1,11 @@ package dao -import models.Models.MixStatus.Complete -import models.Models.MixWithdrawStatus.Withdrawn -import models.Models.MixStatus.Queued +import models.Status.MixStatus.{Queued, Complete} +import models.Status.MixWithdrawStatus.Withdrawn +import models.Status.MixStatus +import models.Request.{MixRequest, MixingRequest} import javax.inject.{Inject, Singleton} -import models.Models.{MixRequest, MixStatus, MixingRequest} import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile @@ -29,7 +29,7 @@ trait MixingRequestsComponent { ) class MixingRequestsTable(tag: Tag) extends Table[MixingRequest](tag, "MIXING_REQUESTS") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def groupId = column[String]("MIX_GROUP_ID") @@ -61,9 +61,9 @@ trait MixingRequestsComponent { def masterKey = column[BigInt]("MASTER_SECRET") - def * = (id, groupId, amount, numRounds, mixStatus, createdTime, withdrawAddress, depositAddress, depositCompleted, neededAmount, numToken, withdrawStatus, mixingTokenAmount, neededTokenAmount, tokenId, masterKey) <> (MixingRequest.tupled, MixingRequest.unapply) + def * = (mixId, groupId, amount, numRounds, mixStatus, createdTime, withdrawAddress, depositAddress, depositCompleted, neededAmount, numToken, withdrawStatus, mixingTokenAmount, neededTokenAmount, tokenId, masterKey) <> (MixingRequest.tupled, MixingRequest.unapply) - def mixRequest = (id, groupId, amount, numRounds, mixStatus, createdTime, withdrawAddress, depositAddress, depositCompleted, neededAmount, numToken, withdrawStatus, mixingTokenAmount, neededTokenAmount, tokenId) <> (MixRequest.tupled, MixRequest.unapply) + def mixRequest = (mixId, groupId, amount, numRounds, mixStatus, createdTime, withdrawAddress, depositAddress, depositCompleted, neededAmount, numToken, withdrawStatus, mixingTokenAmount, neededTokenAmount, tokenId) <> (MixRequest.tupled, MixRequest.unapply) } } @@ -105,7 +105,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig */ def selectByMixId(mixID: String): Future[Option[MixRequest]] = { val query = for { - req <- mixingRequests if req.id === mixID + req <- mixingRequests if req.mixId === mixID } yield req.mixRequest db.run(query.result.headOption) } @@ -115,7 +115,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig * * @param mixID String */ - def selectAllByMixId(mixID: String): Future[Option[MixingRequest]] = db.run(mixingRequests.filter(req => req.id === mixID).result.headOption) + def selectAllByMixId(mixID: String): Future[Option[MixingRequest]] = db.run(mixingRequests.filter(req => req.mixId === mixID).result.headOption) /** * selects all requests (without masterKey) by groupId @@ -137,7 +137,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig def selectPartsByMixGroupId(groupId: String): Future[Seq[(String, Long, Long)]] = { val query = for { req <- mixingRequests if req.groupId === groupId - } yield (req.depositAddress, req.neededAmount, req.mixingTokenAmount) + } yield (req.depositAddress, req.neededAmount, req.neededTokenAmount) db.run(query.result) } @@ -148,7 +148,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig */ def selectMasterKey(mixID: String): Future[Option[BigInt]] = { val query = for { - req <- mixingRequests if req.id === mixID + req <- mixingRequests if req.mixId === mixID } yield req.masterKey db.run(query.result.headOption) } @@ -207,7 +207,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig */ def groupRequestsProgress(groupId: String, mixStatus: MixStatus): Future[Seq[(Int, Int)]] = { val query = for { - (request, state) <- mixingRequests.filter(req => req.groupId === groupId && req.mixStatus === mixStatus) join mixes on(_.id === _.id) + (request, state) <- mixingRequests.filter(req => req.groupId === groupId && req.mixStatus === mixStatus) join mixes on(_.mixId === _.mixId) } yield (request.numRounds, state.round) db.run(query.result) } @@ -220,7 +220,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig */ def updateAddress(mixID: String, address: String): Future[Unit] = { val query = for { - req <- mixingRequests if req.id === mixID + req <- mixingRequests if req.mixId === mixID } yield req.withdrawAddress db.run(query.update(address)).map(_ => ()) } @@ -231,7 +231,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig * @param mixId String * @param status String */ - def updateQueryWithMixId(mixId: String, status: String) = mixingRequests.filter(req => req.id === mixId).map(_.withdrawStatus).update(status) + def updateQueryWithMixId(mixId: String, status: String) = mixingRequests.filter(req => req.mixId === mixId).map(_.withdrawStatus).update(status) /** * updates withdraw status by mixID @@ -241,7 +241,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig */ def updateWithdrawStatus(mixID: String, withdrawStatus: String): Future[Unit] = { val query = for { - req <- mixingRequests if req.id === mixID + req <- mixingRequests if req.mixId === mixID } yield req.withdrawStatus db.run(query.update(withdrawStatus)).map(_ => ()) } @@ -254,7 +254,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig */ def updateMixStatus(mixID: String, mixStatus: MixStatus): Future[Unit] = { val query = for { - req <- mixingRequests if req.id === mixID + req <- mixingRequests if req.mixId === mixID } yield req.mixStatus db.run(query.update(mixStatus)).map(_ => ()) } @@ -266,7 +266,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig */ def withdrawTheRequest(mixID: String): Future[Unit] = { val query = for { - req <- mixingRequests if req.id === mixID + req <- mixingRequests if req.mixId === mixID } yield (req.mixStatus, req.withdrawStatus) db.run(query.update((Complete, Withdrawn.value))).map(_ => ()) } @@ -311,7 +311,7 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig * * @param mixId String */ - def delete(mixId: String): Future[Unit] = db.run(mixingRequests.filter(req => req.id === mixId).delete).map(_ => ()) + def delete(mixId: String): Future[Unit] = db.run(mixingRequests.filter(req => req.mixId === mixId).delete).map(_ => ()) /** * deletes request by mixId @@ -350,6 +350,6 @@ class MixingRequestsDAO @Inject()(protected val dbConfigProvider: DatabaseConfig * * @param mixId String */ - def isMixingErg(mixId: String): Future[Boolean] = db.run(mixingRequests.filter(req => req.id === mixId && req.tokenId === "").exists.result) + def isMixingErg(mixId: String): Future[Boolean] = db.run(mixingRequests.filter(req => req.mixId === mixId && req.tokenId === "").exists.result) } diff --git a/mixer/app/dao/Prune.scala b/mixer/app/dao/Prune.scala index 33fb51d..e789a64 100644 --- a/mixer/app/dao/Prune.scala +++ b/mixer/app/dao/Prune.scala @@ -2,16 +2,15 @@ package dao import app.Configs import helpers.ErgoMixerUtils -import models.Models.MixWithdrawStatus.Withdrawn -import models.Models.MixRequest -import network.{BlockExplorer, NetworkUtils} -import org.ergoplatform.appkit.BlockchainContext +import network.BlockExplorer +import models.Status.MixWithdrawStatus.Withdrawn +import models.Request.MixRequest import play.api.Logger import wallet.WalletHelper import javax.inject.Inject -class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils, explorer: BlockExplorer, +class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, explorer: BlockExplorer, daoUtils: DAOUtils, mixingGroupRequestDAO: MixingGroupRequestDAO, mixingCovertRequestDAO: MixingCovertRequestDAO, @@ -27,10 +26,10 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils emissionDAO: EmissionDAO, tokenEmissionDAO: TokenEmissionDAO, mixTransactionsDAO: MixTransactionsDAO, - rescanDAO: RescanDAO) { + rescanDAO: RescanDAO, + hopMixDAO: HopMixDAO) { private val logger: Logger = Logger(this.getClass) - - import networkUtils._ + private val pruneAfterMilliseconds = Configs.dbPruneAfter * 120L * 1000L // 120 for almost 2 minutes per block mining, 1000 for converting to milliseconds /** * prunes group mixes and covert mixes @@ -38,10 +37,8 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils def processPrune(): Unit = { try { if (Configs.dbPrune) { - usingClient { implicit ctx => - pruneGroupMixes() - pruneCovertMixes() - } + pruneGroupMixes() + pruneCovertMixes() } } catch { case a: Throwable => @@ -53,7 +50,7 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils /** * Will prune group mixes if possible one by one */ - def pruneGroupMixes()(implicit ctx: BlockchainContext): Unit = { + def pruneGroupMixes(): Unit = { val currentTime = WalletHelper.now val requests = daoUtils.awaitResult(mixingGroupRequestDAO.completed) requests.foreach(req => { @@ -64,7 +61,7 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils logger.warn(s"mixId ${mix.id} not found in Withdraw") currentTime }) - shouldPrune &= (currentTime - withdrawTime) >= Configs.dbPruneAfter * 120L + shouldPrune &= (currentTime - withdrawTime) >= pruneAfterMilliseconds }) if (shouldPrune) { mixes.foreach(mix => { @@ -83,7 +80,7 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils /** * will prune covert mix boxes one by one if possible */ - def pruneCovertMixes()(implicit ctx: BlockchainContext): Unit = { + def pruneCovertMixes(): Unit = { val currentTime = WalletHelper.now val requests = daoUtils.awaitResult(mixingCovertRequestDAO.all) requests.foreach(req => { @@ -93,7 +90,7 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils logger.warn(s"mixId ${mix.id} not found in Withdraw") currentTime }) - if ((currentTime - withdrawTime) >= Configs.dbPruneAfter * 120L) { + if ((currentTime - withdrawTime) >= pruneAfterMilliseconds) { val txId: String = daoUtils.awaitResult(withdrawDAO.selectByMixId(mix.id)).getOrElse(throw new Exception(s"mixId ${mix.id} not found in Withdraw")).txId val numConf = explorer.getTxNumConfirmations(txId) if (numConf >= Configs.dbPruneAfter) { @@ -103,7 +100,7 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils } }) // pruning distributed transactions that are mined and have been confirmed at least dbPruneAfter - val distributedTxs = daoUtils.awaitResult(distributeTransactionsDAO.zeroChainByMixGroupIdAndTime(req.id, currentTime - Configs.dbPruneAfter * 120L)) + val distributedTxs = daoUtils.awaitResult(distributeTransactionsDAO.zeroChainByMixGroupIdAndTime(req.id, currentTime - pruneAfterMilliseconds)) distributedTxs.foreach(tx => { val numConf = explorer.getTxNumConfirmations(tx.txId) if (numConf >= Configs.dbPruneAfter) { @@ -114,33 +111,34 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils }) } - def deleteMixBox(box: MixRequest): Unit = { - mixingRequestsDAO.delete(box.id) + def deleteMixBox(mix: MixRequest): Unit = { + mixingRequestsDAO.delete(mix.id) - unspentDepositsDAO.deleteByAddress(box.depositAddress) - spentDepositsDAO.deleteByAddress(box.depositAddress) + unspentDepositsDAO.deleteByAddress(mix.depositAddress) + spentDepositsDAO.deleteByAddress(mix.depositAddress) - mixStateDAO.delete(box.id) - mixStateHistoryDAO.deleteWithArchive(box.id) + mixStateDAO.delete(mix.id) + mixStateHistoryDAO.deleteWithArchive(mix.id) - val halfMixBoxId = halfMixDAO.boxIdByMixId(box.id) - val fullMixBodId = fullMixDAO.boxIdByMixId(box.id) + val halfMixBoxId = halfMixDAO.boxIdByMixId(mix.id) + val fullMixBoxId = fullMixDAO.boxIdByMixId(mix.id) mixTransactionsDAO.delete(daoUtils.awaitResult(halfMixBoxId).getOrElse({ logger.warn(s"halfMixBoxId $halfMixBoxId not found in mixTransaction") "" })) - mixTransactionsDAO.delete(daoUtils.awaitResult(fullMixBodId).getOrElse({ - logger.warn(s"fullMixBoxId $fullMixBodId not found in mixTransaction") + mixTransactionsDAO.delete(daoUtils.awaitResult(fullMixBoxId).getOrElse({ + logger.warn(s"fullMixBoxId $fullMixBoxId not found in mixTransaction") "" })) - halfMixDAO.deleteWithArchive(box.id) - fullMixDAO.deleteWithArchive(box.id) + halfMixDAO.deleteWithArchive(mix.id) + fullMixDAO.deleteWithArchive(mix.id) + hopMixDAO.delete(mix.id) - withdrawDAO.deleteWithArchive(box.id) - emissionDAO.delete(box.id) - tokenEmissionDAO.delete(box.id) - rescanDAO.deleteWithArchive(box.id) + withdrawDAO.deleteWithArchive(mix.id) + emissionDAO.delete(mix.id) + tokenEmissionDAO.delete(mix.id) + rescanDAO.deleteWithArchive(mix.id) } /** @@ -149,12 +147,12 @@ class Prune @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils * @param groupId group mix request */ def deleteGroupMix(groupId: String): Unit = { - val boxes = daoUtils.awaitResult(mixingRequestsDAO.selectByMixGroupId(groupId)) + val mixes = daoUtils.awaitResult(mixingRequestsDAO.selectByMixGroupId(groupId)) mixingGroupRequestDAO.delete(groupId) mixingRequestsDAO.deleteByGroupId(groupId) distributeTransactionsDAO.deleteByGroupId(groupId) - boxes.foreach(box => { - deleteMixBox(box) + mixes.foreach(mix => { + deleteMixBox(mix) }) } } diff --git a/mixer/app/dao/RescanDAO.scala b/mixer/app/dao/RescanDAO.scala index db0e99d..267b31e 100644 --- a/mixer/app/dao/RescanDAO.scala +++ b/mixer/app/dao/RescanDAO.scala @@ -1,7 +1,7 @@ package dao import javax.inject.{Inject, Singleton} -import models.Models.PendingRescan +import models.Rescan.PendingRescan import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile @@ -13,7 +13,7 @@ trait RescanComponent { import profile.api._ class RescanTable(tag: Tag) extends Table[PendingRescan](tag, "RESCAN") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def createdTime = column[Long]("CREATED_TIME") @@ -25,11 +25,11 @@ trait RescanComponent { def mixBoxId = column[String]("MIX_BOX_ID") - def * = (id, createdTime, round, goBackward, boxType, mixBoxId) <> (PendingRescan.tupled, PendingRescan.unapply) + def * = (mixId, createdTime, round, goBackward, boxType, mixBoxId) <> (PendingRescan.tupled, PendingRescan.unapply) } class RescanArchivedTable(tag: Tag) extends Table[(String, Long, Int, Boolean, String, String, String)](tag, "RESCAN_ARCHIVE") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def createdTime = column[Long]("CREATED_TIME") @@ -43,7 +43,7 @@ trait RescanComponent { def reason = column[String]("REASON") - def * = (id, createdTime, round, goBackward, boxType, mixBoxId, reason) + def * = (mixId, createdTime, round, goBackward, boxType, mixBoxId, reason) } } @@ -70,7 +70,7 @@ class RescanDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * * @param mixID String */ - def delete(mixID: String): Future[Unit] = db.run(rescans.filter(rescan => rescan.id === mixID).delete).map(_ => ()) + def delete(mixID: String): Future[Unit] = db.run(rescans.filter(rescan => rescan.mixId === mixID).delete).map(_ => ()) /** * deletes rescan by mixID @@ -78,8 +78,8 @@ class RescanDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * @param mixId String */ def deleteWithArchive(mixId: String): Future[Unit] = db.run(DBIO.seq( - rescans.filter(rescan => rescan.id === mixId).delete, - rescansArchive.filter(rescan => rescan.id === mixId).delete + rescans.filter(rescan => rescan.mixId === mixId).delete, + rescansArchive.filter(rescan => rescan.mixId === mixId).delete )) /** @@ -88,7 +88,7 @@ class RescanDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvider * @param new_rescan PendingRescan */ def updateById(new_rescan: PendingRescan)(implicit insertReason: String): Future[Unit] = db.run(DBIO.seq( - rescans.filter(rescan => rescan.id === new_rescan.mixId).delete, + rescans.filter(rescan => rescan.mixId === new_rescan.mixId).delete, rescans += new_rescan, rescansArchive += (new_rescan.mixId, new_rescan.time, new_rescan.round, new_rescan.goBackward, new_rescan.boxType, new_rescan.mixBoxId, insertReason) )) diff --git a/mixer/app/dao/WithdrawCovertTokenDAO.scala b/mixer/app/dao/WithdrawCovertTokenDAO.scala index aad0545..32c9501 100644 --- a/mixer/app/dao/WithdrawCovertTokenDAO.scala +++ b/mixer/app/dao/WithdrawCovertTokenDAO.scala @@ -1,10 +1,11 @@ package dao -import javax.inject.{Inject, Singleton} -import models.Models.{CovertAsset, CovertAssetWithdrawStatus, CovertAssetWithdrawTx} +import models.Status.CovertAssetWithdrawStatus +import models.Transaction.CovertAssetWithdrawTx import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile +import javax.inject.{Inject, Singleton} import scala.concurrent.{ExecutionContext, Future} trait WithdrawCovertTokenComponent { @@ -13,7 +14,7 @@ trait WithdrawCovertTokenComponent { import profile.api._ class WithdrawCovertTokenTable(tag: Tag) extends Table[CovertAssetWithdrawTx](tag, "WITHDRAW_COVERT_TOKEN") { - def covertId = column[String]("MIX_GROUP_ID") + def groupId = column[String]("MIX_GROUP_ID") def tokenId = column[String]("TOKEN_ID") @@ -27,9 +28,9 @@ trait WithdrawCovertTokenComponent { def tx = column[Array[Byte]]("TX") - def * = (covertId, tokenId, withdrawAddress, createdTime, withdrawStatus, txId, tx) <> (CovertAssetWithdrawTx.tupled, CovertAssetWithdrawTx.unapply) + def * = (groupId, tokenId, withdrawAddress, createdTime, withdrawStatus, txId, tx) <> (CovertAssetWithdrawTx.tupled, CovertAssetWithdrawTx.unapply) - def pk = primaryKey("pk_WITHDRAW_COVERT_TOKEN", (covertId, tokenId, createdTime)) + def pk = primaryKey("pk_WITHDRAW_COVERT_TOKEN", (groupId, tokenId, createdTime)) } } @@ -65,7 +66,7 @@ class WithdrawCovertTokenDAO @Inject()(protected val dbConfigProvider: DatabaseC * checks if a non-complete request for withdraw of this asset is already exists or not * */ - def isActiveRequest(covertId: String, tokenId: String): Future[Boolean] = db.run(covertTokenTx.filter(token => token.covertId === covertId && token.tokenId === tokenId && token.withdrawStatus =!= "complete").exists.result) + def isActiveRequest(covertId: String, tokenId: String): Future[Boolean] = db.run(covertTokenTx.filter(token => token.groupId === covertId && token.tokenId === tokenId && token.withdrawStatus =!= "complete").exists.result) /** * updates txId and txBytes for tokens of tokenIds in covert @@ -75,7 +76,7 @@ class WithdrawCovertTokenDAO @Inject()(protected val dbConfigProvider: DatabaseC * @param txId String * @param tx Array[Byte] */ - def updateTx(covertId: String, tokenIds: Seq[String], txId: String, tx: Array[Byte]): Future[Unit] = db.run(covertTokenTx.filter(token => token.covertId === covertId && token.withdrawStatus =!= CovertAssetWithdrawStatus.Complete.value && token.tokenId.inSet(tokenIds)).map(token => (token.txId, token.tx, token.withdrawStatus)).update(txId, tx, CovertAssetWithdrawStatus.Requested.value)).map(_ => ()) + def updateTx(covertId: String, tokenIds: Seq[String], txId: String, tx: Array[Byte]): Future[Unit] = db.run(covertTokenTx.filter(token => token.groupId === covertId && token.withdrawStatus =!= CovertAssetWithdrawStatus.Complete.value && token.tokenId.inSet(tokenIds)).map(token => (token.txId, token.tx, token.withdrawStatus)).update(txId, tx, CovertAssetWithdrawStatus.Requested.value)).map(_ => ()) /** * sets a request as complete by pair of covertId and tokenId @@ -85,7 +86,7 @@ class WithdrawCovertTokenDAO @Inject()(protected val dbConfigProvider: DatabaseC */ def setRequestComplete(covertId: String, tokenId: String): Future[Unit] = { val query = for { - token <- covertTokenTx if token.covertId === covertId && token.tokenId === tokenId && token.withdrawStatus === CovertAssetWithdrawStatus.Requested.value + token <- covertTokenTx if token.groupId === covertId && token.tokenId === tokenId && token.withdrawStatus === CovertAssetWithdrawStatus.Requested.value } yield token.withdrawStatus db.run(query.update("complete")).map(_ => ()) } @@ -98,7 +99,7 @@ class WithdrawCovertTokenDAO @Inject()(protected val dbConfigProvider: DatabaseC */ def resetRequest(covertId: String, tokenId: String): Future[Unit] = { val query = for { - token <- covertTokenTx if token.covertId === covertId && token.tokenId === tokenId && token.withdrawStatus === CovertAssetWithdrawStatus.Requested.value + token <- covertTokenTx if token.groupId === covertId && token.tokenId === tokenId && token.withdrawStatus === CovertAssetWithdrawStatus.Requested.value } yield (token.txId, token.tx, token.withdrawStatus) db.run(query.update("", Array.empty[Byte], CovertAssetWithdrawStatus.NoWithdrawYet.value)).map(_ => ()) } diff --git a/mixer/app/dao/WithdrawDAO.scala b/mixer/app/dao/WithdrawDAO.scala index b17d304..065b0dd 100644 --- a/mixer/app/dao/WithdrawDAO.scala +++ b/mixer/app/dao/WithdrawDAO.scala @@ -1,8 +1,8 @@ package dao import javax.inject.{Inject, Singleton} -import models.Models.WithdrawTx -import models.Models.MixWithdrawStatus.{AgeUSDRequested, HopRequested, UnderHop, WithdrawRequested} +import models.Transaction.WithdrawTx +import models.Status.MixWithdrawStatus.{AgeUSDRequested, HopRequested, UnderHop, WithdrawRequested} import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider} import slick.jdbc.JdbcProfile @@ -14,7 +14,7 @@ trait WithdrawComponent { import profile.api._ class WithdrawTable(tag: Tag) extends Table[WithdrawTx](tag, "WITHDRAW") { - def id = column[String]("MIX_ID", O.PrimaryKey) + def mixId = column[String]("MIX_ID", O.PrimaryKey) def txId = column[String]("TX_ID") @@ -26,11 +26,11 @@ trait WithdrawComponent { def additionalInfo = column[String]("ADDITIONAL_INFO") - def * = (id, txId, createdTime, boxId, tx, additionalInfo) <> (WithdrawTx.tupled, WithdrawTx.unapply) + def * = (mixId, txId, createdTime, boxId, tx, additionalInfo) <> (WithdrawTx.tupled, WithdrawTx.unapply) } class WithdrawArchivedTable(tag: Tag) extends Table[(String, String, Long, String, Array[Byte], String, String)](tag, "WITHDRAW_ARCHIVED") { - def id = column[String]("MIX_ID") + def mixId = column[String]("MIX_ID") def txId = column[String]("TX_ID") @@ -44,7 +44,7 @@ trait WithdrawComponent { def reason = column[String]("REASON") - def * = (id, txId, createdTime, boxId, tx, additionalInfo, reason) + def * = (mixId, txId, createdTime, boxId, tx, additionalInfo, reason) } } @@ -86,7 +86,7 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * * @param mixID String */ - def selectByMixId(mixID: String): Future[Option[WithdrawTx]] = db.run(withdraws.filter(tx => tx.id === mixID).result.headOption) + def selectByMixId(mixID: String): Future[Option[WithdrawTx]] = db.run(withdraws.filter(tx => tx.mixId === mixID).result.headOption) /** * inserts withdraw into withdraw and withdrawArchived tables and updates mixingRequests table @@ -103,7 +103,7 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * @param withdraw_stat String */ def updateById(new_withdraw: WithdrawTx, withdraw_stat: String)(implicit insertReason: String): Future[Unit] = db.run(DBIO.seq( - withdraws.filter(withdraw => withdraw.id === new_withdraw.mixId).delete, + withdraws.filter(withdraw => withdraw.mixId === new_withdraw.mixId).delete, withdraws += new_withdraw, withdrawsArchive += (new_withdraw.mixId, new_withdraw.txId, new_withdraw.time, new_withdraw.boxId, new_withdraw.txBytes, new_withdraw.additionalInfo, insertReason), mixingRequestsDAO.updateQueryWithMixId(new_withdraw.mixId, withdraw_stat) @@ -115,7 +115,7 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * @param new_withdraw WithdrawTx */ def insertAndArchive(new_withdraw: WithdrawTx)(implicit insertReason: String): Future[Unit] = db.run(DBIO.seq( - withdraws.filter(withdraw => withdraw.id === new_withdraw.mixId).delete, + withdraws.filter(withdraw => withdraw.mixId === new_withdraw.mixId).delete, withdraws += new_withdraw, withdrawsArchive += (new_withdraw.mixId, new_withdraw.txId, new_withdraw.time, new_withdraw.boxId, new_withdraw.txBytes, new_withdraw.additionalInfo, insertReason) )) @@ -126,9 +126,9 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid */ def getWithdrawals: Future[(Seq[WithdrawTx], Seq[WithdrawTx], Seq[WithdrawTx])] = { val query = for { - withdraw <- (mixingRequests.filter(req => req.withdrawStatus === WithdrawRequested.value || req.withdrawStatus === UnderHop.value) join withdraws on(_.id === _.id)).map(_._2).result - mint <- (mixingRequests.filter(req => req.withdrawStatus === AgeUSDRequested.value) join withdraws on(_.id === _.id)).map(_._2).result - hop <- (mixingRequests.filter(req => req.withdrawStatus === HopRequested.value) join withdraws on(_.id === _.id)).map(_._2).result + withdraw <- (mixingRequests.filter(req => req.withdrawStatus === WithdrawRequested.value || req.withdrawStatus === UnderHop.value) join withdraws on(_.mixId === _.mixId)).map(_._2).result + mint <- (mixingRequests.filter(req => req.withdrawStatus === AgeUSDRequested.value) join withdraws on(_.mixId === _.mixId)).map(_._2).result + hop <- (mixingRequests.filter(req => req.withdrawStatus === HopRequested.value) join withdraws on(_.mixId === _.mixId)).map(_._2).result } yield (withdraw, mint, hop) db.run(query) } @@ -139,7 +139,7 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid */ def getMintings: Future[Seq[WithdrawTx]] = { val query = for { - mint <- (mixingRequests.filter(req => req.withdrawStatus === AgeUSDRequested.value) join withdraws on(_.id === _.id)).map(_._2).result + mint <- (mixingRequests.filter(req => req.withdrawStatus === AgeUSDRequested.value) join withdraws on(_.mixId === _.mixId)).map(_._2).result } yield mint db.run(query) } @@ -149,7 +149,7 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * * @param mixId String */ - def delete(mixId: String): Future[Unit] = db.run(withdraws.filter(withdraw => withdraw.id === mixId).delete).map(_ => ()) + def delete(mixId: String): Future[Unit] = db.run(withdraws.filter(withdraw => withdraw.mixId === mixId).delete).map(_ => ()) /** * delete withdraw request by mixId @@ -157,8 +157,8 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * @param mixId String */ def deleteWithArchive(mixId: String): Future[Unit] = db.run(DBIO.seq( - withdraws.filter(withdraw => withdraw.id === mixId).delete, - withdrawsArchive.filter(withdraw => withdraw.id === mixId).delete + withdraws.filter(withdraw => withdraw.mixId === mixId).delete, + withdrawsArchive.filter(withdraw => withdraw.mixId === mixId).delete )) /** @@ -168,7 +168,7 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * @param boxId String */ def shouldWithdraw(mixId: String, boxId: String)(implicit insertReason: String): Boolean = { - val withdrawMix = daoUtils.awaitResult(db.run(withdraws.filter(withdraw => withdraw.id === mixId).result.headOption)).getOrElse(return true) + val withdrawMix = daoUtils.awaitResult(db.run(withdraws.filter(withdraw => withdraw.mixId === mixId).result.headOption)).getOrElse(return true) !withdrawMix.boxId.contains(boxId) } @@ -177,5 +177,5 @@ class WithdrawDAO @Inject()(protected val dbConfigProvider: DatabaseConfigProvid * * @param mixID String */ - def selectCreatedTimeByMixId(mixID: String): Future[Option[Long]] = db.run(withdraws.filter(tx => tx.id === mixID).map(_.createdTime).result.headOption) + def selectCreatedTimeByMixId(mixID: String): Future[Option[Long]] = db.run(withdraws.filter(tx => tx.mixId === mixID).map(_.createdTime).result.headOption) } diff --git a/mixer/app/helpers/ErgoMixerUtils.scala b/mixer/app/helpers/ErgoMixerUtils.scala index 4623fe9..d2a1d46 100644 --- a/mixer/app/helpers/ErgoMixerUtils.scala +++ b/mixer/app/helpers/ErgoMixerUtils.scala @@ -3,16 +3,26 @@ package helpers import java.io._ import java.security.SecureRandom import java.util.zip.{ZipEntry, ZipInputStream, ZipOutputStream} - import app.Configs +import dao.DAOUtils + import javax.inject.Inject -import network.NetworkUtils +import org.ergoplatform.appkit.BlockchainContext import play.api.Logger -class ErgoMixerUtils @Inject()(networkUtils: NetworkUtils) { +import java.text.SimpleDateFormat +import java.util.Date + +class ErgoMixerUtils @Inject()(daoUtils: DAOUtils) { private val logger: Logger = Logger(this.getClass) - def getFee(tokenId: String, tokenAmount: Long, ergAmount: Long, isFull: Boolean): Long = { + def currentDateTimeString(pattern: String = "yyyy-MM-dd'T'HH-mm-ss"): String = { + val date = new Date() + val formatter = new SimpleDateFormat(pattern) + formatter.format(date) + } + + def getFee(tokenId: String, isFull: Boolean): Long = { if (tokenId.nonEmpty) { if (isFull) Configs.defaultFullTokenFee else Configs.defaultHalfTokenFee @@ -30,7 +40,7 @@ class ErgoMixerUtils @Inject()(networkUtils: NetworkUtils) { sw.toString } - def getRandomValidBoxId(origBoxIds: Seq[String]): Option[String] = networkUtils.usingClient { implicit ctx => + def getRandomValidBoxId(origBoxIds: Seq[String])(implicit ctx: BlockchainContext): Option[String] = { val random = new SecureRandom() val boxIds = new scala.util.Random(random).shuffle(origBoxIds) boxIds.find { boxId => @@ -39,23 +49,23 @@ class ErgoMixerUtils @Inject()(networkUtils: NetworkUtils) { true } catch { case a: Throwable => - logger.error(s" Error reading boxId ${boxId}: " + a.getMessage) + logger.error(s" Error reading boxId $boxId: " + a.getMessage) false } } } - def getRandom(seq: Seq[String]): Option[String] = networkUtils.usingClient { implicit ctx => + def getRandom(seq: Seq[String]): Option[String] = { val random = new SecureRandom() new scala.util.Random(random).shuffle(seq).headOption } def backup(): String = { - val path = System.getProperty("user.home") + "/ergoMixer" - val zip = new File(path + "/ergoMixerBackup.zip") + val (dbUrl, baseDbUrl) = daoUtils.getDbUrl + val zip = new File(baseDbUrl + s"ergoMixerBackup-${currentDateTimeString()}.zip") if (zip.exists()) zip.delete() - val toZip = Seq(s"$path/database.mv.db", s"$path/database.trace.db") + val toZip = Seq(s"$dbUrl.mv.db", s"$dbUrl.trace.db") val buf = new Array[Byte](2048) val out = new ZipOutputStream(new FileOutputStream(zip)) toZip.map(name => new File(name)).foreach(file => { @@ -74,14 +84,14 @@ class ErgoMixerUtils @Inject()(networkUtils: NetworkUtils) { } def restore(): Unit = { - val path = System.getProperty("user.home") + "/ergoMixer" - val zip = new File(path + "/ergoMixerRestore.zip") + val (_, baseDbUrl) = daoUtils.getDbUrl + val zip = new File(baseDbUrl + "ergoMixerRestore.zip") if (zip.exists() && zip.length() > 0) { val buf = new Array[Byte](2048) val in = new ZipInputStream(new FileInputStream(zip)) var zipEntry = in.getNextEntry while (zipEntry != null) { - val nf = new File(path, zipEntry.getName) + val nf = new File(baseDbUrl, zipEntry.getName) if (nf.exists()) nf.delete() val fos = new FileOutputStream(nf) var len = in.read(buf) diff --git a/mixer/app/mixer/CovertMixer.scala b/mixer/app/mixer/CovertMixer.scala index 6a9e03e..3252d3a 100644 --- a/mixer/app/mixer/CovertMixer.scala +++ b/mixer/app/mixer/CovertMixer.scala @@ -5,7 +5,7 @@ import mixinterface.AliceOrBob import helpers.ErgoMixerUtils import javax.inject.{Inject, Singleton} -import models.Models.{CovertAsset, CovertAssetWithdrawStatus, CovertAssetWithdrawTx, DistributeTx, EndBox, MixCovertRequest, OutBox} +import models.Models.CovertAsset import network.{BlockExplorer, NetworkUtils} import org.ergoplatform.ErgoAddressEncoder import org.ergoplatform.appkit.{Address, ErgoToken, ErgoTreeTemplate} @@ -15,6 +15,10 @@ import wallet.{Wallet, WalletHelper} import scala.collection.mutable import scala.collection.JavaConverters._ import dao.{CovertAddressesDAO, CovertDefaultsDAO, DAOUtils, DistributeTransactionsDAO, MixingCovertRequestDAO, MixingRequestsDAO, WithdrawCovertTokenDAO} +import models.Box.{EndBox, OutBox} +import models.Request.MixCovertRequest +import models.Status.CovertAssetWithdrawStatus +import models.Transaction.{CovertAssetWithdrawTx, DistributeTx} import scorex.crypto.hash.Sha256 import scorex.util.encode.Base16 import sigmastate.Values.ErgoTree @@ -226,7 +230,7 @@ class CovertMixer @Inject()(ergoMixer: ErgoMixer, aliceOrBob: AliceOrBob, case None => spendingTxId = "" } - if ((!spendingTxId.isEmpty && spendingTxId != req.id) || outputs.contains(boxId)) { + if ((spendingTxId.nonEmpty && spendingTxId != req.id) || outputs.contains(boxId)) { false } else true @@ -283,7 +287,7 @@ class CovertMixer @Inject()(ergoMixer: ErgoMixer, aliceOrBob: AliceOrBob, * */ def processRequestedWithdrawAsset(covertId: String, tokenId: String, txId: String, tx: Array[Byte]): Unit = { - logger.info(s"[covert: ${covertId}] processing withdraw request of token ${tokenId}...") + logger.info(s"[covert: $covertId] processing withdraw request of token $tokenId...") if (txId != "") { // check confirmation val confNum = explorer.getTxNumConfirmations(txId) @@ -337,7 +341,7 @@ class CovertMixer @Inject()(ergoMixer: ErgoMixer, aliceOrBob: AliceOrBob, * */ def withdrawAsset(covertId: String, withdrawAddress: String, tokenIds: Seq[String]): Unit = { - logger.info(s"[covert: ${covertId}] processing withdraw request of token list [${tokenIds.mkString(",")}]...") + logger.info(s"[covert: $covertId] processing withdraw request of token list [${tokenIds.mkString(",")}]...") val covertInfo = ergoMixer.covertInfoById(covertId) val depositAddress = covertInfo._1 val proverDlogSecrets = covertInfo._2.bigInteger diff --git a/mixer/app/mixer/Deposits.scala b/mixer/app/mixer/Deposits.scala index 1caace7..0187285 100644 --- a/mixer/app/mixer/Deposits.scala +++ b/mixer/app/mixer/Deposits.scala @@ -38,7 +38,7 @@ class Deposits @Inject()( } } - private implicit val insertReason = "Deposits.insertDeposit" + private implicit val insertReason: String = "Deposits.insertDeposit" /** * inserts deposits into db diff --git a/mixer/app/mixer/ErgoMixer.scala b/mixer/app/mixer/ErgoMixer.scala index 4481bbe..eac8a2e 100644 --- a/mixer/app/mixer/ErgoMixer.scala +++ b/mixer/app/mixer/ErgoMixer.scala @@ -1,19 +1,22 @@ package mixer -import java.util.UUID import app.Configs -import wallet.WalletHelper._ - -import javax.inject.Inject -import models.Models.MixStatus.{Complete, Queued, Running} -import models.Models.MixWithdrawStatus.{HopRequested, NoWithdrawYet, WithdrawRequested, Withdrawn} +import dao._ +import models.Box.MixingBox import models.Models._ +import models.Request.{MixCovertRequest, MixGroupRequest, MixRequest, MixingRequest} +import models.Status.MixStatus.{Queued, Running} +import models.Status.MixWithdrawStatus.{HopRequested, NoWithdrawYet, WithdrawRequested, Withdrawn} +import models.Status.{GroupMixStatus, MixStatus} +import models.Transaction.{CreateWithdraw, Withdraw} import network.NetworkUtils import play.api.Logger +import wallet.WalletHelper._ import wallet.{Wallet, WalletHelper} +import java.util.UUID +import javax.inject.Inject import scala.collection.mutable -import dao._ class ErgoMixer @Inject()( @@ -61,7 +64,7 @@ class ErgoMixer @Inject()( val req: MixCovertRequest = MixCovertRequest(nameCovert, mixId, now, depositAddress, numRounds, privateKey.nonEmpty, masterSecret) val covertAddresses = addresses.map({(mixId, _)}) val asset = CovertAsset(mixId, "", lastErgRing, 0L, now) - covertsDAO.createCovert(req, covertAddresses, asset) + daoUtils.awaitResult(covertsDAO.createCovert(req, covertAddresses, asset)) logger.info(s"covert address $mixId is created, addr: $depositAddress. you can add supported asset for it.") depositAddress } @@ -267,9 +270,9 @@ class ErgoMixer @Inject()( val masterSecret = randBigInt val wallet = new Wallet(masterSecret) val depositSecret = wallet.getSecret(-1) - val depositAddress = WalletHelper.getProveDlogAddress(depositSecret, ctx) + val depositAddress = WalletHelper.getAddressOfSecret(depositSecret) val mixId = UUID.randomUUID().toString - val req = MixingRequest(mixId, topId, ergRing, numRounds, MixStatus.fromString(Queued.value), now, withdrawAddress, depositAddress, false, ergNeeded, numRounds, NoWithdrawYet.value, tokenRing, tokenNeeded, mixingTokenId, masterSecret) + val req = MixingRequest(mixId, topId, ergRing, numRounds, MixStatus.fromString(Queued.value), now, withdrawAddress, depositAddress, depositCompleted = false, ergNeeded, numRounds, NoWithdrawYet.value, tokenRing, tokenNeeded, mixingTokenId, masterSecret) mixingRequestsDAO.insert(req) depositAddress } @@ -296,7 +299,7 @@ class ErgoMixer @Inject()( var mixingTokenAmount: Long = 0 var mixingTokenId: String = "" val depositSecret = wallet.getSecret(-1) - val depositAddress = WalletHelper.getProveDlogAddress(depositSecret, ctx) + val depositAddress = WalletHelper.getAddressOfSecret(depositSecret) val mixId = UUID.randomUUID().toString mixRequests.foreach(mixBox => { val price = mixBox.price @@ -321,14 +324,14 @@ class ErgoMixer @Inject()( /** * @return returns group mixes */ - def getMixRequestGroups = { + def getMixRequestGroups: Seq[MixGroupRequest] = { daoUtils.awaitResult(mixingGroupRequestDAO.all) } /** * @return returns not completed group mixes */ - def getMixRequestGroupsActive = { + def getMixRequestGroupsActive: Seq[MixGroupRequest] = { daoUtils.awaitResult(mixingGroupRequestDAO.active) } @@ -363,7 +366,7 @@ class ErgoMixer @Inject()( /** * @return all group mixes */ - def getMixRequestGroupsComplete = { + def getMixRequestGroupsComplete: Seq[MixGroupRequest] = { daoUtils.awaitResult(mixingGroupRequestDAO.completed) } @@ -373,7 +376,7 @@ class ErgoMixer @Inject()( * @param id id of the group or covert request * @return mix box info list of a specific group, whether it is half or full box, and whether it is withdrawn or not including tx id */ - def getMixes(id: String, status: String) = { + def getMixes(id: String, status: String): Seq[Mix] = { val boxes: Seq[MixRequest] = daoUtils.awaitResult({ if (status == "all") mixingRequestsDAO.selectByMixGroupId(id) else if (status == "active") mixingRequestsDAO.selectActiveRequests(id, Withdrawn.value) diff --git a/mixer/app/mixer/FullMixer.scala b/mixer/app/mixer/FullMixer.scala index f2ca665..4367bfe 100644 --- a/mixer/app/mixer/FullMixer.scala +++ b/mixer/app/mixer/FullMixer.scala @@ -1,18 +1,21 @@ package mixer import app.Configs -import mixinterface.{AliceOrBob, TokenErgoMix} +import dao._ import helpers.ErgoMixerUtils -import wallet.WalletHelper.now - -import javax.inject.Inject -import models.Models.MixStatus.{Complete, Running} -import models.Models.MixWithdrawStatus.{HopRequested, WithdrawRequested} -import models.Models.{CreateMixTransaction, FullMix, HalfMix, MixHistory, MixState, MixTransaction, OutBox, PendingRescan, WithdrawTx} +import mixinterface.{AliceOrBob, TokenErgoMix} +import models.Box.OutBox +import models.Models.{FullMix, HalfMix, MixHistory, MixState} +import models.Rescan.PendingRescan +import models.Status.MixStatus.Complete +import models.Status.MixWithdrawStatus.{HopRequested, WithdrawRequested} +import models.Transaction.{MixTransaction, WithdrawTx} import network.{BlockExplorer, NetworkUtils} import play.api.Logger +import wallet.WalletHelper.now import wallet.{Wallet, WalletHelper} -import dao.{AllMixDAO, DAOUtils, EmissionDAO, FullMixDAO, HalfMixDAO, MixStateDAO, MixStateHistoryDAO, MixTransactionsDAO, MixingRequestsDAO, RescanDAO, TokenEmissionDAO, WithdrawDAO} + +import javax.inject.Inject class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils, explorer: BlockExplorer, @@ -87,7 +90,7 @@ class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils } } - private implicit val insertReason = "FullMixer.processFullMix" + private implicit val insertReason: String = "FullMixer.processFullMix" private def str(isAlice: Boolean) = if (isAlice) "Alice" else "Bob" @@ -147,12 +150,12 @@ class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils if (withdrawDAO.shouldWithdraw(mixId, fullMixBoxId) && optFeeEmissionBoxId.nonEmpty) { val tx = if (withdrawStatus.equals(HopRequested.value)) { val hopSecret = wallet.getSecret(0, toFirst = true) - val hopAddress = WalletHelper.getProveDlogAddress(hopSecret, ctx) + val hopAddress = WalletHelper.getAddressOfSecret(hopSecret) val tx = aliceOrBob.spendFullMixBox(isAlice, secret, fullMixBoxId, hopAddress, Array[String](optFeeEmissionBoxId.get), Configs.defaultHalfFee, withdrawAddress, broadCast = false) val txBytes = tx.toJson(false).getBytes("utf-16") val new_withdraw = WithdrawTx(mixId, tx.getId, currentTime, fullMixBoxId + "," + optFeeEmissionBoxId.get, txBytes) withdrawDAO.updateById(new_withdraw, HopRequested.value) - logger.info(s" [FULL:$mixId ($currentRound) ${str(isAlice)}] Hop txId: ${tx.getId}, is requested: ${(withdrawStatus.equals(WithdrawRequested.value) || withdrawStatus.equals(HopRequested.value))}") + logger.info(s" [FULL:$mixId ($currentRound) ${str(isAlice)}] Hop txId: ${tx.getId}, is requested: ${withdrawStatus.equals(WithdrawRequested.value) || withdrawStatus.equals(HopRequested.value)}") tx } else { @@ -160,7 +163,7 @@ class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils val txBytes = tx.toJson(false).getBytes("utf-16") val new_withdraw = WithdrawTx(mixId, tx.getId, currentTime, fullMixBoxId + "," + optFeeEmissionBoxId.get, txBytes) withdrawDAO.updateById(new_withdraw, WithdrawRequested.value) - logger.info(s" [FULL:$mixId ($currentRound) ${str(isAlice)}] Withdraw txId: ${tx.getId}, is requested: ${(withdrawStatus.equals(WithdrawRequested.value) || withdrawStatus.equals(HopRequested.value))}") + logger.info(s" [FULL:$mixId ($currentRound) ${str(isAlice)}] Withdraw txId: ${tx.getId}, is requested: ${withdrawStatus.equals(WithdrawRequested.value) || withdrawStatus.equals(HopRequested.value)}") tx } @@ -190,10 +193,10 @@ class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils val nextSecret = wallet.getSecret(nextRound) def nextAlice = { - val feeAmount = getFee(mixingTokenId, tokenAmount = fullMixBox.getToken(mixingTokenId), fullMixBox.amount, isFull = false) + val feeAmount = getFee(mixingTokenId, isFull = false) val halfMixTx = aliceOrBob.spendFullMixBox_RemixAsAlice(isAlice, secret, fullMixBoxId, nextSecret, feeEmissionBoxId, feeAmount) halfMixDAO.insertHalfMix(HalfMix(mixId, nextRound, currentTime, halfMixTx.getHalfMixBox.id, isSpent = false)) - mixStateDAO.updateById(MixState(mixId, nextRound, true)) + mixStateDAO.updateById(MixState(mixId, nextRound, isAlice = true)) mixStateHistoryDAO.insertMixHistory(MixHistory(mixId, nextRound, isAlice = true, currentTime)) val new_tx = halfMixTx.tx mixTransactionsDAO.updateById(MixTransaction(halfMixTx.getHalfMixBox.id, new_tx.getId, new_tx.toJson(false).getBytes("utf-16"))) @@ -210,12 +213,12 @@ class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils } def nextBob(halfMixBoxId: String) = { - val feeAmount = getFee(mixingTokenId, tokenAmount = fullMixBox.getToken(mixingTokenId), fullMixBox.amount, isFull = true) + val feeAmount = getFee(mixingTokenId, isFull = true) val (fullMixTx, bit) = aliceOrBob.spendFullMixBox_RemixAsBob(isAlice, secret, fullMixBoxId, nextSecret, halfMixBoxId, feeEmissionBoxId, feeAmount) val (left, right) = fullMixTx.getFullMixBoxes val bobFullMixBox = if (bit) right else left fullMixDAO.insertFullMix(FullMix(mixId, nextRound, currentTime, halfMixBoxId, bobFullMixBox.id)) - mixStateDAO.updateById(MixState(mixId, nextRound, false)) + mixStateDAO.updateById(MixState(mixId, nextRound, isAlice = false)) mixStateHistoryDAO.insertMixHistory(MixHistory(mixId, nextRound, isAlice = false, currentTime)) val new_tx = fullMixTx.tx mixTransactionsDAO.updateById(MixTransaction(bobFullMixBox.id, new_tx.getId, new_tx.toJson(false).getBytes("utf-16"))) @@ -276,7 +279,7 @@ class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils // the halfMixBox used in the fullMix has been spent, while the fullMixBox generated has zero confirmations. try { logger.info(s" [FULL:$mixId ($currentRound) ${str(isAlice)}] <-- Bob (undo). [full: $fullMixBoxId not spent while half: $halfMixBoxId or token: $tokenBoxId spent]") - allMixDAO.undoMixStep(mixId, currentRound, fullMixBoxId, true) + allMixDAO.undoMixStep(mixId, currentRound, fullMixBoxId, isFullMix = true) } catch { case a: Throwable => logger.error(getStackTraceStr(a)) @@ -287,7 +290,7 @@ class FullMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils // the emissionBox used in the fullMix has been spent, while the fullMixBox generated has zero confirmations. try { logger.info(s" [FULL:$mixId ($currentRound) ${str(isAlice)}] <-- Bob (undo). [full:$fullMixBoxId not spent while fee:$emissionBoxId spent]") - allMixDAO.undoMixStep(mixId, currentRound, fullMixBoxId, true) + allMixDAO.undoMixStep(mixId, currentRound, fullMixBoxId, isFullMix = true) } catch { case a: Throwable => logger.error(getStackTraceStr(a)) diff --git a/mixer/app/mixer/GroupMixer.scala b/mixer/app/mixer/GroupMixer.scala index bc0540f..ef046b2 100644 --- a/mixer/app/mixer/GroupMixer.scala +++ b/mixer/app/mixer/GroupMixer.scala @@ -6,13 +6,15 @@ import helpers.ErgoMixerUtils import javax.inject.Inject import scala.collection.JavaConverters._ -import models.Models.GroupMixStatus._ -import models.Models.{DistributeTx, EndBox, MixGroupRequest} +import models.Status.GroupMixStatus._ import network.{BlockExplorer, NetworkUtils} import org.ergoplatform.appkit.{Address, ErgoToken} import play.api.Logger import wallet.{Wallet, WalletHelper} import dao.{DAOUtils, DistributeTransactionsDAO, MixingGroupRequestDAO, MixingRequestsDAO} +import models.Box.EndBox +import models.Request.MixGroupRequest +import models.Transaction.DistributeTx class GroupMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils, explorer: BlockExplorer, @@ -109,7 +111,7 @@ class GroupMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtil requests(0) = (requests(0)._1, requests(0)._2 + excessErg, requests(0)._3 + excessToken) val reqEndBoxes = requests.map(cur => { var token: Seq[ErgoToken] = Seq() - if (!req.tokenId.isEmpty) token = Seq(new ErgoToken(req.tokenId, cur._3)) + if (req.tokenId.nonEmpty) token = Seq(new ErgoToken(req.tokenId, cur._3)) EndBox(Address.create(cur._1).getErgoAddress.script, Seq(), cur._2, token) }) if (excessErg > 0) logger.info(s" excess deposit: $excessErg...") diff --git a/mixer/app/mixer/HalfMixer.scala b/mixer/app/mixer/HalfMixer.scala index 447d1c1..50b1484 100644 --- a/mixer/app/mixer/HalfMixer.scala +++ b/mixer/app/mixer/HalfMixer.scala @@ -1,20 +1,21 @@ package mixer import app.Configs -import mixinterface.AliceOrBob +import dao._ import helpers.ErgoMixerUtils -import wallet.WalletHelper.now - -import javax.inject.Inject -import models.Models.MixStatus.Running -import models.Models.{CreateMixTransaction, FullMix, FullMixBox, HopMix, OutBox, PendingRescan, WithdrawTx} -import models.Models.MixWithdrawStatus.{HopRequested, WithdrawRequested} +import mixinterface.AliceOrBob +import models.Box.OutBox +import models.Models.FullMix +import models.Rescan.PendingRescan +import models.Status.MixWithdrawStatus.{HopRequested, WithdrawRequested} +import models.Transaction.WithdrawTx import network.{BlockExplorer, NetworkUtils} -import org.ergoplatform.appkit.InputBox import play.api.Logger import sigmastate.eval._ +import wallet.WalletHelper.now import wallet.{Wallet, WalletHelper} -import dao.{AllMixDAO, DAOUtils, EmissionDAO, FullMixDAO, HalfMixDAO, HopMixDAO, MixTransactionsDAO, RescanDAO, SpentDepositsDAO, TokenEmissionDAO, WithdrawDAO} + +import javax.inject.Inject class HalfMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils, explorer: BlockExplorer, @@ -95,7 +96,7 @@ class HalfMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils val tx = if (withdrawStatus.equals(HopRequested.value)) { val hopSecret = wallet.getSecret(0, toFirst = true) - val hopAddress = WalletHelper.getProveDlogAddress(hopSecret, ctx) + val hopAddress = WalletHelper.getAddressOfSecret(hopSecret) val tx = aliceOrBob.spendBox(halfMixBoxId, optFeeEmissionBoxId, hopAddress, Array(secret), Configs.defaultHalfFee, broadCast = false) val txBytes = tx.toJson(false).getBytes("utf-16") @@ -176,7 +177,7 @@ class HalfMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils // logger.info(s" [HalfMix $mixId] Zero conf. halfMixBoxId: $halfMixBoxId, currentRound: $currentRound while emissionBoxId $emissionBoxId spent") // the emissionBox used in the fullMix has been spent, while the fullMixBox generated has zero confirmations. try { - allMixDAO.undoMixStep(mixId, currentRound, halfMixBoxId, false) + allMixDAO.undoMixStep(mixId, currentRound, halfMixBoxId, isFullMix = false) logger.info(s" [HALF:$mixId ($currentRound)] <-- (undo) [half:$halfMixBoxId not spent while fee:$emissionBoxId spent]") } catch { case a: Throwable => @@ -197,12 +198,12 @@ class HalfMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils } } } - case _ => { + case _ => // no emission box, so this is an entry. // token emission box double spent check if (networkUtils.isDoubleSpent(tokenBoxId, halfMixBoxId)) { try { - allMixDAO.undoMixStep(mixId, currentRound, halfMixBoxId, false) + allMixDAO.undoMixStep(mixId, currentRound, halfMixBoxId, isFullMix = false) logger.info(s" [HALF:$mixId ($currentRound)] <-- (undo) [half:$halfMixBoxId not spent while token:$tokenBoxId spent]") } catch { case a: Throwable => @@ -220,7 +221,6 @@ class HalfMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils rescanDAO.updateById(new_scan) } } - } } } } diff --git a/mixer/app/mixer/HopMixer.scala b/mixer/app/mixer/HopMixer.scala index b2c8495..cc2460e 100644 --- a/mixer/app/mixer/HopMixer.scala +++ b/mixer/app/mixer/HopMixer.scala @@ -4,12 +4,12 @@ import app.Configs import dao._ import helpers.ErgoMixerUtils import mixinterface.AliceOrBob -import models.Models.MixWithdrawStatus.{UnderHop, WithdrawRequested} -import models.Models.{FullMix, FullMixBox, HopMix, PendingRescan, WithdrawTx} +import models.Models.HopMix +import models.Rescan.PendingRescan +import models.Transaction.WithdrawTx import network.{BlockExplorer, NetworkUtils} -import org.ergoplatform.appkit.{BlockchainContext, InputBox} +import org.ergoplatform.appkit.BlockchainContext import play.api.Logger -import sigmastate.eval._ import wallet.WalletHelper.now import wallet.{Wallet, WalletHelper} @@ -83,7 +83,7 @@ class HopMixer @Inject()(ergoMixerUtils: ErgoMixerUtils, } else { val nextHopSecret = wallet.getSecret(hopBox.round + 1, toFirst = true) - val nextHopAddress = WalletHelper.getProveDlogAddress(nextHopSecret, ctx) + val nextHopAddress = WalletHelper.getAddressOfSecret(nextHopSecret) try { val tx = aliceOrBob.spendHopBox(secret, hopBox.boxId, nextHopAddress) @@ -114,12 +114,23 @@ class HopMixer @Inject()(ergoMixerUtils: ErgoMixerUtils, * @param hopBox HopMix */ private def investigateHopBoxStatus(hopBox: HopMix): Unit = { - explorer.getSpendingTxId(hopBox.boxId) match { - case Some(txId) => - logger.error(s" [HOP:${hopBox.mixId} (${hopBox.round})] [ERROR] Rescanning because hop:$hopBox is spent in txId: $txId") - Thread.currentThread().getStackTrace foreach println - val new_scan = PendingRescan(hopBox.mixId, now, hopBox.round, goBackward = false, "hop", hopBox.boxId) + explorer.doesBoxExist(hopBox.boxId) match { + case Some(false) => + // hopBox is no longer confirmed. This indicates a fork. We need to rescan + logger.error(s" [HOP:${hopBox.mixId} (${hopBox.round})] [ERROR] Rescanning [hop:$hopBox disappeared]") + val new_scan = PendingRescan(hopBox.mixId, now, hopBox.round, goBackward = true, "hop", hopBox.boxId) rescanDAO.updateById(new_scan) + case Some(true) => + explorer.getSpendingTxId(hopBox.boxId) match { + case Some(txId) => + logger.error(s" [HOP:${hopBox.mixId} (${hopBox.round})] [ERROR] Rescanning because hop:$hopBox is spent in txId: $txId") + val new_scan = PendingRescan(hopBox.mixId, now, hopBox.round, goBackward = false, "hop", hopBox.boxId) + rescanDAO.updateById(new_scan) + case None => + throw new Exception("this case should never happen") + } + case None => + logger.error(s" An error occurred while checking the hopBox ${hopBox.boxId} with explorer") } } diff --git a/mixer/app/mixer/MixScanner.scala b/mixer/app/mixer/MixScanner.scala index f037d39..70f4a6b 100644 --- a/mixer/app/mixer/MixScanner.scala +++ b/mixer/app/mixer/MixScanner.scala @@ -1,7 +1,9 @@ package mixer +import models.Box.{FBox, HBox} +import models.Rescan.{FollowedHop, FollowedMix, FollowedWithdraw} + import javax.inject.{Inject, Singleton} -import models.Models.{FBox, FollowedHop, FollowedMix, FollowedWithdraw, HBox} import network.{BlockExplorer, NetworkUtils} import play.api.Logger import sigmastate.eval._ @@ -56,15 +58,6 @@ class MixScanner @Inject()(networkUtils: NetworkUtils, explorer: BlockExplorer) }.getOrElse(Nil) } - @deprecated("This takes a lot of time. Use followFullMix and followHalfMix", "1.0") - def followDeposit(boxId: String, masterSecret: BigInt, poolAmount: Long = 1000000000): Seq[FollowedMix] = { - getSpendingTx(boxId).map { tx => - tx.outboxes.filter(_.amount == poolAmount).flatMap { outBox => - followHalfMix(outBox.id, 0, masterSecret) ++ followFullMix(outBox.id, 0, masterSecret) - } - }.getOrElse(Nil) - } - /** * returns withdraw information if the box is withdrawn * diff --git a/mixer/app/mixer/NewMixer.scala b/mixer/app/mixer/NewMixer.scala index 948f7dd..c344b93 100644 --- a/mixer/app/mixer/NewMixer.scala +++ b/mixer/app/mixer/NewMixer.scala @@ -6,10 +6,12 @@ import helpers.ErgoMixerUtils import wallet.WalletHelper.now import javax.inject.Inject -import models.Models -import models.Models.MixStatus.Running -import models.Models.MixWithdrawStatus.WithdrawRequested -import models.Models.{Deposit, FullMix, HalfMix, MixHistory, MixRequest, MixState, MixTransaction, OutBox, SpentDeposit, WithdrawTx} +import models.Status.MixStatus.Running +import models.Status.MixWithdrawStatus.WithdrawRequested +import models.Models.{Deposit, FullMix, HalfMix, MixHistory, MixState, SpentDeposit} +import models.Box.OutBox +import models.Request.MixRequest +import models.Transaction.{MixTransaction, WithdrawTx} import network.NetworkUtils import org.ergoplatform.appkit.SignedTransaction import play.api.Logger @@ -59,23 +61,22 @@ class NewMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils, if (reqs.nonEmpty) logger.info(s"[NEW] Processing following ids") reqs.foreach { - case req => logger.info(s" > ${req.id} depositAddress: ${req.depositAddress}") + req => logger.info(s" > ${req.id} depositAddress: ${req.depositAddress}") } halfs = null - reqs.foreach { - case req => - try { - initiateMix(req.toMixRequest, req.masterKey) - } catch { - case a: Throwable => - logger.error(s" [NEW: ${req.id}] An error occurred. Stacktrace below") - logger.error(getStackTraceStr(a)) - } + reqs.foreach {req => + try { + initiateMix(req.toMixRequest, req.masterKey) + } catch { + case a: Throwable => + logger.error(s" [NEW: ${req.id}] An error occurred. Stacktrace below") + logger.error(getStackTraceStr(a)) + } } } - private implicit val insertReason = "NewMixer.initiateMix" + private implicit val insertReason: String = "NewMixer.initiateMix" /** * starts mixing (as bob if possible or as alice) @@ -110,7 +111,7 @@ class NewMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils, return } - def updateTablesMixes(isAlice: Boolean, mixRequestId: String, time: Long, tx: SignedTransaction, depositsToUse: Seq[Models.Deposit], optTokenBoxId: Option[String]): Unit = { + def updateTablesMixes(isAlice: Boolean, mixRequestId: String, time: Long, tx: SignedTransaction, depositsToUse: Seq[Deposit], optTokenBoxId: Option[String]): Unit = { depositsToUse.map { d => val spent_deposit = SpentDeposit(d.address, d.boxId, d.amount, d.createdTime, d.tokenAmount, tx.getId, time, mixRequestId) spentDepositsDAO.insertDeposit(spent_deposit) @@ -133,7 +134,7 @@ class NewMixer @Inject()(aliceOrBob: AliceOrBob, ergoMixerUtils: ErgoMixerUtils, val neededToken = mixRequest.neededTokenAmount // get a random token emission box val optTokenBoxId = getRandomValidBoxId(networkUtils.getTokenEmissionBoxes(numFeeToken, considerPool = true) - .filterNot(box => daoUtils.awaitResult(tokenEmissionDAO.existsByBoxId(box.id))).map(_.id.toString)) + .filterNot(box => daoUtils.awaitResult(tokenEmissionDAO.existsByBoxId(box.id))).map(_.id)) if (avbl < neededErg || avblToken < neededToken) { // should not happen because we are only considering completed deposits. throw new Exception(s"Insufficient funds. Needed $neededErg. Available $avbl") diff --git a/mixer/app/mixer/Rescan.scala b/mixer/app/mixer/Rescan.scala index f1d2276..f3b0995 100644 --- a/mixer/app/mixer/Rescan.scala +++ b/mixer/app/mixer/Rescan.scala @@ -1,17 +1,17 @@ package mixer +import dao._ +import models.Models.{FullMix, HalfMix, HopMix, MixHistory, MixState} +import models.Rescan.{FollowedHop, FollowedMix, FollowedWithdraw, PendingRescan} +import models.Status.MixStatus.Complete +import models.Status.MixWithdrawStatus.UnderHop +import models.Transaction.WithdrawTx +import play.api.Logger import wallet.WalletHelper.now import javax.inject.Inject -import models.Models.{FollowedHop, FollowedMix, FollowedWithdraw, FullMix, HalfMix, HopMix, MixHistory, MixState, PendingRescan, WithdrawTx} -import dao.{DAOUtils, FullMixDAO, HalfMixDAO, HopMixDAO, MixStateDAO, MixStateHistoryDAO, MixingRequestsDAO, RescanDAO, WithdrawDAO} -import models.Models.MixStatus.Complete -import models.Models.MixWithdrawStatus.{UnderHop, WithdrawRequested} -import network.NetworkUtils -import org.ergoplatform.appkit.BlockchainContext - -class Rescan @Inject()(networkUtils: NetworkUtils, - mixScanner: MixScanner, + +class Rescan @Inject()(mixScanner: MixScanner, daoUtils: DAOUtils, mixingRequestsDAO: MixingRequestsDAO, rescanDAO: RescanDAO, @@ -22,21 +22,28 @@ class Rescan @Inject()(networkUtils: NetworkUtils, hopMixDAO: HopMixDAO, withdrawDAO: WithdrawDAO) { - private implicit val insertReason = "rescan" + private val logger: Logger = Logger(this.getClass) + + private implicit val insertReason: String = "rescan" def processRescanQueue(): Unit = { daoUtils.awaitResult(rescanDAO.all).foreach { case PendingRescan(mixId, _, round, goBackward, boxType, mixBoxId) => - processRescan(mixId, round, goBackward, boxType, mixBoxId) - rescanDAO.delete(mixId) + try { + processRescan(mixId, round, goBackward, boxType, mixBoxId) + rescanDAO.delete(mixId) + } + catch { + case e: Throwable => + logger.error(e.getMessage) + } } } def processRescan(mixId: String, round: Int, goBackward: Boolean, boxType: String, mixBoxId: String): Unit = { - val masterSecretFuture = mixingRequestsDAO.selectMasterKey(mixId) - val poolAmountFuture = mixingRequestsDAO.selectByMixId(mixId) - val masterSecret = daoUtils.awaitResult(masterSecretFuture).getOrElse(throw new Exception("Unable to read master secret")) - val poolAmount = daoUtils.awaitResult(poolAmountFuture).getOrElse(throw new Exception("Unable to read pool amount")).amount + logger.info(s" [RESCAN: $mixId] boxId: $mixBoxId processing. boxType: $boxType, goBackward: $goBackward") + val masterSecret = daoUtils.awaitResult(mixingRequestsDAO.selectMasterKey(mixId)) + .getOrElse(throw new Exception("Unable to read master secret")) if (!goBackward) { // go forward boxType match { @@ -61,9 +68,46 @@ class Rescan @Inject()(networkUtils: NetworkUtils, } } else { // go backward - // TODO: Rescan from last good box instead of beginning. For now doing from beginning - val followedMixes: Seq[FollowedMix] = mixScanner.followDeposit(mixBoxId, masterSecret, poolAmount) - applyMixes(mixId, followedMixes) + if (round == 0 && boxType != "hop") { + // TODO: in this case, should get deposit box and follow forward from it. + logger.warn(s" backward rescan for $boxType in round 0 not implemented...") + return + } + boxType match { + case "half" => + clearFutureRounds(mixId, round - 1) + val previousFullBox = daoUtils.awaitResult(fullMixDAO.getMixBoxIdByRound(mixId, round - 1)) + .getOrElse(throw new Exception("Unable to retrieve previous full box")) + + val followedMixes: Seq[FollowedMix] = mixScanner.followFullMix(previousFullBox, round - 1, masterSecret) + applyMixes(mixId, followedMixes) + + case "full" => + val previousHalfBox = daoUtils.awaitResult(halfMixDAO.getMixBoxIdByRound(mixId, round)) + if (previousHalfBox.isDefined) { + daoUtils.awaitResult(mixStateHistoryDAO.deleteFutureRounds(mixId, round - 1)) + daoUtils.awaitResult(fullMixDAO.deleteFutureRounds(mixId, round - 1)) + + val followedMixes: Seq[FollowedMix] = mixScanner.followHalfMix(previousHalfBox.get, round, masterSecret) + applyMixes(mixId, followedMixes) + } + else { + clearFutureRounds(mixId, round - 1) + val previousFullBox = daoUtils.awaitResult(fullMixDAO.getMixBoxIdByRound(mixId, round - 1)) + .getOrElse(throw new Exception("Unable to retrieve previous full box")) + + val followedMixes: Seq[FollowedMix] = mixScanner.followHalfMix(previousFullBox, round - 1, masterSecret) + applyMixes(mixId, followedMixes) + } + + case "hop" => + hopMixDAO.deleteFutureRounds(mixId, round - 1) + val previousHopBox = daoUtils.awaitResult(hopMixDAO.getMixBoxIdByRound(mixId, round - 1)) + .getOrElse(throw new Exception("Unable to retrieve previous hop box")) + val (followedHop, followedWithdraw) = mixScanner.followHopMix(previousHopBox, round - 1, masterSecret) + applyHopMixes(mixId, followedHop) + if (followedWithdraw.isDefined) applyWithdrawTx(mixId, followedWithdraw.get, UnderHop.value) + } } } @@ -85,32 +129,32 @@ class Rescan @Inject()(networkUtils: NetworkUtils, if (followedWithdraw.isDefined) applyWithdrawTx(mixId, followedWithdraw.get, UnderHop.value) } - private def updateMixHistory(mixId: String, round: Int, isAlice: Boolean) = { + private def updateMixHistory(mixId: String, round: Int, isAlice: Boolean): Unit = { val mixHistory = MixHistory(mixId, round, isAlice, now) daoUtils.awaitResult(mixStateHistoryDAO.updateById(mixHistory)) } - private def updateFullMix(mixId: String, round: Int, halfMixBoxId: String, fullMixBoxId: String) = { + private def updateFullMix(mixId: String, round: Int, halfMixBoxId: String, fullMixBoxId: String): Unit = { val fullMix = FullMix(mixId, round, now, halfMixBoxId, fullMixBoxId) daoUtils.awaitResult(fullMixDAO.updateById(fullMix)) } - private def updateHalfMix(mixId: String, round: Int, halfMixBoxId: String, isSpent: Boolean) = { + private def updateHalfMix(mixId: String, round: Int, halfMixBoxId: String, isSpent: Boolean): Unit = { val halfMix = HalfMix(mixId, round, now, halfMixBoxId, isSpent) daoUtils.awaitResult(halfMixDAO.updateById(halfMix)) } - private def updateMixState(mixId: String, round: Int, isAlice: Boolean) = { + private def updateMixState(mixId: String, round: Int, isAlice: Boolean): Unit = { val mixState = MixState(mixId, round, isAlice) daoUtils.awaitResult(mixStateDAO.updateInRescan(mixState)) } - private def updateHopMix(mixId: String, round: Int, hopBoxId: String) = { + private def updateHopMix(mixId: String, round: Int, hopBoxId: String): Unit = { val hopMix = HopMix(mixId, round, now, hopBoxId) daoUtils.awaitResult(hopMixDAO.updateById(hopMix)) } - private def applyMix(mixId: String, followedMix: FollowedMix) = { + private def applyMix(mixId: String, followedMix: FollowedMix): Unit = { followedMix match { case FollowedMix(round, true, halfMixBoxId, Some(fullMixBoxId)) => @@ -131,7 +175,7 @@ class Rescan @Inject()(networkUtils: NetworkUtils, updateMixHistory(mixId, round, isAlice = false) updateMixState(mixId, round, isAlice = false) - case _ => ??? // should never happen + case _ => throw new Exception("this case should never happen") } } @@ -151,7 +195,7 @@ class Rescan @Inject()(networkUtils: NetworkUtils, followedHopMixes.lastOption.map(lastHop => hopMixDAO.deleteFutureRounds(mixId, lastHop.round)) } - private def applyWithdrawTx(mixId: String, tx: FollowedWithdraw, withdrawStatus: String) = { + private def applyWithdrawTx(mixId: String, tx: FollowedWithdraw, withdrawStatus: String): Unit = { val withdrawTx = WithdrawTx(mixId, tx.txId, now, tx.boxId, Array.empty[Byte], "generated by rescan") daoUtils.awaitResult(withdrawDAO.updateById(withdrawTx, withdrawStatus)) } diff --git a/mixer/app/mixer/WithdrawMixer.scala b/mixer/app/mixer/WithdrawMixer.scala index 845e4f2..1d871d3 100644 --- a/mixer/app/mixer/WithdrawMixer.scala +++ b/mixer/app/mixer/WithdrawMixer.scala @@ -1,18 +1,21 @@ package mixer import app.Configs +import dao._ import helpers.ErgoMixerUtils - -import javax.inject.Inject -import models.Models.MixStatus.Complete -import models.Models.MixWithdrawStatus.{AgeUSDRequested, NoWithdrawYet, UnderHop, WithdrawRequested, Withdrawn} -import models.Models.{CreateMixRequest, CreateWithdrawTx, GroupMixStatus, HopMix, MixRequest, WithdrawTx} +import models.Models.HopMix +import models.Request.MixRequest +import models.Status.GroupMixStatus +import models.Status.MixStatus.Complete +import models.Status.MixWithdrawStatus.{NoWithdrawYet, UnderHop} +import models.Transaction.WithdrawTx import network.{BlockExplorer, NetworkUtils} -import play.api.Logger -import dao.{DAOUtils, HopMixDAO, MixingGroupRequestDAO, MixingRequestsDAO, WithdrawDAO} import org.ergoplatform.appkit.BlockchainContext +import play.api.Logger import wallet.WalletHelper +import javax.inject.Inject + class WithdrawMixer @Inject()(ergoMixerUtils: ErgoMixerUtils, networkUtils: NetworkUtils, explorer: BlockExplorer, daoUtils: DAOUtils, @@ -34,26 +37,24 @@ class WithdrawMixer @Inject()(ergoMixerUtils: ErgoMixerUtils, val minting = withdrawals._2 val hopping = withdrawals._3 - withdraws.foreach { - case tx => - try { - processWithdraw(tx) - } catch { - case a: Throwable => - logger.info(s" [WITHDRAW: ${tx.mixId}] txId: ${tx.txId} An error occurred. Stacktrace below") - logger.error(getStackTraceStr(a)) - } + withdraws.foreach {tx => + try { + processWithdraw(tx) + } catch { + case a: Throwable => + logger.info(s" [WITHDRAW: ${tx.mixId}] txId: ${tx.txId} An error occurred. Stacktrace below") + logger.error(getStackTraceStr(a)) + } } - minting.foreach { - case tx => - try { - processWithdraw(tx, isMinting = true) - } catch { - case a: Throwable => - logger.info(s" [WITHDRAW (minting): ${tx.mixId}] txId: ${tx.txId} An error occurred. Stacktrace below") - logger.error(getStackTraceStr(a)) - } + minting.foreach {tx => + try { + processWithdraw(tx, isMinting = true) + } catch { + case a: Throwable => + logger.info(s" [WITHDRAW (minting): ${tx.mixId}] txId: ${tx.txId} An error occurred. Stacktrace below") + logger.error(getStackTraceStr(a)) + } } hopping.foreach(tx => { diff --git a/mixer/app/mixinterface/AliceImpl.scala b/mixer/app/mixinterface/AliceImpl.scala index 6879aab..9a07d19 100644 --- a/mixer/app/mixinterface/AliceImpl.scala +++ b/mixer/app/mixinterface/AliceImpl.scala @@ -3,7 +3,8 @@ package mixinterface import java.math.BigInteger import mixinterface.ErgoMixBase._ -import models.Models.{EndBox, FullMixBox, HalfMixTx} +import models.Box.{EndBox, FullMixBox} +import models.Transaction.HalfMixTx import org.ergoplatform.appkit._ import org.ergoplatform.appkit.impl.ErgoTreeContract import sigmastate.eval._ diff --git a/mixer/app/mixinterface/AliceOrBob.scala b/mixer/app/mixinterface/AliceOrBob.scala index a41cf8f..a5c9147 100644 --- a/mixer/app/mixinterface/AliceOrBob.scala +++ b/mixer/app/mixinterface/AliceOrBob.scala @@ -9,9 +9,9 @@ import mixinterface.ErgoMixBase._ import javax.inject.{Inject, Singleton} import network.{BlockExplorer, NetworkUtils} import org.ergoplatform.appkit._ -import models.Models.{EndBox, FullMixBox, FullMixTx, HalfMixBox, HalfMixTx, TokenMap} -import org.ergoplatform.ErgoBox -import org.ergoplatform.ErgoBox.NonMandatoryRegisterId +import models.Models.TokenMap +import models.Box.{EndBox, FullMixBox, HalfMixBox} +import models.Transaction.{FullMixTx, HalfMixTx} import org.ergoplatform.appkit.impl.ErgoTreeContract import wallet.WalletHelper diff --git a/mixer/app/mixinterface/BobImpl.scala b/mixer/app/mixinterface/BobImpl.scala index 2807298..36e54d7 100644 --- a/mixer/app/mixinterface/BobImpl.scala +++ b/mixer/app/mixinterface/BobImpl.scala @@ -3,7 +3,8 @@ package mixinterface import java.math.BigInteger import mixinterface.ErgoMixBase._ -import models.Models.{EndBox, FullMixBox, FullMixTx, HalfMixBox} +import models.Box.{EndBox, FullMixBox, HalfMixBox} +import models.Transaction.FullMixTx import org.ergoplatform.appkit._ import org.ergoplatform.appkit.impl.ErgoTreeContract import sigmastate.eval._ diff --git a/mixer/app/mixinterface/ErgoMixBase.scala b/mixer/app/mixinterface/ErgoMixBase.scala index 862de7a..33e500f 100644 --- a/mixer/app/mixinterface/ErgoMixBase.scala +++ b/mixer/app/mixinterface/ErgoMixBase.scala @@ -1,9 +1,9 @@ package mixinterface import java.math.BigInteger - import app.Configs -import models.Models._ +import models.Box.{EndBox, FullMixBox, HalfMixBox} +import models.Transaction.{FullMixTx, HalfMixTx} import network.NetworkUtils import org.ergoplatform.ErgoLikeTransaction import org.ergoplatform.appkit._ diff --git a/mixer/app/models/Box.scala b/mixer/app/models/Box.scala new file mode 100644 index 0000000..61a8d3c --- /dev/null +++ b/mixer/app/models/Box.scala @@ -0,0 +1,152 @@ +package models + +import app.Configs +import io.circe.generic.semiauto.deriveDecoder +import io.circe.{Decoder, parser} +import mixinterface.TokenErgoMix +import org.ergoplatform.appkit.{Address, ErgoToken, ErgoValue, InputBox} +import play.api.libs.json.{JsArray, JsResult, JsSuccess, JsValue, Reads} +import sigmastate.Values.ErgoTree +import special.sigma.GroupElement +import wallet.WalletHelper + +import scala.collection.JavaConverters._ +import scala.collection.mutable + +object Box { + + case class InBox(id: String, address: String, createdTxId: String, value: Long) + + case class OutBox(id: String, amount: Long, registers: Map[String, String], ergoTree: String, tokens: Seq[ErgoToken], creationHeight: Int, address: String, spendingTxId: Option[String]) { + def ge(regId: String): GroupElement = WalletHelper.hexToGroupElement(registers(regId).drop(2)) + + def getToken(tokenId: String): Long = { + tokens.filter(_.getId.toString.equals(tokenId)).map(_.getValue.longValue()).sum + } + + def mixBox(tokenErgoMix: TokenErgoMix): Option[Either[HBox, FBox]] = { + try { + val fullMixBoxErgoTree = tokenErgoMix.fullMixScriptErgoTree.bytesHex + val halfMixBoxErgoTree = tokenErgoMix.halfMixContract.getErgoTree.bytesHex + ergoTree match { + case `fullMixBoxErgoTree` => + Some(Right(FBox(id, ge("R4"), ge("R5"), ge("R6")))) + case `halfMixBoxErgoTree` => + Some(Left(HBox(id, ge("R4")))) + case _ => + None + } + } catch { + case _: Throwable => + None + } + } + + def getFBox(tokenErgoMix: TokenErgoMix): Option[FBox] = mixBox(tokenErgoMix).flatMap { + case Right(fBox) => Some(fBox) + case _ => None + } + + def isAddressEqualTo(address: String): Boolean = { + val addressErgoTree = Address.create(address).getErgoAddress.script.bytesHex + addressErgoTree == ergoTree + } + } + + // for scanning blockchain + case class FBox(id: String, r4: GroupElement, r5: GroupElement, r6: GroupElement) + + case class HBox(id: String, r4: GroupElement) + + case class MixingBox(withdraw: String, amount: Long, token: Int, mixingTokenAmount: Long, mixingTokenId: String) { + def price: (Long, Long) = { + MixingBox.getPrice(amount, mixingTokenAmount, token) + } + } + + object MixingBox { + + /** + * calculates needed token for a given ring + * + * @return token needed to enter mixing, i.e. ring + tokenFee + */ + def getTokenPrice(ring: Long): Long = { + val rate: Int = Configs.entranceFee.getOrElse(1000000) + ring + (if (rate > 0 && rate < 1000000) ring / rate else 0) + } + + /** + * calculates needed amount with current fees for a specific mix box + * + * @param ergRing erg ring of mix + * @param tokenRing token ring of mix + * @param mixRounds number of mixing rounds i.e. token num + * @return (erg needed, token needed) + */ + def getPrice(ergRing: Long, tokenRing: Long, mixRounds: Int): (Long, Long) = { + val rate: Int = Configs.entranceFee.getOrElse(1000000) + val tokenPrice: Long = Configs.tokenPrices.get.getOrElse(mixRounds, -1) + assert(tokenPrice != -1) + val ergVal = if (rate > 0 && rate < 1000000) ergRing / rate else 0 + (ergRing + Configs.startFee + tokenPrice + ergVal, getTokenPrice(tokenRing)) + } + + implicit val mixingBoxDecoder: Decoder[MixingBox] = deriveDecoder[MixingBox] + + def apply(jsonString: String): MixingBox = { + parser.decode[MixingBox](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing MixingBox from Json: $e") + case Right(asset) => asset + } + } + } + + case class MixBoxList(items: Iterable[MixingBox]) + + object MixBoxList { + implicit val ReadsMixBoxList: Reads[MixBoxList] = new Reads[MixBoxList] { + override def reads(json: JsValue): JsResult[MixBoxList] = { + JsSuccess(MixBoxList(json.as[JsArray].value.map(item => { + val withdraw = (item \ "withdraw").as[String] + val amount = (item \ "amount").as[Long] + val token = (item \ "token").as[Int] + val mixingTokenId = (item \ "mixingTokenId").as[String] + val mixingTokenAmount = (item \ "mixingTokenAmount").as[Long] + MixingBox(withdraw, amount, token, mixingTokenAmount, mixingTokenId) + }))) + } + } + } + + case class EndBox(receiverBoxScript: ErgoTree, receiverBoxRegs: Seq[ErgoValue[_]] = Nil, value: Long, tokens: Seq[ErgoToken] = Nil) // box spending full mix box + + abstract class MixBox(inputBox: InputBox) { + def getRegs: mutable.Seq[ErgoValue[_]] = inputBox.getRegisters.asScala + + def getR4: ErgoValue[_] = getRegs.head + + def getR5: ErgoValue[_] = getRegs(1) + + def getR6: ErgoValue[_] = getRegs(2) + } + + case class HalfMixBox(inputBox: InputBox) extends MixBox(inputBox) { + def id: String = inputBox.getId.toString + + val gX: GroupElement = getR4.getValue match { + case g: GroupElement => g + case any => throw new Exception(s"Invalid value in R4: $any of type ${any.getClass}") + } + } + + case class FullMixBox(inputBox: InputBox) extends MixBox(inputBox) { + def id: String = inputBox.getId.toString + + val (r4, r5, r6) = (getR4.getValue, getR5.getValue, getR6.getValue) match { + case (c1: GroupElement, c2: GroupElement, gX: GroupElement) => (c1, c2, gX) //Values.GroupElementConstant(c1), Values.GroupElementConstant(c2)) => (c1, c2) + case (r4, r5, r6) => throw new Exception(s"Invalid registers R4:$r4, R5:$r5, R6:$r6") + } + } + +} diff --git a/mixer/app/models/Models.scala b/mixer/app/models/Models.scala index 30c0855..1dbd35b 100644 --- a/mixer/app/models/Models.scala +++ b/mixer/app/models/Models.scala @@ -1,295 +1,25 @@ package models -import java.nio.charset.StandardCharsets -import app.Configs import io.circe.generic.auto._ import io.circe.generic.semiauto.deriveDecoder import io.circe.syntax._ -import io.circe.{Decoder, Encoder, HCursor, Json, parser} -import mixinterface.TokenErgoMix -import org.ergoplatform.appkit.{Address, ErgoToken, ErgoValue, InputBox, SignedTransaction} -import play.api.libs.json._ -import sigmastate.Values.ErgoTree +import io.circe._ +import models.Request.MixRequest +import models.Transaction.Withdraw +import org.ergoplatform.appkit.{ErgoToken, InputBox} import special.collection.Coll -import special.sigma.GroupElement -import wallet.WalletHelper -import scala.collection.JavaConverters._ +import java.nio.charset.StandardCharsets import scala.collection.mutable object Models { - case class SpendTx(inboxes: Seq[InBox], outboxes: Seq[OutBox], id: String, address: String, timestamp: Long) - - case class InBox(id: String, address: String, createdTxId: String, value: Long) - - case class OutBox(id: String, amount: Long, registers: Map[String, String], ergoTree: String, tokens: Seq[ErgoToken], creationHeight: Int, address: String, spendingTxId: Option[String]) { - def ge(regId: String): GroupElement = WalletHelper.hexToGroupElement(registers(regId).drop(2)) - - def getToken(tokenId: String): Long = { - tokens.filter(_.getId.toString.equals(tokenId)).map(_.getValue.longValue()).sum - } - - def mixBox(tokenErgoMix: TokenErgoMix): Option[Either[HBox, FBox]] = { - try { - val fullMixBoxErgoTree = tokenErgoMix.fullMixScriptErgoTree.bytesHex - val halfMixBoxErgoTree = tokenErgoMix.halfMixContract.getErgoTree.bytesHex - ergoTree match { - case `fullMixBoxErgoTree` => - Some(Right(FBox(id, ge("R4"), ge("R5"), ge("R6")))) - case `halfMixBoxErgoTree` => - Some(Left(HBox(id, ge("R4")))) - case any => - None - } - } catch { - case _: Throwable => - None - } - } - - def getFBox(tokenErgoMix: TokenErgoMix): Option[FBox] = mixBox(tokenErgoMix).flatMap { - case Right(fBox) => Some(fBox) - case _ => None - } - - def isAddressEqualTo(address: String): Boolean = { - val addressErgoTree = Address.create(address).getErgoAddress.script.bytesHex - addressErgoTree == ergoTree - } - } - - sealed abstract class MixStatus(val value: String) - - implicit val encodeMixStatus: Encoder[MixStatus] = (a: MixStatus) => Json.fromString(a.value) - - object MixStatus { - - object Queued extends MixStatus("queued") - - object Running extends MixStatus("running") - - object Complete extends MixStatus("complete") - - private def all = Seq(Queued, Running, Complete) - - def fromString(s: String): MixStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) - - implicit val decodeMixStatus: Decoder[MixStatus] = (c: HCursor) => for { - status <- c.as[String] - } yield { - MixStatus.fromString(status) - } - } - implicit val decodeBigInt: Decoder[BigInt] = (c: HCursor) => for { bi <- c.as[String] } yield { BigInt(bi) } - sealed abstract class GroupMixStatus(val value: String) - - object GroupMixStatus { - - object Queued extends GroupMixStatus("queued") - - object Starting extends GroupMixStatus("starting") - - object Running extends GroupMixStatus("running") - - object Complete extends GroupMixStatus("complete") - - private def all = Seq(Queued, Running, Complete) - - def fromString(s: String): GroupMixStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) - } - - sealed abstract class MixWithdrawStatus(val value: String) - - object MixWithdrawStatus { - - object NoWithdrawYet extends MixWithdrawStatus("nothing") - - object WithdrawRequested extends MixWithdrawStatus("withdrawing") - - object AgeUSDRequested extends MixWithdrawStatus("minting") - - object HopRequested extends MixWithdrawStatus("hopping") - - object UnderHop extends MixWithdrawStatus("under hop") - - object Withdrawn extends MixWithdrawStatus("withdrawn") - - private def all = Seq(NoWithdrawYet, WithdrawRequested, Withdrawn) - - def fromString(s: String): MixWithdrawStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) - } - - case class MixRequest(id: String, groupId: String, amount: Long, numRounds: Int, mixStatus: MixStatus, createdTime: Long, withdrawAddress: String, depositAddress: String, depositCompleted: Boolean, neededAmount: Long, numToken: Int, withdrawStatus: String, mixingTokenAmount: Long, neededTokenAmount: Long, tokenId: String) { - - - def isErg: Boolean = tokenId.isEmpty - - def getAmount: Long = { - if (isErg) amount - else mixingTokenAmount - } - - override def toString: String = this.asJson.toString - } - - object CreateMixRequest { - def apply(a: Array[Any]): MixRequest = { - val i = a.toIterator - new MixRequest( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Long], - i.next().asInstanceOf[Int], - MixStatus.fromString(i.next().asInstanceOf[String]), - i.next().asInstanceOf[Long], - i.next().asInstanceOf[String], // withdraw address - i.next().asInstanceOf[String], // deposit address - i.next().asInstanceOf[Boolean], - i.next().asInstanceOf[Long], // needed - i.next().asInstanceOf[Int], // token - i.next().asInstanceOf[String], // withdraw status - i.next().asInstanceOf[Long], // mixing token amount - i.next().asInstanceOf[Long], // needed tokens - i.next().asInstanceOf[String] // token id - ) - } - } - - // The only difference between this class and MixRequest is masterKey - case class MixingRequest( - id: String, - groupId: String, - amount: Long, - numRounds: Int, - mixStatus: MixStatus, - createdTime: Long, - withdrawAddress: String, - depositAddress: String, - depositCompleted: Boolean, - neededAmount: Long, - numToken: Int, - withdrawStatus: String, - mixingTokenAmount: Long, - neededTokenAmount: Long, - tokenId: String, - masterKey: BigInt, - ) { - - def toMixRequest: MixRequest = MixRequest( - id, - groupId, - amount, - numRounds, - mixStatus, - createdTime, - withdrawAddress, - depositAddress, - depositCompleted, - neededAmount, - numToken, - withdrawStatus, - mixingTokenAmount, - neededTokenAmount, - tokenId - ) - } - - object CreateMixingRequest { - def apply(a: Array[Any]): MixingRequest = { - val i = a.toIterator - new MixingRequest( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Long], - i.next().asInstanceOf[Int], - MixStatus.fromString(i.next().asInstanceOf[String]), - i.next().asInstanceOf[Long], - i.next().asInstanceOf[String], // withdraw address - i.next().asInstanceOf[String], // deposit address - i.next().asInstanceOf[Boolean], - i.next().asInstanceOf[Long], // needed - i.next().asInstanceOf[Int], // token - i.next().asInstanceOf[String], // withdraw status - i.next().asInstanceOf[Long], // mixing token amount - i.next().asInstanceOf[Long], // needed tokens - i.next().asInstanceOf[String], // token id - i.next().asInstanceOf[BigInt] // master secret key - ) - } - - implicit val mixingRequestDecoder: Decoder[MixingRequest] = deriveDecoder[MixingRequest] - - def apply(jsonString: String): MixingRequest = { - parser.decode[MixingRequest](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing MixingRequest from Json: $e") - case Right(req) => req - } - } - } - - case class MixCovertRequest(nameCovert: String = "", id: String, createdTime: Long, depositAddress: String, numRounds: Int, isManualCovert: Boolean, masterKey: BigInt) { - - - def getMinNeeded(ergRing: Long, tokenRing: Long): (Long, Long) = { // returns what is needed to distribute - val needed = MixingBox.getPrice(ergRing, tokenRing, numRounds) - (needed._1 + Configs.distributeFee, needed._2) - } - - def getMixingNeed(ergRing: Long, tokenRing: Long): (Long, Long) = { // returns what is needed for a single mix box - MixingBox.getPrice(ergRing, tokenRing, numRounds) - } - - def toJson(assets: Seq[CovertAsset], currentMixing: Map[String, Long] = Map.empty, runningMixing: Map[String, Long] = Map.empty): String = { - val sortedAssets = assets.sortBy(_.lastActivity).reverse.sortBy(!_.isErg).sortBy(_.ring == 0) - val assetJsons = sortedAssets.map(asset => { - val curMixingAmount = currentMixing.getOrElse(asset.tokenId, 0L) - val runningMixingAmount = runningMixing.getOrElse(asset.tokenId, 0L) - if (asset.isErg) asset.toJson(MixingBox.getPrice(asset.ring, 0, numRounds)._1, curMixingAmount, runningMixingAmount) - else asset.toJson(MixingBox.getTokenPrice(asset.ring), curMixingAmount, runningMixingAmount) - }) - s"""{ - | "nameCovert": "${nameCovert}", - | "id": "${id}", - | "createdDate": ${createdTime}, - | "deposit": "${depositAddress}", - | "numRounds": $numRounds, - | "assets": [${assetJsons.mkString(",")}], - | "isManualCovert": $isManualCovert - |}""".stripMargin - } - } - - object CreateMixCovertRequest { - def apply(a: Array[Any]): MixCovertRequest = { - val iterator = a.toIterator - new MixCovertRequest( - iterator.next().asInstanceOf[String], // name Covert - iterator.next().asInstanceOf[String], // id - iterator.next().asInstanceOf[Long], // created time - iterator.next().asInstanceOf[String], // deposit address - iterator.next().asInstanceOf[Int], // num rounds - iterator.next().asInstanceOf[Boolean], // isManualCovert - iterator.next.asInstanceOf[BigDecimal].toBigInt() // master secret - ) - } - - implicit val mixCovertDecoder: Decoder[MixCovertRequest] = deriveDecoder[MixCovertRequest] - - def apply(jsonString: String): MixCovertRequest = { - parser.decode[MixCovertRequest](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing MixCovertRequest from Json: $e") - case Right(req) => req - } - } - } - case class CovertAsset(covertId: String, tokenId: String, ring: Long, confirmedDeposit: Long, lastActivity: Long) { /** * @param needed needed amount of this asset for the mix to start @@ -302,7 +32,7 @@ object Models { | "ring": $ring, | "need": $needed, | "confirmedDeposit": $confirmedDeposit, - | "lastActivity": ${lastActivity}, + | "lastActivity": $lastActivity, | "currentMixingAmount": $currentMixing, | "runningMixingAmount": $runningMixing |}""".stripMargin @@ -316,7 +46,7 @@ object Models { object CreateCovertAsset { def apply(a: Array[Any]): CovertAsset = { val i = a.toIterator - new CovertAsset( + CovertAsset( i.next().asInstanceOf[String], i.next().asInstanceOf[String], i.next().asInstanceOf[Long], @@ -335,59 +65,6 @@ object Models { } } - //ixGroupIdCol, amountCol, mixStatusCol, createdTimeCol, depositAddressCol, depositCompletedCol - case class MixGroupRequest(id: String, neededAmount: Long, status: String, createdTime: Long, depositAddress: String, doneDeposit: Long, tokenDoneDeposit: Long, mixingAmount: Long, mixingTokenAmount: Long, neededTokenAmount: Long, tokenId: String, masterKey: BigInt) { - - - def toJson(statJson: String = null): String = { - s""" - |{ - | "id": "${id}", - | "amount": ${neededAmount}, - | "tokenAmount": ${neededTokenAmount}, - | "createdDate": ${createdTime}, - | "deposit": "${depositAddress}", - | "status": "${status}", - | "mixingAmount": $mixingAmount, - | "mixingTokenId": "$tokenId", - | "mixingTokenAmount": $mixingTokenAmount, - | "doneDeposit": ${doneDeposit}, - | "doneTokenDeposit": ${tokenDoneDeposit}, - | "groupStat": $statJson - |} - |""".stripMargin - } - } - - object CreateMixGroupRequest { - def apply(a: Array[Any]): MixGroupRequest = { - val iterator = a.toIterator - new MixGroupRequest( - iterator.next().asInstanceOf[String], // id - iterator.next().asInstanceOf[Long], // amount - iterator.next().asInstanceOf[String], // mix status - iterator.next().asInstanceOf[Long], // created time - iterator.next().asInstanceOf[String], // deposit address - iterator.next().asInstanceOf[Long], // done deposit - iterator.next().asInstanceOf[Long], // token done deposit - iterator.next().asInstanceOf[Long], // mixing amount - iterator.next().asInstanceOf[Long], // mixing token amount - iterator.next().asInstanceOf[Long], // needed tokens - iterator.next().asInstanceOf[String], // token id - iterator.next.asInstanceOf[BigDecimal].toBigInt() // master secret - ) - } - - implicit val mixGroupRequestDecoder: Decoder[MixGroupRequest] = deriveDecoder[MixGroupRequest] - - def apply(jsonString: String): MixGroupRequest = { - parser.decode[MixGroupRequest](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing MixGroupRequest from Json: $e") - case Right(asset) => asset - } - } - } - implicit val encodeMixState: Encoder[MixState] = (a: MixState) => { Json.obj( "isAlice" -> Json.fromBoolean(a.isAlice), @@ -402,7 +79,7 @@ object Models { object CreateMixState { def apply(a: Array[Any]): MixState = { val i = a.toIterator - new MixState( + MixState( i.next().asInstanceOf[String], i.next().asInstanceOf[Int], i.next().asInstanceOf[Boolean] @@ -426,7 +103,7 @@ object Models { object CreateMixHistory { def apply(a: Array[Any]): MixHistory = { val i = a.toIterator - new MixHistory( + MixHistory( i.next().asInstanceOf[String], i.next().asInstanceOf[Int], i.next().asInstanceOf[Boolean], @@ -444,95 +121,6 @@ object Models { } } - case class WithdrawTx(mixId: String, txId: String, time: Long, boxId: String, txBytes: Array[Byte], additionalInfo: String = "") { - override def toString: String = new String(txBytes, StandardCharsets.UTF_16) - - def getFeeBox: Option[String] = { // returns fee box used in this tx if available - val inputs = boxId.split(",") - if (inputs.size > 1) return Some(inputs(inputs.size - 1)) - Option.empty - } - - def getJson: Json = io.circe.parser.parse(toString).getOrElse(Json.Null) - - def getDataInputs: Seq[String] = { - getJson.hcursor.downField("dataInputs").as[Seq[Json]].getOrElse(Seq()) - .map(js => js.hcursor.downField("boxId").as[String].getOrElse(null)) - } - - def getOutputs: Seq[String] = { - getJson.hcursor.downField("outputs").as[Seq[Json]].getOrElse(Seq()) - .map(js => js.hcursor.downField("boxId").as[String].getOrElse(null)) - } - - def getInputs: Seq[String] = { - getJson.hcursor.downField("inputs").as[Seq[Json]].getOrElse(Seq()) - .map(js => js.hcursor.downField("boxId").as[String].getOrElse(null)) - } - } - - implicit val decodeArrayByte: Decoder[Array[Byte]] = (c: HCursor) => for { - s <- c.as[String] - } yield { - s.getBytes("utf-16") - } - - object CreateWithdrawTx { - def apply(a: Array[Any]): WithdrawTx = { - val i = a.toIterator - new WithdrawTx( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Long], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Array[Byte]], - i.next().asInstanceOf[String], - ) - } - - implicit val withdrawTxDecoder: Decoder[WithdrawTx] = deriveDecoder[WithdrawTx] - - def apply(jsonString: String): WithdrawTx = { - parser.decode[WithdrawTx](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing WithdrawTx from Json: $e") - case Right(asset) => asset - } - } - } - - case class MixTransaction(boxId: String, txId: String, txBytes: Array[Byte]) { - override def toString: String = new String(txBytes, StandardCharsets.UTF_16) - } - - object CreateMixTransaction { - def apply(a: Array[Any]): MixTransaction = { - val i = a.toIterator - new MixTransaction( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Array[Byte]] - ) - } - } - - case class DistributeTx(mixGroupId: String, txId: String, order: Int, time: Long, txBytes: Array[Byte], inputs: String) { - override def toString: String = new String(txBytes, StandardCharsets.UTF_16) - } - - object CreateDistributeTx { - def apply(a: Array[Any]): DistributeTx = { - val i = a.toIterator - new DistributeTx( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Int], - i.next().asInstanceOf[Long], - i.next().asInstanceOf[Array[Byte]], - i.next().asInstanceOf[String], - ) - } - } - case class Deposit(address: String, boxId: String, amount: Long, createdTime: Long, tokenAmount: Long) { override def toString: String = this.asJson.toString } @@ -540,7 +128,7 @@ object Models { object CreateDeposit { def apply(a: Array[Any]): Deposit = { val i = a.toIterator - new Deposit( + Deposit( i.next().asInstanceOf[String], i.next().asInstanceOf[String], i.next().asInstanceOf[Long], @@ -564,7 +152,7 @@ object Models { object CreateSpentDeposit { def apply(a: Array[Any]): SpentDeposit = { val i = a.toIterator - new SpentDeposit( + SpentDeposit( i.next().asInstanceOf[String], i.next().asInstanceOf[String], i.next().asInstanceOf[Long], @@ -594,7 +182,7 @@ object Models { object CreateHalfMix { def apply(a: Array[Any]): HalfMix = { val i = a.toIterator - new HalfMix( + HalfMix( i.next().asInstanceOf[String], i.next().asInstanceOf[Int], i.next().asInstanceOf[Long], @@ -631,7 +219,7 @@ object Models { object CreateFullMix { def apply(a: Array[Any]): FullMix = { val i = a.toIterator - new FullMix( + FullMix( i.next().asInstanceOf[String], i.next().asInstanceOf[Int], i.next().asInstanceOf[Long], @@ -657,134 +245,12 @@ object Models { ) } - case class Withdraw(mixId: String, txId: String, createdTime: Long, fullMixBoxId: String, tx: Json) { - override def toString: String = this.asJson.toString - } - - object CreateWithdraw { - def apply(a: Array[Any]): Withdraw = { - val i = a.toIterator - new Withdraw( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Long], - i.next().asInstanceOf[String], - parseTx(i.next().asInstanceOf[Array[Byte]]) - ) - } - - private def parseTx(txBytes: Array[Byte]) = { - parser.parse(new String(txBytes, "utf-16")) match { - case Right(txJson) => txJson - case Left(_) => Json.Null - } - } - } - case class Mix(mixRequest: MixRequest, mixState: Option[MixState], halfMix: Option[HalfMix], fullMix: Option[FullMix], withdraw: Option[Withdraw]) { override def toString: String = this.asJson.toString } - // for scanning blockchain - case class FBox(id: String, r4: GroupElement, r5: GroupElement, r6: GroupElement) - - case class HBox(id: String, r4: GroupElement) - - case class FollowedMix(round: Int, isAlice: Boolean, halfMixBoxId: String, fullMixBoxId: Option[String]) { - override def toString: String = this.asJson.toString - } - - object CreateFollowedMix { - def apply(a: Array[Any]): FollowedMix = { - val i = a.toIterator - FollowedMix( - i.next().asInstanceOf[Int], - i.next().asInstanceOf[Boolean], - i.next().asInstanceOf[String], - try { - Option(i.next().asInstanceOf[String]) - } catch { - case _: Throwable => Option.empty[String] - } - ) - } - - implicit val followedMixDecoder: Decoder[FollowedMix] = deriveDecoder[FollowedMix] - - def apply(jsonString: String): FollowedMix = { - parser.decode[FollowedMix](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing FollowedMix from Json: $e") - case Right(asset) => asset - } - } - } - - case class FollowedHop(round: Int, hopMixBoxId: String) { - override def toString: String = this.asJson.toString - } - - object CreateFollowedHop { - def apply(a: Array[Any]): FollowedHop = { - val i = a.toIterator - FollowedHop( - i.next().asInstanceOf[Int], - i.next().asInstanceOf[String] - ) - } - - implicit val followedHopDecoder: Decoder[FollowedHop] = deriveDecoder[FollowedHop] - - def apply(jsonString: String): FollowedHop = { - parser.decode[FollowedHop](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing FollowedHop from Json: $e") - case Right(asset) => asset - } - } - } - - case class FollowedWithdraw(txId: String, boxId: String) { - override def toString: String = this.asJson.toString - } - - object CreateFollowedWithdraw { - def apply(a: Array[Any]): FollowedWithdraw = { - val i = a.toIterator - FollowedWithdraw( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String] - ) - } - - implicit val followedWithdrawDecoder: Decoder[FollowedWithdraw] = deriveDecoder[FollowedWithdraw] - - def apply(jsonString: String): FollowedWithdraw = { - parser.decode[FollowedWithdraw](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing FollowedWithdraw from Json: $e") - case Right(asset) => asset - } - } - } - - case class PendingRescan(mixId: String, time: Long, round: Int, goBackward: Boolean, boxType: String, mixBoxId: String) { - override def toString: String = this.asJson.toString - } - - object CreatePendingRescan { - def apply(a: Array[Any]): PendingRescan = { - val i = a.toIterator - new PendingRescan( - i.next().asInstanceOf[String], - i.next().asInstanceOf[Long], - i.next().asInstanceOf[Int], - i.next().asInstanceOf[Boolean], - i.next().asInstanceOf[String], - i.next().asInstanceOf[String] - ) - } - } - case class EntityInfo(name: String, id: String, rings: Seq[Long], decimals: Int, dynamicFeeRate: Long) { - def toJson(): String = { + def toJson: String = { s"""{ | "name": "$name", | "id": "$id", @@ -805,108 +271,6 @@ object Models { } } - object MixingBox { - - /** - * calculates needed token for a given ring - * - * @return token needed to enter mixing, i.e. ring + tokenFee - */ - def getTokenPrice(ring: Long): Long = { - val rate: Int = Configs.entranceFee.getOrElse(1000000) - ring + (if (rate > 0 && rate < 1000000) ring / rate else 0) - } - - /** - * calculates needed amount with current fees for a specific mix box - * - * @param ergRing erg ring of mix - * @param tokenRing token ring of mix - * @param mixRounds number of mixing rounds i.e. token num - * @return (erg needed, token needed) - */ - def getPrice(ergRing: Long, tokenRing: Long, mixRounds: Int): (Long, Long) = { - val rate: Int = Configs.entranceFee.getOrElse(1000000) - val tokenPrice: Long = Configs.tokenPrices.get.getOrElse(mixRounds, -1) - assert(tokenPrice != -1) - val ergVal = if (rate > 0 && rate < 1000000) ergRing / rate else 0 - (ergRing + Configs.startFee + tokenPrice + ergVal, getTokenPrice(tokenRing)) - } - - implicit val mixingBoxDecoder: Decoder[MixingBox] = deriveDecoder[MixingBox] - - def apply(jsonString: String): MixingBox = { - parser.decode[MixingBox](jsonString) match { - case Left(e) => throw new Exception(s"Error while parsing MixingBox from Json: $e") - case Right(asset) => asset - } - } - } - - case class MixingBox(withdraw: String, amount: Long, token: Int, mixingTokenAmount: Long, mixingTokenId: String) { - def price: (Long, Long) = { - MixingBox.getPrice(amount, mixingTokenAmount, token) - } - } - - case class MixBoxList(items: Iterable[MixingBox]) - - object MixBoxList { - implicit val ReadsMixBoxList: Reads[MixBoxList] = new Reads[MixBoxList] { - override def reads(json: JsValue): JsResult[MixBoxList] = { - JsSuccess(MixBoxList(json.as[JsArray].value.map(item => { - val withdraw = (item \ "withdraw").as[String] - val amount = (item \ "amount").as[Long] - val token = (item \ "token").as[Int] - val mixingTokenId = (item \ "mixingTokenId").as[String] - val mixingTokenAmount = (item \ "mixingTokenAmount").as[Long] - MixingBox(withdraw, amount, token, mixingTokenAmount, mixingTokenId) - }))) - } - } - } - - case class EndBox(receiverBoxScript: ErgoTree, receiverBoxRegs: Seq[ErgoValue[_]] = Nil, value: Long, tokens: Seq[ErgoToken] = Nil) // box spending full mix box - - case class HalfMixTx(tx: SignedTransaction)(implicit ergoMix: TokenErgoMix) { - val getHalfMixBox: HalfMixBox = HalfMixBox(tx.getOutputsToSpend.get(0)) - require(getHalfMixBox.inputBox.getErgoTree == ergoMix.halfMixContract.getErgoTree) - } - - case class FullMixTx(tx: SignedTransaction)(implicit ergoMix: TokenErgoMix) { - val getFullMixBoxes: (FullMixBox, FullMixBox) = (FullMixBox(tx.getOutputsToSpend.get(0)), FullMixBox(tx.getOutputsToSpend.get(1))) - require(getFullMixBoxes._1.inputBox.getErgoTree == ergoMix.fullMixScriptErgoTree) - require(getFullMixBoxes._2.inputBox.getErgoTree == ergoMix.fullMixScriptErgoTree) - } - - abstract class MixBox(inputBox: InputBox) { - def getRegs = inputBox.getRegisters.asScala - - def getR4 = getRegs.head - - def getR5 = getRegs(1) - - def getR6 = getRegs(2) - } - - case class HalfMixBox(inputBox: InputBox) extends MixBox(inputBox) { - def id = inputBox.getId.toString - - val gX: GroupElement = getR4.getValue match { - case g: GroupElement => g - case any => throw new Exception(s"Invalid value in R4: $any of type ${any.getClass}") - } - } - - case class FullMixBox(inputBox: InputBox) extends MixBox(inputBox) { - def id = inputBox.getId.toString - - val (r4, r5, r6) = (getR4.getValue, getR5.getValue, getR6.getValue) match { - case (c1: GroupElement, c2: GroupElement, gX: GroupElement) => (c1, c2, gX) //Values.GroupElementConstant(c1), Values.GroupElementConstant(c2)) => (c1, c2) - case (r4, r5, r6) => throw new Exception(s"Invalid registers R4:$r4, R5:$r5, R6:$r6") - } - } - case class HopMix(mixId: String, round: Int, createdTime: Long, boxId: String) { override def toString: String = this.asJson.toString } @@ -932,42 +296,6 @@ object Models { } } - case class CovertAssetWithdrawTx(covertId: String, tokenId: String, withdrawAddress: String, createdTime: Long, withdrawStatus: String, txId: String, tx: Array[Byte]) { - override def toString: String = new String(tx, StandardCharsets.UTF_16) - - def getJson: Json = io.circe.parser.parse(toString).getOrElse(Json.Null) - } - - object CreateCovertAssetWithdrawTx { - def apply(a: Array[Any]): CovertAssetWithdrawTx = { - val i = a.toIterator - CovertAssetWithdrawTx( - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Long], - i.next().asInstanceOf[String], - i.next().asInstanceOf[String], - i.next().asInstanceOf[Array[Byte]] - ) - } - } - - sealed abstract class CovertAssetWithdrawStatus(val value: String) - - object CovertAssetWithdrawStatus { - - object NoWithdrawYet extends CovertAssetWithdrawStatus("nothing") - - object Requested extends CovertAssetWithdrawStatus("requested") - - object Complete extends CovertAssetWithdrawStatus("complete") - - private def all = Seq(NoWithdrawYet, Requested, Complete) - - def fromString(s: String): CovertAssetWithdrawStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) - } - class TokenMap { val tokens = mutable.Map.empty[String, Long] diff --git a/mixer/app/models/Request.scala b/mixer/app/models/Request.scala new file mode 100644 index 0000000..4249bba --- /dev/null +++ b/mixer/app/models/Request.scala @@ -0,0 +1,231 @@ +package models + +import app.Configs +import io.circe.generic.auto._ +import io.circe.generic.semiauto.deriveDecoder +import io.circe.syntax._ +import io.circe.{Decoder, parser} +import models.Box.MixingBox +import models.Models.CovertAsset +import models.Status.MixStatus + +object Request { + + case class MixRequest(id: String, groupId: String, amount: Long, numRounds: Int, mixStatus: MixStatus, createdTime: Long, withdrawAddress: String, depositAddress: String, depositCompleted: Boolean, neededAmount: Long, numToken: Int, withdrawStatus: String, mixingTokenAmount: Long, neededTokenAmount: Long, tokenId: String) { + + + def isErg: Boolean = tokenId.isEmpty + + def getAmount: Long = { + if (isErg) amount + else mixingTokenAmount + } + + override def toString: String = this.asJson.toString + } + + object CreateMixRequest { + def apply(a: Array[Any]): MixRequest = { + val i = a.toIterator + MixRequest( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Long], + i.next().asInstanceOf[Int], + MixStatus.fromString(i.next().asInstanceOf[String]), + i.next().asInstanceOf[Long], + i.next().asInstanceOf[String], // withdraw address + i.next().asInstanceOf[String], // deposit address + i.next().asInstanceOf[Boolean], + i.next().asInstanceOf[Long], // needed + i.next().asInstanceOf[Int], // token + i.next().asInstanceOf[String], // withdraw status + i.next().asInstanceOf[Long], // mixing token amount + i.next().asInstanceOf[Long], // needed tokens + i.next().asInstanceOf[String] // token id + ) + } + } + + // The only difference between this class and MixRequest is masterKey + case class MixingRequest( + id: String, + groupId: String, + amount: Long, + numRounds: Int, + mixStatus: MixStatus, + createdTime: Long, + withdrawAddress: String, + depositAddress: String, + depositCompleted: Boolean, + neededAmount: Long, + numToken: Int, + withdrawStatus: String, + mixingTokenAmount: Long, + neededTokenAmount: Long, + tokenId: String, + masterKey: BigInt, + ) { + + def toMixRequest: MixRequest = MixRequest( + id, + groupId, + amount, + numRounds, + mixStatus, + createdTime, + withdrawAddress, + depositAddress, + depositCompleted, + neededAmount, + numToken, + withdrawStatus, + mixingTokenAmount, + neededTokenAmount, + tokenId + ) + } + + object CreateMixingRequest { + def apply(a: Array[Any]): MixingRequest = { + val i = a.toIterator + MixingRequest( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Long], + i.next().asInstanceOf[Int], + MixStatus.fromString(i.next().asInstanceOf[String]), + i.next().asInstanceOf[Long], + i.next().asInstanceOf[String], // withdraw address + i.next().asInstanceOf[String], // deposit address + i.next().asInstanceOf[Boolean], + i.next().asInstanceOf[Long], // needed + i.next().asInstanceOf[Int], // token + i.next().asInstanceOf[String], // withdraw status + i.next().asInstanceOf[Long], // mixing token amount + i.next().asInstanceOf[Long], // needed tokens + i.next().asInstanceOf[String], // token id + i.next().asInstanceOf[BigInt] // master secret key + ) + } + + implicit val mixingRequestDecoder: Decoder[MixingRequest] = deriveDecoder[MixingRequest] + + def apply(jsonString: String): MixingRequest = { + parser.decode[MixingRequest](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing MixingRequest from Json: $e") + case Right(req) => req + } + } + } + + case class MixCovertRequest(nameCovert: String = "", id: String, createdTime: Long, depositAddress: String, numRounds: Int, isManualCovert: Boolean, masterKey: BigInt) { + + + def getMinNeeded(ergRing: Long, tokenRing: Long): (Long, Long) = { // returns what is needed to distribute + val needed = MixingBox.getPrice(ergRing, tokenRing, numRounds) + (needed._1 + Configs.distributeFee, needed._2) + } + + def getMixingNeed(ergRing: Long, tokenRing: Long): (Long, Long) = { // returns what is needed for a single mix box + MixingBox.getPrice(ergRing, tokenRing, numRounds) + } + + def toJson(assets: Seq[CovertAsset], currentMixing: Map[String, Long] = Map.empty, runningMixing: Map[String, Long] = Map.empty): String = { + val sortedAssets = assets.sortBy(_.lastActivity).reverse.sortBy(!_.isErg).sortBy(_.ring == 0) + val assetJsons = sortedAssets.map(asset => { + val curMixingAmount = currentMixing.getOrElse(asset.tokenId, 0L) + val runningMixingAmount = runningMixing.getOrElse(asset.tokenId, 0L) + if (asset.isErg) asset.toJson(MixingBox.getPrice(asset.ring, 0, numRounds)._1, curMixingAmount, runningMixingAmount) + else asset.toJson(MixingBox.getTokenPrice(asset.ring), curMixingAmount, runningMixingAmount) + }) + s"""{ + | "nameCovert": "$nameCovert", + | "id": "$id", + | "createdDate": $createdTime, + | "deposit": "$depositAddress", + | "numRounds": $numRounds, + | "assets": [${assetJsons.mkString(",")}], + | "isManualCovert": $isManualCovert + |}""".stripMargin + } + } + + object CreateMixCovertRequest { + def apply(a: Array[Any]): MixCovertRequest = { + val iterator = a.toIterator + MixCovertRequest( + iterator.next().asInstanceOf[String], // name Covert + iterator.next().asInstanceOf[String], // id + iterator.next().asInstanceOf[Long], // created time + iterator.next().asInstanceOf[String], // deposit address + iterator.next().asInstanceOf[Int], // num rounds + iterator.next().asInstanceOf[Boolean], // isManualCovert + iterator.next.asInstanceOf[BigDecimal].toBigInt() // master secret + ) + } + + implicit val mixCovertDecoder: Decoder[MixCovertRequest] = deriveDecoder[MixCovertRequest] + + def apply(jsonString: String): MixCovertRequest = { + parser.decode[MixCovertRequest](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing MixCovertRequest from Json: $e") + case Right(req) => req + } + } + } + + //ixGroupIdCol, amountCol, mixStatusCol, createdTimeCol, depositAddressCol, depositCompletedCol + case class MixGroupRequest(id: String, neededAmount: Long, status: String, createdTime: Long, depositAddress: String, doneDeposit: Long, tokenDoneDeposit: Long, mixingAmount: Long, mixingTokenAmount: Long, neededTokenAmount: Long, tokenId: String, masterKey: BigInt) { + + + def toJson(statJson: String = null): String = { + s""" + |{ + | "id": "$id", + | "amount": $neededAmount, + | "tokenAmount": $neededTokenAmount, + | "createdDate": $createdTime, + | "deposit": "$depositAddress", + | "status": "$status", + | "mixingAmount": $mixingAmount, + | "mixingTokenId": "$tokenId", + | "mixingTokenAmount": $mixingTokenAmount, + | "doneDeposit": $doneDeposit, + | "doneTokenDeposit": $tokenDoneDeposit, + | "groupStat": $statJson + |} + |""".stripMargin + } + } + + object CreateMixGroupRequest { + def apply(a: Array[Any]): MixGroupRequest = { + val iterator = a.toIterator + MixGroupRequest( + iterator.next().asInstanceOf[String], // id + iterator.next().asInstanceOf[Long], // amount + iterator.next().asInstanceOf[String], // mix status + iterator.next().asInstanceOf[Long], // created time + iterator.next().asInstanceOf[String], // deposit address + iterator.next().asInstanceOf[Long], // done deposit + iterator.next().asInstanceOf[Long], // token done deposit + iterator.next().asInstanceOf[Long], // mixing amount + iterator.next().asInstanceOf[Long], // mixing token amount + iterator.next().asInstanceOf[Long], // needed tokens + iterator.next().asInstanceOf[String], // token id + iterator.next.asInstanceOf[BigDecimal].toBigInt() // master secret + ) + } + + implicit val mixGroupRequestDecoder: Decoder[MixGroupRequest] = deriveDecoder[MixGroupRequest] + + def apply(jsonString: String): MixGroupRequest = { + parser.decode[MixGroupRequest](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing MixGroupRequest from Json: $e") + case Right(asset) => asset + } + } + } + +} diff --git a/mixer/app/models/Rescan.scala b/mixer/app/models/Rescan.scala new file mode 100644 index 0000000..94da5f5 --- /dev/null +++ b/mixer/app/models/Rescan.scala @@ -0,0 +1,103 @@ +package models + +import io.circe.generic.semiauto.deriveDecoder +import io.circe.{Decoder, parser} +import io.circe.generic.auto._ +import io.circe.syntax._ + +object Rescan { + + case class FollowedMix(round: Int, isAlice: Boolean, halfMixBoxId: String, fullMixBoxId: Option[String]) { + override def toString: String = this.asJson.toString + } + + object CreateFollowedMix { + def apply(a: Array[Any]): FollowedMix = { + val i = a.toIterator + FollowedMix( + i.next().asInstanceOf[Int], + i.next().asInstanceOf[Boolean], + i.next().asInstanceOf[String], + try { + Option(i.next().asInstanceOf[String]) + } catch { + case _: Throwable => Option.empty[String] + } + ) + } + + implicit val followedMixDecoder: Decoder[FollowedMix] = deriveDecoder[FollowedMix] + + def apply(jsonString: String): FollowedMix = { + parser.decode[FollowedMix](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing FollowedMix from Json: $e") + case Right(asset) => asset + } + } + } + + case class FollowedHop(round: Int, hopMixBoxId: String) { + override def toString: String = this.asJson.toString + } + + object CreateFollowedHop { + def apply(a: Array[Any]): FollowedHop = { + val i = a.toIterator + FollowedHop( + i.next().asInstanceOf[Int], + i.next().asInstanceOf[String] + ) + } + + implicit val followedHopDecoder: Decoder[FollowedHop] = deriveDecoder[FollowedHop] + + def apply(jsonString: String): FollowedHop = { + parser.decode[FollowedHop](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing FollowedHop from Json: $e") + case Right(asset) => asset + } + } + } + + case class FollowedWithdraw(txId: String, boxId: String) { + override def toString: String = this.asJson.toString + } + + object CreateFollowedWithdraw { + def apply(a: Array[Any]): FollowedWithdraw = { + val i = a.toIterator + FollowedWithdraw( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String] + ) + } + + implicit val followedWithdrawDecoder: Decoder[FollowedWithdraw] = deriveDecoder[FollowedWithdraw] + + def apply(jsonString: String): FollowedWithdraw = { + parser.decode[FollowedWithdraw](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing FollowedWithdraw from Json: $e") + case Right(asset) => asset + } + } + } + + case class PendingRescan(mixId: String, time: Long, round: Int, goBackward: Boolean, boxType: String, mixBoxId: String) { + override def toString: String = this.asJson.toString + } + + object CreatePendingRescan { + def apply(a: Array[Any]): PendingRescan = { + val i = a.toIterator + PendingRescan( + i.next().asInstanceOf[String], + i.next().asInstanceOf[Long], + i.next().asInstanceOf[Int], + i.next().asInstanceOf[Boolean], + i.next().asInstanceOf[String], + i.next().asInstanceOf[String] + ) + } + } + +} diff --git a/mixer/app/models/Status.scala b/mixer/app/models/Status.scala new file mode 100644 index 0000000..c69ebaa --- /dev/null +++ b/mixer/app/models/Status.scala @@ -0,0 +1,83 @@ +package models + +import io.circe.{Decoder, Encoder, HCursor, Json} + +object Status { + + sealed abstract class MixStatus(val value: String) + + implicit val encodeMixStatus: Encoder[MixStatus] = (a: MixStatus) => Json.fromString(a.value) + + object MixStatus { + + object Queued extends MixStatus("queued") + + object Running extends MixStatus("running") + + object Complete extends MixStatus("complete") + + private def all = Seq(Queued, Running, Complete) + + def fromString(s: String): MixStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) + + implicit val decodeMixStatus: Decoder[MixStatus] = (c: HCursor) => for { + status <- c.as[String] + } yield { + MixStatus.fromString(status) + } + } + + sealed abstract class GroupMixStatus(val value: String) + + object GroupMixStatus { + + object Queued extends GroupMixStatus("queued") + + object Starting extends GroupMixStatus("starting") + + object Running extends GroupMixStatus("running") + + object Complete extends GroupMixStatus("complete") + + private def all = Seq(Queued, Running, Complete) + + def fromString(s: String): GroupMixStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) + } + + sealed abstract class MixWithdrawStatus(val value: String) + + object MixWithdrawStatus { + + object NoWithdrawYet extends MixWithdrawStatus("nothing") + + object WithdrawRequested extends MixWithdrawStatus("withdrawing") + + object AgeUSDRequested extends MixWithdrawStatus("minting") + + object HopRequested extends MixWithdrawStatus("hopping") + + object UnderHop extends MixWithdrawStatus("under hop") + + object Withdrawn extends MixWithdrawStatus("withdrawn") + + private def all = Seq(NoWithdrawYet, WithdrawRequested, Withdrawn) + + def fromString(s: String): MixWithdrawStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) + } + + sealed abstract class CovertAssetWithdrawStatus(val value: String) + + object CovertAssetWithdrawStatus { + + object NoWithdrawYet extends CovertAssetWithdrawStatus("nothing") + + object Requested extends CovertAssetWithdrawStatus("requested") + + object Complete extends CovertAssetWithdrawStatus("complete") + + private def all = Seq(NoWithdrawYet, Requested, Complete) + + def fromString(s: String): CovertAssetWithdrawStatus = all.find(_.value == s).getOrElse(throw new Exception(s"Invalid status $s")) + } + +} diff --git a/mixer/app/models/Transaction.scala b/mixer/app/models/Transaction.scala new file mode 100644 index 0000000..2b7992c --- /dev/null +++ b/mixer/app/models/Transaction.scala @@ -0,0 +1,162 @@ +package models + +import io.circe.generic.semiauto.deriveDecoder +import io.circe.{Decoder, HCursor, Json, parser} +import io.circe.generic.auto._ +import io.circe.syntax._ +import mixinterface.TokenErgoMix +import models.Box.{FullMixBox, HalfMixBox, InBox, OutBox} +import org.ergoplatform.appkit.SignedTransaction + +import java.nio.charset.StandardCharsets + +object Transaction { + + case class SpendTx(inboxes: Seq[InBox], outboxes: Seq[OutBox], id: String, address: String, timestamp: Long) + + case class WithdrawTx(mixId: String, txId: String, time: Long, boxId: String, txBytes: Array[Byte], additionalInfo: String = "") { + override def toString: String = new String(txBytes, StandardCharsets.UTF_16) + + def getFeeBox: Option[String] = { // returns fee box used in this tx if available + val inputs = boxId.split(",") + if (inputs.size > 1) return Some(inputs(inputs.size - 1)) + Option.empty + } + + def getJson: Json = io.circe.parser.parse(toString).getOrElse(Json.Null) + + def getDataInputs: Seq[String] = { + getJson.hcursor.downField("dataInputs").as[Seq[Json]].getOrElse(Seq()) + .map(js => js.hcursor.downField("boxId").as[String].getOrElse(null)) + } + + def getOutputs: Seq[String] = { + getJson.hcursor.downField("outputs").as[Seq[Json]].getOrElse(Seq()) + .map(js => js.hcursor.downField("boxId").as[String].getOrElse(null)) + } + + def getInputs: Seq[String] = { + getJson.hcursor.downField("inputs").as[Seq[Json]].getOrElse(Seq()) + .map(js => js.hcursor.downField("boxId").as[String].getOrElse(null)) + } + } + + implicit val decodeArrayByte: Decoder[Array[Byte]] = (c: HCursor) => for { + s <- c.as[String] + } yield { + s.getBytes("utf-16") + } + + object CreateWithdrawTx { + def apply(a: Array[Any]): WithdrawTx = { + val i = a.toIterator + WithdrawTx( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Long], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Array[Byte]], + i.next().asInstanceOf[String], + ) + } + + implicit val withdrawTxDecoder: Decoder[WithdrawTx] = deriveDecoder[WithdrawTx] + + def apply(jsonString: String): WithdrawTx = { + parser.decode[WithdrawTx](jsonString) match { + case Left(e) => throw new Exception(s"Error while parsing WithdrawTx from Json: $e") + case Right(asset) => asset + } + } + } + + case class MixTransaction(boxId: String, txId: String, txBytes: Array[Byte]) { + override def toString: String = new String(txBytes, StandardCharsets.UTF_16) + } + + object CreateMixTransaction { + def apply(a: Array[Any]): MixTransaction = { + val i = a.toIterator + MixTransaction( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Array[Byte]] + ) + } + } + + case class DistributeTx(mixGroupId: String, txId: String, order: Int, time: Long, txBytes: Array[Byte], inputs: String) { + override def toString: String = new String(txBytes, StandardCharsets.UTF_16) + } + + object CreateDistributeTx { + def apply(a: Array[Any]): DistributeTx = { + val i = a.toIterator + DistributeTx( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Int], + i.next().asInstanceOf[Long], + i.next().asInstanceOf[Array[Byte]], + i.next().asInstanceOf[String], + ) + } + } + + case class Withdraw(mixId: String, txId: String, createdTime: Long, fullMixBoxId: String, tx: Json) { + override def toString: String = this.asJson.toString + } + + object CreateWithdraw { + def apply(a: Array[Any]): Withdraw = { + val i = a.toIterator + Withdraw( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Long], + i.next().asInstanceOf[String], + parseTx(i.next().asInstanceOf[Array[Byte]]) + ) + } + + private def parseTx(txBytes: Array[Byte]) = { + parser.parse(new String(txBytes, "utf-16")) match { + case Right(txJson) => txJson + case Left(_) => Json.Null + } + } + } + + case class HalfMixTx(tx: SignedTransaction)(implicit ergoMix: TokenErgoMix) { + val getHalfMixBox: HalfMixBox = HalfMixBox(tx.getOutputsToSpend.get(0)) + require(getHalfMixBox.inputBox.getErgoTree == ergoMix.halfMixContract.getErgoTree) + } + + case class FullMixTx(tx: SignedTransaction)(implicit ergoMix: TokenErgoMix) { + val getFullMixBoxes: (FullMixBox, FullMixBox) = (FullMixBox(tx.getOutputsToSpend.get(0)), FullMixBox(tx.getOutputsToSpend.get(1))) + require(getFullMixBoxes._1.inputBox.getErgoTree == ergoMix.fullMixScriptErgoTree) + require(getFullMixBoxes._2.inputBox.getErgoTree == ergoMix.fullMixScriptErgoTree) + } + + case class CovertAssetWithdrawTx(covertId: String, tokenId: String, withdrawAddress: String, createdTime: Long, withdrawStatus: String, txId: String, tx: Array[Byte]) { + override def toString: String = new String(tx, StandardCharsets.UTF_16) + + def getJson: Json = io.circe.parser.parse(toString).getOrElse(Json.Null) + } + + object CreateCovertAssetWithdrawTx { + def apply(a: Array[Any]): CovertAssetWithdrawTx = { + val i = a.toIterator + CovertAssetWithdrawTx( + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Long], + i.next().asInstanceOf[String], + i.next().asInstanceOf[String], + i.next().asInstanceOf[Array[Byte]] + ) + } + } + +} diff --git a/mixer/app/network/BlockExplorer.scala b/mixer/app/network/BlockExplorer.scala index 31c4863..f25959c 100644 --- a/mixer/app/network/BlockExplorer.scala +++ b/mixer/app/network/BlockExplorer.scala @@ -6,8 +6,9 @@ import app.Configs import io.circe.Json import javax.inject.Singleton -import models.Models.{InBox, OutBox, SpendTx} -import org.ergoplatform.appkit.{ErgoToken, ErgoTreeTemplate} +import models.Box.{InBox, OutBox} +import models.Transaction.SpendTx +import org.ergoplatform.appkit.ErgoToken import scala.util.{Failure, Success, Try} diff --git a/mixer/app/network/Client.scala b/mixer/app/network/Client.scala index 220ebb2..57f7853 100644 --- a/mixer/app/network/Client.scala +++ b/mixer/app/network/Client.scala @@ -1,8 +1,7 @@ package network -import java.net.{Authenticator, InetSocketAddress, PasswordAuthentication, Proxy} +import java.net.{InetSocketAddress, Proxy} -import app.Configs.readKey import app.Configs import helpers.{ErgoMixerUtils, TrayUtils} import javax.inject.Inject diff --git a/mixer/app/network/NetworkUtils.scala b/mixer/app/network/NetworkUtils.scala index ed7aa22..3876936 100644 --- a/mixer/app/network/NetworkUtils.scala +++ b/mixer/app/network/NetworkUtils.scala @@ -3,8 +3,7 @@ package network import app._ import javax.inject.{Inject, Singleton} import mixinterface.TokenErgoMix -import models.Models -import models.Models.OutBox +import models.Box.OutBox import org.ergoplatform.appkit.{BlockchainContext, ErgoClient, InputBox} import play.api.Logger import wallet.WalletHelper @@ -74,7 +73,7 @@ class NetworkUtils @Inject()(explorer: BlockExplorer) { if (considerPool) txPool = explorer.getPoolTransactionsStr val unspentBoxes = getUnspentBoxes(tokenErgoMix.get.halfMixAddress.toString) unspentBoxes.filter(box => { - (!considerPool || !txPool.contains(s""""${box.id.toString}","transactionId"""")) && // not already in mempool + (!considerPool || !txPool.contains(s""""${box.id}","transactionId"""")) && // not already in mempool box.registers.contains("R4") && !WalletHelper.poisonousHalfs.contains(box.ge("R4")) // not poisonous }).toList @@ -111,7 +110,7 @@ class NetworkUtils @Inject()(explorer: BlockExplorer) { * @param poolAmount mix ring * @return list of unspent full-boxes in a specific ring */ - def getFullMixBoxes(poolAmount: Long = -1): Seq[Models.OutBox] = { + def getFullMixBoxes(poolAmount: Long = -1): Seq[OutBox] = { usingClient { implicit ctx => val unspent = getUnspentBoxes(tokenErgoMix.get.fullMixAddress.toString) if (poolAmount != -1) @@ -145,7 +144,7 @@ class NetworkUtils @Inject()(explorer: BlockExplorer) { * @param boxId box id * @return tx spending the box with id boxId */ - def getSpendingTxId(boxId: String) = { + def getSpendingTxId(boxId: String): Option[String] = { usingClient { implicit ctx => explorer.getSpendingTxId(boxId) } diff --git a/mixer/app/wallet/WalletHelper.scala b/mixer/app/wallet/WalletHelper.scala index ce10ce1..a2ce66b 100644 --- a/mixer/app/wallet/WalletHelper.scala +++ b/mixer/app/wallet/WalletHelper.scala @@ -3,7 +3,7 @@ package wallet import java.math.BigInteger import app.Configs import org.ergoplatform.{ErgoAddress, ErgoAddressEncoder} -import org.ergoplatform.appkit.{Address, BlockchainContext, ConstantsBuilder, JavaHelpers, NetworkType} +import org.ergoplatform.appkit.{Address, JavaHelpers, NetworkType} import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.eval._ import sigmastate.interpreter.CryptoConstants @@ -34,17 +34,6 @@ object WalletHelper { JavaHelpers.decodeStringToGE(hex) } - @deprecated("This needs blockchain context and compiles contract. Use getAddressOfSecret instead", "1.0") - def getProveDlogAddress(z: BigInt, ctx: BlockchainContext): String = { - val gZ: GroupElement = g.exp(z.bigInteger) - val contract = ctx.compileContract( - ConstantsBuilder.create().item( - "gZ", gZ - ).build(), "{proveDlog(gZ)}" - ) - addressEncoder.fromProposition(contract.getErgoTree).get.toString - } - def getAddressOfSecret(secret: BigInt): String = new Address(JavaHelpers.createP2PKAddress( DLogProverInput(secret.bigInteger).publicImage, addressEncoder.networkPrefix)).toString diff --git a/mixer/build.sbt b/mixer/build.sbt index dd9fdc7..adde1a8 100644 --- a/mixer/build.sbt +++ b/mixer/build.sbt @@ -8,7 +8,7 @@ libraryDependencies += filters lazy val commonSettings = Seq( organization := "ergoMixer", - version := "4.1.1", + version := "4.2.0", scalaVersion := "2.12.10", resolvers ++= Seq( Resolver.sonatypeRepo("public"), diff --git a/mixer/conf/logback.xml b/mixer/conf/logback.xml index a28c311..df978a6 100644 --- a/mixer/conf/logback.xml +++ b/mixer/conf/logback.xml @@ -1,5 +1,8 @@ + + + @@ -8,8 +11,13 @@ - - ${user.home}/ergoMixer/application.log + + ${logPath}/application.log + + ${logPath}/log_%d{yyyy-MM-dd}_%i.zip + ${maxLogFileSize} + ${maxLogHistory} + %date [%level] from %logger in %thread - %message%n%xException diff --git a/mixer/conf/reference.conf b/mixer/conf/reference.conf index 65f2cd3..0e0fde2 100644 --- a/mixer/conf/reference.conf +++ b/mixer/conf/reference.conf @@ -35,3 +35,9 @@ slick.dbs.default.db.password=${?database.pass} play.evolutions.autoApply = true play.evolutions.db.default.autoApply=true play.evolutions.db.default.autoApplyDowns=true + +logPath=${user.home}/ergoMixer +// maximum size for each log file +maxLogFileSize=10MB +// how many days keep log files +maxLogHistory=45 diff --git a/mixer/conf/routes b/mixer/conf/routes index 3cfeb63..7395a5a 100644 --- a/mixer/conf/routes +++ b/mixer/conf/routes @@ -22,33 +22,33 @@ GET /rings controllers.ApiController.rings # Add withdraw to database +nocsrf -POST /mix/withdraw controllers.ApiController.withdraw +POST /mix/withdraw controllers.MixController.withdraw # New mixing backend with tokens -POST /mix/request controllers.ApiController.mixRequest -GET /mix/request/list controllers.ApiController.mixGroupRequestList -GET /mix/request/activeList controllers.ApiController.mixGroupRequestActiveList -GET /mix/request/completeList controllers.ApiController.mixGroupRequestCompleteList -GET /mix/request/:id/list controllers.ApiController.mixRequestList(id: String, status ?= "all") -GET /mix/fee controllers.ApiController.mixingFee -GET /mix/supported controllers.ApiController.supported - -POST /covert controllers.ApiController.covertRequest -POST /covert/:covertId/asset controllers.ApiController.covertAddOrUpdate(covertId: String) -POST /covert/:covertId/name controllers.ApiController.covertChangeName(covertId: String) -GET /covert/list controllers.ApiController.covertList -GET /covert/:covertId/asset controllers.ApiController.covertAssetList(covertId: String) -POST /covert/:covertId/address controllers.ApiController.setCovertAddresses(covertId: String) -GET /covert/:covertId/address controllers.ApiController.getCovertAddresses(covertId: String) -GET /covert/keys controllers.ApiController.covertKeys -GET /covert/:covertId/key controllers.ApiController.getCovertPrivateKey(covertId: String) -POST /covert/:covertId/withdraw controllers.ApiController.withdrawCovertAsset(covertId: String) - -GET /ageusd/height controllers.ApiController.currentHeight() -GET /ageusd/fee controllers.ApiController.ageusdFee() -POST /ageusd/mint controllers.ApiController.mint() -GET /ageusd/oracle/:tokenId controllers.ApiController.oracleBox(tokenId: String) -GET /ageusd/bank/:tokenId controllers.ApiController.bankBox(tokenId: String) +POST /mix/request controllers.MixController.mixRequest +GET /mix/request/list controllers.MixController.mixGroupRequestList +GET /mix/request/activeList controllers.MixController.mixGroupRequestActiveList +GET /mix/request/completeList controllers.MixController.mixGroupRequestCompleteList +GET /mix/request/:id/list controllers.MixController.mixRequestList(id: String, status ?= "all") +GET /mix/fee controllers.MixController.mixingFee +GET /mix/supported controllers.MixController.supported + +POST /covert controllers.CovertController.covertRequest +POST /covert/:covertId/asset controllers.CovertController.covertAddOrUpdate(covertId: String) +POST /covert/:covertId/name controllers.CovertController.covertChangeName(covertId: String) +GET /covert/list controllers.CovertController.covertList +GET /covert/:covertId/asset controllers.CovertController.covertAssetList(covertId: String) +POST /covert/:covertId/address controllers.CovertController.setCovertAddresses(covertId: String) +GET /covert/:covertId/address controllers.CovertController.getCovertAddresses(covertId: String) +GET /covert/keys controllers.CovertController.covertKeys +GET /covert/:covertId/key controllers.CovertController.getCovertPrivateKey(covertId: String) +POST /covert/:covertId/withdraw controllers.CovertController.withdrawCovertAsset(covertId: String) + +GET /ageusd/height controllers.AgeUSDController.currentHeight() +GET /ageusd/fee controllers.AgeUSDController.ageusdFee() +POST /ageusd/mint controllers.AgeUSDController.mint() +GET /ageusd/oracle/:tokenId controllers.AgeUSDController.oracleBox(tokenId: String) +GET /ageusd/bank/:tokenId controllers.AgeUSDController.bankBox(tokenId: String) GET /dashboard controllers.ApiController.dashboard diff --git a/mixer/test/dataset/Sample17_FullMixList.json b/mixer/test/dataset/Sample17_FullMixList.json new file mode 100644 index 0000000..7586d8a --- /dev/null +++ b/mixer/test/dataset/Sample17_FullMixList.json @@ -0,0 +1,19 @@ +{ + "items": [ + { + "mixId": "9d791ee1-7b40-4a7b-9a71-7b61af3cde37", + "round": 2, + "createdTime": 1634197489046, + "halfMixBoxId": "e76ec269312eb6cd431af5aa83212d5be547e189f5585cceaa849fc80e32043a", + "fullMixBoxId": "1a643aeab506622b90b1a5616866082f295f525d428929604a7f94f994c0f1fa" + }, + { + "mixId": "9d791ee1-7b40-4a7b-9a71-7b61af3cde37", + "round": 3, + "createdTime": 1634197489046, + "halfMixBoxId": "86c9641bdd9773bf79f8ffa391870a0b4339b8a51b6a2f9e16a707e3e363f547", + "fullMixBoxId": "801f4ea1f70e8b6198c9cb702ad0522630df022637827d6de307546aaa89d1ac" + } + ] +} + diff --git a/mixer/test/dataset/Sample17_HalfMixList.json b/mixer/test/dataset/Sample17_HalfMixList.json new file mode 100644 index 0000000..d45e637 --- /dev/null +++ b/mixer/test/dataset/Sample17_HalfMixList.json @@ -0,0 +1,11 @@ +{ + "items": [ + { + "mixId": "9d791ee1-7b40-4a7b-9a71-7b61af3cde37", + "round": 3, + "createdTime": 1634194123949, + "halfMixBoxId": "86c9641bdd9773bf79f8ffa391870a0b4339b8a51b6a2f9e16a707e3e363f547", + "isSpent": true + } + ] +} diff --git a/mixer/test/dataset/Sample17_HopMixList.json b/mixer/test/dataset/Sample17_HopMixList.json new file mode 100644 index 0000000..0f9871e --- /dev/null +++ b/mixer/test/dataset/Sample17_HopMixList.json @@ -0,0 +1,17 @@ +{ + "items": [ + { + "mixId": "9d791ee1-7b40-4a7b-9a71-7b61af3cde37", + "round": 0, + "createdTime": 1634194123949, + "boxId": "fcae6237003333ce37acd77c6accd981e889c52bb7e848c8574b98def9f2e990" + }, + { + "mixId": "9d791ee1-7b40-4a7b-9a71-7b61af3cde37", + "round": 1, + "createdTime": 1634194123949, + "boxId": "ab29c72b55fa135bc82cc566ad36c6fa38c9a3c1afcf06eede61e68abcf7eeee" + } + ] +} + diff --git a/mixer/test/dataset/Sample17_MixRequest.json b/mixer/test/dataset/Sample17_MixRequest.json new file mode 100644 index 0000000..d1203ce --- /dev/null +++ b/mixer/test/dataset/Sample17_MixRequest.json @@ -0,0 +1,18 @@ +{ + "id": "9d791ee1-7b40-4a7b-9a71-7b61af3cde37", + "groupId": "a30a08af-3a9b-47a0-9703-cfe5bad5e875", + "amount": 10000000000, + "numRounds": 30, + "mixStatus": "running", + "createdTime": 1633802506226, + "withdrawAddress": "9fYfh5kJRw1CWkCRepfEAzJkj9dFGX88rfsq3sNTRiVRgnWGGRC", + "depositAddress": "9fsEvV5qcApUyZGiUfReqSZnwn4vSkXMr2juYjAz4Hu1tmqFVT5", + "depositCompleted": true, + "neededAmount": 10155833333, + "numToken": 30, + "withdrawStatus": "nothing", + "mixingTokenAmount": 0, + "neededTokenAmount": 0, + "tokenId": "", + "masterKey": "13409451885754448647109802699845420163693098594592537239731787078490546220417" +} diff --git a/mixer/test/mixer/ErgoMixerSpec.scala b/mixer/test/mixer/ErgoMixerSpec.scala index b16140e..d51504a 100644 --- a/mixer/test/mixer/ErgoMixerSpec.scala +++ b/mixer/test/mixer/ErgoMixerSpec.scala @@ -3,8 +3,10 @@ package mixer import wallet.WalletHelper import dao._ import mocked.MockedNetworkUtils -import models.Models.{CovertAsset, FullMix, HalfMix, HopMix, MixCovertRequest, MixState, MixingRequest, WithdrawTx} -import models.Models.MixWithdrawStatus.{HopRequested, WithdrawRequested} +import models.Models.{CovertAsset, FullMix, HalfMix, HopMix, MixState} +import models.Request.{MixCovertRequest, MixingRequest} +import models.Status.MixWithdrawStatus.{HopRequested, WithdrawRequested} +import models.Transaction.WithdrawTx import org.scalatestplus.mockito.MockitoSugar.mock import play.api.db.slick.DatabaseConfigProvider import testHandlers.{ErgoMixerDataset, TestSuite} diff --git a/mixer/test/mixer/HopMixerSpec.scala b/mixer/test/mixer/HopMixerSpec.scala index 582761b..dbca726 100644 --- a/mixer/test/mixer/HopMixerSpec.scala +++ b/mixer/test/mixer/HopMixerSpec.scala @@ -3,7 +3,9 @@ package mixer import dao._ import helpers.ErgoMixerUtils import mocked.{MockedAliceOrBob, MockedBlockExplorer, MockedBlockchainContext, MockedErgoMixer, MockedNetworkUtils} -import models.Models.{HopMix, MixingRequest, WithdrawTx} +import models.Models.HopMix +import models.Request.MixingRequest +import models.Transaction.WithdrawTx import org.ergoplatform.appkit.SignedTransaction import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when diff --git a/mixer/test/mixer/RescanSpec.scala b/mixer/test/mixer/RescanSpec.scala index 01bfe45..5306db6 100644 --- a/mixer/test/mixer/RescanSpec.scala +++ b/mixer/test/mixer/RescanSpec.scala @@ -3,8 +3,8 @@ package mixer import dao.DAOUtils import helpers.ErgoMixerUtils import mocked._ -import models.Models.MixStatus.Complete -import models.Models.MixWithdrawStatus.UnderHop +import models.Status.MixStatus.Complete +import models.Status.MixWithdrawStatus.UnderHop import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar.mock @@ -28,7 +28,6 @@ class RescanSpec private val dataset = MixScannerDataset def createRescanObject: Rescan = new Rescan( - networkUtils.getMocked, mixScanner.getMocked, daoUtils: DAOUtils, daoContext.mixingRequestsDAO, @@ -163,4 +162,119 @@ class RescanSpec db_withdraw should contain ((withdrawTx.mixId, withdrawTx.txId, withdrawTx.boxId)) } + /** + * Name: processRescan + * Purpose: Testing data removal in database + * Dependencies: + * mixingRequestDAO + * mixScanner + * fullMixDAO, + * halfMixDAO + */ + property("processRescan should previous mix box (half box) in backward rescanning") { + // get test data from dataset + val testSample_pendingRescan = dataset.backwardRescanFull_specData + val testSample_dbData = dataset.backwardRescanMix_dbData + val testSample_result = dataset.backwardRescan_mixResultData + val mixId = testSample_pendingRescan._1 + val round = testSample_pendingRescan._2 + val goBackward = testSample_pendingRescan._3 + val boxType = testSample_pendingRescan._4 + val mixBoxId = testSample_pendingRescan._5 + + val fullMixList = testSample_result._1 + val halfMixList = testSample_dbData._3.map(mix => (mix.mixId, mix.round, mix.halfMixBoxId)) + + // make dependency tables ready before test + daoUtils.awaitResult(daoContext.mixingRequestsDAO.clear) + daoUtils.awaitResult(daoContext.fullMixDAO.clear) + daoUtils.awaitResult(daoContext.mixingRequestsDAO.insert(testSample_dbData._1)) + testSample_dbData._2.foreach(full => daoUtils.awaitResult(daoContext.fullMixDAO.insert(full))) + testSample_dbData._3.foreach(half => daoUtils.awaitResult(daoContext.halfMixDAO.insert(half))) + + val rescan = createRescanObject + rescan.processRescan(mixId, round, goBackward, boxType, mixBoxId) + + // verify return value + val db_fullMix = daoUtils.awaitResult(daoContext.fullMixDAO.all).map(mix => (mix.mixId, mix.round, mix.halfMixBoxId, mix.fullMixBoxId)) + db_fullMix should equal (fullMixList) + val db_halfMix = daoUtils.awaitResult(daoContext.halfMixDAO.all).map(mix => (mix.mixId, mix.round, mix.halfMixBoxId)) + db_halfMix should equal (halfMixList) + } + + /** + * Name: processRescan + * Purpose: Testing data removal in database + * Dependencies: + * mixingRequestDAO + * mixScanner + * fullMixDAO, + * halfMixDAO + */ + property("processRescan should delete previous mix box (full box) in backward rescanning") { + // get test data from dataset + val testSample_pendingRescan = dataset.backwardRescanHalf_specData + val testSample_dbData = dataset.backwardRescanMix_dbData + val testSample_result = dataset.backwardRescan_mixResultData + val mixId = testSample_pendingRescan._1 + val round = testSample_pendingRescan._2 + val goBackward = testSample_pendingRescan._3 + val boxType = testSample_pendingRescan._4 + val mixBoxId = testSample_pendingRescan._5 + + val fullMixList = testSample_result._1 + val halfMixList = testSample_result._2 + + // make dependency tables ready before test + daoUtils.awaitResult(daoContext.mixingRequestsDAO.clear) + daoUtils.awaitResult(daoContext.fullMixDAO.clear) + daoUtils.awaitResult(daoContext.halfMixDAO.clear) + daoUtils.awaitResult(daoContext.mixingRequestsDAO.insert(testSample_dbData._1)) + testSample_dbData._2.foreach(full => daoUtils.awaitResult(daoContext.fullMixDAO.insert(full))) + testSample_dbData._3.foreach(half => daoUtils.awaitResult(daoContext.halfMixDAO.insert(half))) + + val rescan = createRescanObject + rescan.processRescan(mixId, round, goBackward, boxType, mixBoxId) + + // verify return value + val db_fullMix = daoUtils.awaitResult(daoContext.fullMixDAO.all).map(mix => (mix.mixId, mix.round, mix.halfMixBoxId, mix.fullMixBoxId)) + db_fullMix should equal (fullMixList) + val db_halfMix = daoUtils.awaitResult(daoContext.halfMixDAO.all).map(mix => (mix.mixId, mix.round, mix.halfMixBoxId)) + db_halfMix should equal (halfMixList) + } + + /** + * Name: processRescan + * Purpose: Testing data removal in database + * Dependencies: + * mixingRequestDAO + * mixScanner + * hopMixDAO + */ + property("processRescan should delete last hop box in backward rescanning") { + // get test data from dataset + val testSample_pendingRescan = dataset.backwardRescanHop_specData + val testSample_dbData = dataset.backwardRescanHop_dbData + val mixId = testSample_pendingRescan._1 + val round = testSample_pendingRescan._2 + val goBackward = testSample_pendingRescan._3 + val boxType = testSample_pendingRescan._4 + val mixBoxId = testSample_pendingRescan._5 + + val hopMixList = dataset.backwardRescan_hopResultData + + // make dependency tables ready before test + daoUtils.awaitResult(daoContext.mixingRequestsDAO.clear) + daoUtils.awaitResult(daoContext.hopMixDAO.clear) + daoUtils.awaitResult(daoContext.mixingRequestsDAO.insert(testSample_dbData._1)) + testSample_dbData._2.foreach(hop => daoUtils.awaitResult(daoContext.hopMixDAO.insert(hop))) + + val rescan = createRescanObject + rescan.processRescan(mixId, round, goBackward, boxType, mixBoxId) + + // verify return value + val db_hopMix = daoUtils.awaitResult(daoContext.hopMixDAO.all).map(mix => (mix.mixId, mix.round, mix.boxId)) + db_hopMix should equal (hopMixList) + } + } diff --git a/mixer/test/mixer/WithdrawMixerSpec.scala b/mixer/test/mixer/WithdrawMixerSpec.scala index 7c9273a..237bf10 100644 --- a/mixer/test/mixer/WithdrawMixerSpec.scala +++ b/mixer/test/mixer/WithdrawMixerSpec.scala @@ -3,7 +3,10 @@ package mixer import dao._ import helpers.ErgoMixerUtils import mocked.{MockedBlockExplorer, MockedBlockchainContext, MockedNetworkUtils} -import models.Models.{HopMix, MixGroupRequest, MixStatus, MixWithdrawStatus, MixingRequest, WithdrawTx} +import models.Models.HopMix +import models.Request.{MixGroupRequest, MixingRequest} +import models.Status.{MixStatus, MixWithdrawStatus} +import models.Transaction.WithdrawTx import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar.mock diff --git a/mixer/test/mocked/MockedAliceOrBob.scala b/mixer/test/mocked/MockedAliceOrBob.scala index a99bea4..3d6ee7f 100644 --- a/mixer/test/mocked/MockedAliceOrBob.scala +++ b/mixer/test/mocked/MockedAliceOrBob.scala @@ -17,7 +17,7 @@ class MockedAliceOrBob extends MockitoSugar { def getMocked = aliceOrBob - def setTestCases: Unit = { + def setTestCases(): Unit = { val withdrawFromHop = dataset_hopMixer.withdrawFromHop_aliceOrBobMockedData val nextHop = dataset_hopMixer.nextHop_aliceOrBobMockedData @@ -35,6 +35,6 @@ class MockedAliceOrBob extends MockitoSugar { */ def setReturnValue_spendHopBox(secret: BigInteger, boxId: String, destinationAddress: String, tx: SignedTransaction): Unit = when(aliceOrBob.spendHopBox(secret, boxId, destinationAddress)).thenReturn(tx) - setTestCases + setTestCases() } diff --git a/mixer/test/mocked/MockedBlockExplorer.scala b/mixer/test/mocked/MockedBlockExplorer.scala index 8bcdd13..f366580 100644 --- a/mixer/test/mocked/MockedBlockExplorer.scala +++ b/mixer/test/mocked/MockedBlockExplorer.scala @@ -1,10 +1,11 @@ package mocked -import models.Models.SpendTx +import models.Transaction.SpendTx import org.scalatestplus.mockito.MockitoSugar import network.BlockExplorer import org.mockito.Mockito.when import testHandlers.{HopMixerDataset, MixScannerDataset, WithdrawMixerDataset} + import javax.inject.Singleton @Singleton @@ -17,7 +18,7 @@ class MockedBlockExplorer extends MockitoSugar { def getMocked = blockExplorer - def setTestCases: Unit = { + def setTestCases(): Unit = { val confirmedTx = dataset_withdrawMixer.confirmedTx_mockedData val notMinedTx = dataset_withdrawMixer.notMinedTx_mockedData val confirmedTx_initiateHop = dataset_withdrawMixer.confirmedTxInitiateHop_mockedData @@ -89,6 +90,6 @@ class MockedBlockExplorer extends MockitoSugar { */ def setReturnValue_getBoxIdsByAddress(address: String, boxIds: Seq[String]): Unit = when(blockExplorer.getBoxIdsByAddress(address)).thenReturn(boxIds) - setTestCases + setTestCases() } diff --git a/mixer/test/mocked/MockedBlockchainContext.scala b/mixer/test/mocked/MockedBlockchainContext.scala index 26d93f3..8649dad 100644 --- a/mixer/test/mocked/MockedBlockchainContext.scala +++ b/mixer/test/mocked/MockedBlockchainContext.scala @@ -18,7 +18,7 @@ class MockedBlockchainContext extends MockitoSugar { def getMocked = context - def setTestCases: Unit = { + def setTestCases(): Unit = { val notMinedTx_initiateHop = dataset_withdrawMixer.notMinedWithdrawTx setReturnValue_signedTxFromJson(notMinedTx_initiateHop._1.toString, notMinedTx_initiateHop._2) @@ -47,6 +47,6 @@ class MockedBlockchainContext extends MockitoSugar { */ def setExceptionOnCall_sendTransaction(tx: SignedTransaction): Unit = when(context.sendTransaction(tx)).thenAnswer(_ => {throw new Throwable("Malformed transaction (unit-test case)")}) - setTestCases + setTestCases() } diff --git a/mixer/test/mocked/MockedErgoMixer.scala b/mixer/test/mocked/MockedErgoMixer.scala index 50ef0cf..143773a 100644 --- a/mixer/test/mocked/MockedErgoMixer.scala +++ b/mixer/test/mocked/MockedErgoMixer.scala @@ -14,7 +14,7 @@ class MockedErgoMixer extends MockitoSugar { def getMocked = ergoMixer - def setTestCases: Unit = { + def setTestCases(): Unit = { val withdrawFromHop = dataset_hopMixer.withdrawFromHop_ergoMixerMockedData setReturnValue_getMasterSecret(withdrawFromHop._1, withdrawFromHop._2) @@ -37,6 +37,6 @@ class MockedErgoMixer extends MockitoSugar { */ def setReturnValue_getWithdrawAddress(mixId: String, withdrawAddress: String): Unit = when(ergoMixer.getWithdrawAddress(mixId)).thenReturn(withdrawAddress) - setTestCases + setTestCases() } diff --git a/mixer/test/mocked/MockedMixScanner.scala b/mixer/test/mocked/MockedMixScanner.scala index 21fe0bb..eb97d6d 100644 --- a/mixer/test/mocked/MockedMixScanner.scala +++ b/mixer/test/mocked/MockedMixScanner.scala @@ -1,7 +1,7 @@ package mocked import mixer.MixScanner -import models.Models.{FollowedHop, FollowedMix, FollowedWithdraw} +import models.Rescan.{FollowedHop, FollowedMix, FollowedWithdraw} import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar import testHandlers.MixScannerDataset @@ -16,15 +16,22 @@ class MockedMixScanner extends MockitoSugar { def getMocked = mixScanner - def setTestCases: Unit = { + def setTestCases(): Unit = { val withdrawFromHop = dataset_mixScanner.rescan_mockedData val followFullMixMockedData = withdrawFromHop._1 val withdrawWithHopMockedData = withdrawFromHop._2 val followHopMix = dataset_mixScanner.mockFollowHopMix + val backwardRescan = dataset_mixScanner.backwardRescan_mockedData + val backwardRescanFollowedFullMockedData = backwardRescan._1 + val backwardRescanFollowedHopMockedData = backwardRescan._2 + val backwardRescanFollowedHalfMockedData = backwardRescan._3 setReturnValue_followFullMix(followFullMixMockedData._1, followFullMixMockedData._2, followFullMixMockedData._3, followFullMixMockedData._4) + setReturnValue_followFullMix(backwardRescanFollowedFullMockedData._1, backwardRescanFollowedFullMockedData._2, backwardRescanFollowedFullMockedData._3, backwardRescanFollowedFullMockedData._4) + setReturnValue_followHalfMix(backwardRescanFollowedHalfMockedData._1, backwardRescanFollowedHalfMockedData._2, backwardRescanFollowedHalfMockedData._3, backwardRescanFollowedHalfMockedData._4) setReturnValue_followWithdrawal(withdrawWithHopMockedData._1, withdrawWithHopMockedData._2, withdrawWithHopMockedData._3, withdrawWithHopMockedData._4) setReturnValue_followHopMix(followHopMix._1, followHopMix._2, followHopMix._3, followHopMix._4, followHopMix._5) + setReturnValue_followHopMix(backwardRescanFollowedHopMockedData._1, backwardRescanFollowedHopMockedData._2, backwardRescanFollowedHopMockedData._3, backwardRescanFollowedHopMockedData._4, backwardRescanFollowedHopMockedData._5) } /** @@ -39,6 +46,18 @@ class MockedMixScanner extends MockitoSugar { when(mixScanner.followFullMix(boxId, round, masterKey)) .thenReturn(followedMixes) + /** + * specify what to return when followHalfMix of mock class called + * + * @param boxId box ID + * @param round box mix round + * @param masterKey master secret key + * @param followedMixes recovered mix rounds returned by method + */ + def setReturnValue_followHalfMix(boxId: String, round: Int, masterKey: BigInt, followedMixes: Seq[FollowedMix]): Unit = + when(mixScanner.followHalfMix(boxId, round, masterKey)) + .thenReturn(followedMixes) + /** * specify what to return when followHopMix of mock class called * @@ -64,6 +83,6 @@ class MockedMixScanner extends MockitoSugar { when(mixScanner.followWithdrawal(boxId, masterKey)) .thenReturn((followedHops, followedWithdraw)) - setTestCases + setTestCases() } diff --git a/mixer/test/mocked/MockedNetworkUtils.scala b/mixer/test/mocked/MockedNetworkUtils.scala index a42d33c..37ee3bf 100644 --- a/mixer/test/mocked/MockedNetworkUtils.scala +++ b/mixer/test/mocked/MockedNetworkUtils.scala @@ -22,11 +22,8 @@ class MockedNetworkUtils /** * Set the output type of NetworkUtils.UsingClient - * 0: String - * - * @param t Int */ - def setUsingClientReturnType[T]: Unit = { + def setUsingClientReturnType[T](): Unit = { when(networkUtils.usingClient(any())).thenAnswer((invocation: InvocationOnMock) => { val f = invocation.getArgument(0, classOf[BlockchainContext => T]) val ergoClient: FileMockedErgoClient = createMockedErgoClient(MockData(Nil, Nil)) @@ -44,7 +41,7 @@ class MockedNetworkUtils */ def jsonToSignedTx(jsonString: String): SignedTransaction = networkUtils.usingClient { implicit ctx => ctx.signedTxFromJson(jsonString)} - setUsingClientReturnType[String] + setUsingClientReturnType[String]() val tokenErgoMix: Option[TokenErgoMix] = networkUtils.usingClient { implicit ctx => Some(new TokenErgoMix(ctx)) } when(networkUtils.tokenErgoMix).thenReturn(tokenErgoMix) diff --git a/mixer/test/testHandlers/DatasetSuite.scala b/mixer/test/testHandlers/DatasetSuite.scala index ef01df1..ec93b19 100644 --- a/mixer/test/testHandlers/DatasetSuite.scala +++ b/mixer/test/testHandlers/DatasetSuite.scala @@ -3,7 +3,9 @@ package testHandlers import app.Configs import io.circe.parser.parse import io.circe.{Json, parser} +import models.Box.{InBox, OutBox} import models.Models._ +import models.Transaction.SpendTx import org.ergoplatform.appkit.{ErgoToken, SignedTransaction} import scala.collection.mutable @@ -23,7 +25,7 @@ class DatasetSuite { jsonString } - def setConfigData: Unit = { + def setConfigData(): Unit = { val idToParam = mutable.Map.empty[String, EntityInfo] rings.foreach(param => idToParam(param.id) = param) Configs.params = idToParam @@ -107,5 +109,6 @@ class DatasetSuite { TestSignedTransaction(id, inBoxes, outBoxes) } - setConfigData + setConfigData() + } diff --git a/mixer/test/testHandlers/ErgoMixerDataset.scala b/mixer/test/testHandlers/ErgoMixerDataset.scala index 9346d46..5f93a6e 100644 --- a/mixer/test/testHandlers/ErgoMixerDataset.scala +++ b/mixer/test/testHandlers/ErgoMixerDataset.scala @@ -2,7 +2,9 @@ package testHandlers import io.circe.{Json, parser} import models.Models._ -import testHandlers.MixScannerDataset.{jsonToObjectList, readJsonFile} +import models.Transaction._ +import models.Request._ +import models.Box._ import scala.collection.mutable diff --git a/mixer/test/testHandlers/HopMixerDataset.scala b/mixer/test/testHandlers/HopMixerDataset.scala index 0af7768..1f6d54e 100644 --- a/mixer/test/testHandlers/HopMixerDataset.scala +++ b/mixer/test/testHandlers/HopMixerDataset.scala @@ -1,7 +1,9 @@ package testHandlers import mocked.MockedNetworkUtils -import models.Models.{CreateHopMix, CreateMixingRequest, CreateWithdrawTx, HopMix, MixingRequest, WithdrawTx} +import models.Models.{CreateHopMix, HopMix} +import models.Request.{CreateMixingRequest, MixingRequest} +import models.Transaction.{CreateWithdrawTx, WithdrawTx} import org.ergoplatform.appkit.SignedTransaction import wallet.Wallet diff --git a/mixer/test/testHandlers/MixScannerDataset.scala b/mixer/test/testHandlers/MixScannerDataset.scala index 68f90bb..848dc52 100644 --- a/mixer/test/testHandlers/MixScannerDataset.scala +++ b/mixer/test/testHandlers/MixScannerDataset.scala @@ -1,9 +1,12 @@ package testHandlers import mocked.MockedNetworkUtils -import models.Models.MixStatus.Complete -import models.Models.MixWithdrawStatus.UnderHop +import models.Status.MixStatus.Complete +import models.Status.MixWithdrawStatus.UnderHop import models.Models._ +import models.Transaction._ +import models.Request._ +import models.Rescan._ /** * Dataset of test values for classes: @@ -70,6 +73,29 @@ object MixScannerDataset extends DatasetSuite { private val sample16_hopRound = 0 private val sample16_hopBoxType = "hop" + private val sample17_MixingRequest: MixingRequest = CreateMixingRequest(readJsonFile("./test/dataset/Sample17_MixRequest.json")) + private val sample17_mixId = sample17_MixingRequest.id + private val sample17_fullMixList = jsonToObjectList[FullMix](readJsonFile("./test/dataset/Sample17_FullMixList.json"), CreateFullMix.apply) + private val sample17_halfMixList = jsonToObjectList[HalfMix](readJsonFile("./test/dataset/Sample17_HalfMixList.json"), CreateHalfMix.apply) + private val sample17_hopMixList = jsonToObjectList[HopMix](readJsonFile("./test/dataset/Sample17_HopMixList.json"), CreateHopMix.apply) + private val sample17_fullRound = sample17_fullMixList.last.round + private val sample17_halfRound = sample17_halfMixList.last.round + private val sample17_hopRound = sample17_hopMixList.last.round + private val sample17_FullBox = sample17_fullMixList.last.fullMixBoxId + private val sample17_HalfBox = sample17_halfMixList.last.halfMixBoxId + private val sample17_HopBox = sample17_hopMixList.last.boxId + private val sample17_preHalfBox = sample17_halfMixList.head.halfMixBoxId + private val sample17_preFullBox = sample17_fullMixList.head.fullMixBoxId + private val sample17_preHopBox = sample17_hopMixList.head.boxId + private val sample17_masterSecret = sample17_MixingRequest.masterKey + private val sample17_followedMixList = Nil + private val sample17_followedHopList = Nil + private val sample17_followedWithdraw = Option.empty[FollowedWithdraw] + private val sample17_fullResult = sample17_fullMixList.map(mix => (mix.mixId, mix.round, mix.halfMixBoxId, mix.fullMixBoxId)).dropRight(1) + private val sample17_halfResult = sample17_halfMixList.map(mix => (mix.mixId, mix.round, mix.halfMixBoxId)).dropRight(1) + private val sample17_hopResult = sample17_hopMixList.map(mix => (mix.mixId, mix.round, mix.boxId)).dropRight(1) + + /** * apis for testing MixScanner.scanHopMix and MixScanner.followHopMix * spec data: (BigInt, Seq[FollowedHop], Option[FollowedWithdraw]), the mix request master secret and function expected return values @@ -116,4 +142,26 @@ object MixScannerDataset extends DatasetSuite { def rescanHop_resultData = (sample16_hopMixList, sample16_WithdrawTx) + def backwardRescanHop_specData: (String, Int, Boolean, String, String) = (sample17_mixId, sample17_hopRound, true, "hop", sample17_HopBox) + + def backwardRescanFull_specData: (String, Int, Boolean, String, String) = (sample17_mixId, sample17_fullRound, true, "full", sample17_FullBox) + + def backwardRescanHalf_specData: (String, Int, Boolean, String, String) = (sample17_mixId, sample17_halfRound, true, "half", sample17_HalfBox) + + def backwardRescanMix_dbData: (MixingRequest, Seq[FullMix], Seq[HalfMix]) = (sample17_MixingRequest, sample17_fullMixList, sample17_halfMixList) + + def backwardRescanHop_dbData: (MixingRequest, Seq[HopMix]) = (sample17_MixingRequest, sample17_hopMixList) + + def backwardRescan_mockedData = (mockBackwardFollowFullMix, mockBackwardFollowHopMix, mockBackwardFollowHalfMix) + + def mockBackwardFollowFullMix: (String, Int, BigInt, Seq[FollowedMix]) = (sample17_preFullBox, sample17_fullRound - 1, sample17_masterSecret, sample17_followedMixList) + + def mockBackwardFollowHalfMix: (String, Int, BigInt, Seq[FollowedMix]) = (sample17_preHalfBox, sample17_fullRound, sample17_masterSecret, sample17_followedMixList) + + def mockBackwardFollowHopMix: (String, Int, BigInt, Seq[FollowedHop], Option[FollowedWithdraw]) = (sample17_preHopBox, sample17_hopRound - 1, sample17_masterSecret, sample17_followedHopList, sample17_followedWithdraw) + + def backwardRescan_mixResultData = (sample17_fullResult, sample17_halfResult) + + def backwardRescan_hopResultData = sample17_hopResult + } diff --git a/mixer/test/testHandlers/TestSignedTransaction.scala b/mixer/test/testHandlers/TestSignedTransaction.scala index 0501a3f..50b3231 100644 --- a/mixer/test/testHandlers/TestSignedTransaction.scala +++ b/mixer/test/testHandlers/TestSignedTransaction.scala @@ -1,6 +1,6 @@ package testHandlers -import models.Models.{InBox, OutBox} +import models.Box.{InBox, OutBox} import org.ergoplatform.appkit.{ErgoId, InputBox, SignedInput, SignedTransaction} import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar diff --git a/mixer/test/testHandlers/WithdrawMixerDataset.scala b/mixer/test/testHandlers/WithdrawMixerDataset.scala index d8153c9..d8ed78b 100644 --- a/mixer/test/testHandlers/WithdrawMixerDataset.scala +++ b/mixer/test/testHandlers/WithdrawMixerDataset.scala @@ -1,7 +1,9 @@ package testHandlers import mocked.MockedNetworkUtils -import models.Models.{CreateHopMix, CreateMixGroupRequest, CreateMixingRequest, CreateWithdrawTx, HopMix, MixGroupRequest, MixingRequest, WithdrawTx} +import models.Models.{CreateHopMix, HopMix} +import models.Request._ +import models.Transaction.{CreateWithdrawTx, WithdrawTx} import org.ergoplatform.appkit.SignedTransaction /**