From 0fc9ca887d4c07701d7458ad51dfbff232858473 Mon Sep 17 00:00:00 2001 From: catena Date: Tue, 2 Jul 2019 13:48:22 +0300 Subject: [PATCH 01/27] Only inculde transactions with lower complexity --- src/main/resources/reference.conf | 3 +++ .../scala/org/ergoplatform/local/ErgoMiner.scala | 7 +++++-- .../nodeView/state/UtxoStateReader.scala | 14 +++++++++++--- .../settings/NodeConfigurationSettings.scala | 2 ++ .../ergoplatform/mining/ErgoMinerPropSpec.scala | 6 +++--- .../nodeView/state/UtxoStateSpecification.scala | 2 +- 6 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index e3f858d912..2983a4a9bf 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -26,6 +26,9 @@ ergo { # Is the node is doing mining mining = false + # Node will only include transactions with lower scipt complexity to own blocks + maxScriptComplexity = 100 + # Use external miner, native miner is used if set to `false` useExternalMiner = true diff --git a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala index 40761d16fa..89795b140b 100644 --- a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala @@ -60,6 +60,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, private val protocolVersion = ergoSettings.chainSettings.protocolVersion private val powScheme = ergoSettings.chainSettings.powScheme private val externalMinerMode = ergoSettings.nodeSettings.useExternalMiner + private val maxScriptComplexity: Int = ergoSettings.nodeSettings.maxScriptComplexity // shared mutable state private var isMining = false @@ -317,13 +318,14 @@ class ErgoMiner(ergoSettings: ErgoSettings, //only transactions valid from against the current utxo state we take from the mem pool val emissionTxOpt = ErgoMiner.collectEmission(state, minerPk, ergoSettings.chainSettings.emissionRules).map { tx => implicit val verifier: ErgoInterpreter = ErgoInterpreter(state.stateContext.currentParameters) - val cost = state.validateWithCost(tx, Some(upcomingContext)).get + val cost = state.validateWithCost(tx, Some(upcomingContext), Int.MaxValue).get tx -> cost } val (txs, toEliminate) = ErgoMiner.collectTxs(minerPk, state.stateContext.currentParameters.maxBlockCost, state.stateContext.currentParameters.maxBlockSize, + maxScriptComplexity, state, upcomingContext, pool.getAllPrioritized, @@ -422,6 +424,7 @@ object ErgoMiner extends ScorexLogging { def collectTxs(minerPk: ProveDlog, maxBlockCost: Long, maxBlockSize: Long, + maxScriptComplexity: Int, us: UtxoStateReader, upcomingContext: ErgoStateContext, mempoolTxsIn: Iterable[ErgoTransaction], @@ -444,7 +447,7 @@ object ErgoMiner extends ScorexLogging { case Some(tx) => implicit val verifier: ErgoInterpreter = ErgoInterpreter(us.stateContext.currentParameters) // check validity and calculate transaction cost - us.validateWithCost(tx, Some(upcomingContext)) match { + us.validateWithCost(tx, Some(upcomingContext), maxScriptComplexity) match { case Success(costConsumed) => val newTxs = fixTxsConflicts((tx, costConsumed) +: acc) val newBoxes = newTxs.flatMap(_._1.outputs) diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala index 35b8f363db..9c94094519 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala @@ -30,12 +30,20 @@ trait UtxoStateReader extends ErgoStateReader with TransactionValidation[ErgoTra * Validate transaction provided state context if specified * or state context from the previous block if not */ - def validateWithCost(tx: ErgoTransaction, stateContextOpt: Option[ErgoStateContext]): Try[Long] = { + def validateWithCost(tx: ErgoTransaction, + stateContextOpt: Option[ErgoStateContext], + complexityLimit: Int): Try[Long] = { val verifier = ErgoInterpreter(stateContext.currentParameters) val context = stateContextOpt.getOrElse(stateContext) tx.statelessValidity.flatMap { _ => + val boxesToSpend = tx.inputs.flatMap(i => boxById(i.boxId)) + boxesToSpend.foreach { b => + if (b.ergoTree.complexity > complexityLimit) { + throw new Exception(s"Box ${Algos.encode(b.id)} have too complex script: ${b.ergoTree.complexity}") + } + } tx.statefulValidity( - tx.inputs.flatMap(i => boxById(i.boxId)), + boxesToSpend, tx.dataInputs.flatMap(i => boxById(i.boxId)), context)(verifier) } @@ -47,7 +55,7 @@ trait UtxoStateReader extends ErgoStateReader with TransactionValidation[ErgoTra * as soon as state (both UTXO set and state context) will change. * */ - override def validate(tx: ErgoTransaction): Try[Unit] = validateWithCost(tx, None).map(_ => Unit) + override def validate(tx: ErgoTransaction): Try[Unit] = validateWithCost(tx, None, Int.MaxValue).map(_ => Unit) /** * diff --git a/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala b/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala index 009384be62..8e03de256f 100644 --- a/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala @@ -17,6 +17,7 @@ case class NodeConfigurationSettings(stateType: StateType, poPoWBootstrap: Boolean, minimalSuffix: Int, mining: Boolean, + maxScriptComplexity: Int, miningDelay: FiniteDuration, useExternalMiner: Boolean, miningPubKeyHex: Option[String], @@ -39,6 +40,7 @@ trait NodeConfigurationReaders extends StateTypeReaders with ModifierIdReader { cfg.as[Boolean](s"$path.PoPoWBootstrap"), cfg.as[Int](s"$path.minimalSuffix"), cfg.as[Boolean](s"$path.mining"), + cfg.as[Int](s"$path.maxScriptComplexity"), cfg.as[FiniteDuration](s"$path.miningDelay"), cfg.as[Boolean](s"$path.useExternalMiner"), cfg.as[Option[String]](s"$path.miningPubKeyHex"), diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerPropSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerPropSpec.scala index 0b1ad2ccd4..b19954a8fd 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerPropSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerPropSpec.scala @@ -86,15 +86,15 @@ class ErgoMinerPropSpec extends ErgoPropertyTest { val upcomingContext = us.stateContext.upcoming(h.minerPk, h.timestamp, h.nBits, h.votes, emptyVSUpdate, h.version) upcomingContext.currentHeight shouldBe (us.stateContext.currentHeight + 1) - val fromSmallMempool = ErgoMiner.collectTxs(defaultMinerPk, maxCost, maxSize, us, upcomingContext, Seq(head), Seq())(validationSettingsNoIl)._1 + val fromSmallMempool = ErgoMiner.collectTxs(defaultMinerPk, maxCost, maxSize, Int.MaxValue, us, upcomingContext, Seq(head), Seq())(validationSettingsNoIl)._1 fromSmallMempool.size shouldBe 2 fromSmallMempool.contains(head) shouldBe true - val fromBigMempool = ErgoMiner.collectTxs(defaultMinerPk, maxCost, maxSize, us, upcomingContext, txsWithFees, Seq())(validationSettingsNoIl)._1 + val fromBigMempool = ErgoMiner.collectTxs(defaultMinerPk, maxCost, maxSize, Int.MaxValue, us, upcomingContext, txsWithFees, Seq())(validationSettingsNoIl)._1 val newBoxes = fromBigMempool.flatMap(_.outputs) val costs: Seq[Long] = fromBigMempool.map { tx => - us.validateWithCost(tx, Some(upcomingContext)).getOrElse { + us.validateWithCost(tx, Some(upcomingContext), Int.MaxValue).getOrElse { val boxesToSpend = tx.inputs.map(i => newBoxes.find(b => b.id sameElements i.boxId).get) tx.statefulValidity(boxesToSpend, IndexedSeq(), upcomingContext).get } diff --git a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala index 3e6fdfd4f2..4f3cdcfae4 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala @@ -44,7 +44,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera val newBoxes = IndexedSeq(newFoundersBox, rewardBox) val unsignedTx = new UnsignedErgoTransaction(inputs, IndexedSeq(), newBoxes) val tx: ErgoTransaction = ErgoTransaction(defaultProver.sign(unsignedTx, IndexedSeq(foundersBox), emptyDataBoxes, us.stateContext).get) - us.validateWithCost(tx, None).get should be <= 100000L + us.validateWithCost(tx, None, Int.MaxValue).get should be <= 100000L val block1 = validFullBlock(Some(lastBlock), us, Seq(ErgoTransaction(tx))) us = us.applyModifier(block1).get foundersBox = tx.outputs.head From 75f6d85567c6f5f4d64ecc8e536f9d1eb90adca4 Mon Sep 17 00:00:00 2001 From: catena Date: Tue, 2 Jul 2019 14:06:22 +0300 Subject: [PATCH 02/27] compile tests --- .../ergoplatform/settings/ErgoSettingsSpecification.scala | 6 +++--- src/test/scala/org/ergoplatform/tools/ChainGenerator.scala | 2 +- .../scala/org/ergoplatform/utils/HistoryTestHelpers.scala | 2 +- src/test/scala/org/ergoplatform/utils/Stubs.scala | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala index c2c2d8a52e..66d84b95ed 100644 --- a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala +++ b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala @@ -15,7 +15,7 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { property("should read default settings") { val settings = ErgoSettings.read() settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 1000, - poPoWBootstrap = false, 10, mining = false, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, 100, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, keepVersions = 200, mempoolCapacity = 100000, blacklistCapacity = 100000, mempoolCleanupDuration = 10.seconds, minimalFeeAmount = 0) } @@ -23,14 +23,14 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { property("should read user settings from json file") { val settings = ErgoSettings.read(Args(Some("src/test/resources/settings.json"), None)) settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 12, - poPoWBootstrap = false, 10, mining = false, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, 100, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 10.seconds, minimalFeeAmount = 0) } property("should read user settings from HOCON file") { val settings = ErgoSettings.read(Args(Some("src/test/resources/settings.conf"), None)) settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 13, - poPoWBootstrap = false, 10, mining = false, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, 100, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 10.seconds, minimalFeeAmount = 0) } diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index 6c5a6bd51d..65ba1697e8 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -60,7 +60,7 @@ object ChainGenerator extends TestKit(ActorSystem()) with App with ErgoTestHelpe val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, - -1, poPoWBootstrap = false, minimalSuffix, mining = false, miningDelay, useExternalMiner = false, + -1, poPoWBootstrap = false, minimalSuffix, mining = false, 100, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val ms = settings.chainSettings.monetary.copy( minerRewardDelay = RewardDelay diff --git a/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala b/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala index f733a9e674..37dc065554 100644 --- a/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala +++ b/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala @@ -42,7 +42,7 @@ trait HistoryTestHelpers extends ErgoPropertyTest { val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(stateType, verifyTransactions, blocksToKeep, - PoPoWBootstrap, minimalSuffix, mining = false, miningDelay, useExternalMiner = false, miningPubKeyHex = None, + PoPoWBootstrap, minimalSuffix, mining = false, 100, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val scorexSettings: ScorexSettings = null val testingSettings: TestingSettings = null diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 8d33efda32..11914d68b3 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -251,7 +251,7 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(stateType, verifyTransactions, blocksToKeep, - PoPoWBootstrap, minimalSuffix, mining = false, miningDelay, useExternalMiner = false, miningPubKeyHex = None, + PoPoWBootstrap, minimalSuffix, mining = false, 100, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val scorexSettings: ScorexSettings = null val testingSettings: TestingSettings = null From 93783f2a7da1f5dae92c38b597fbc62691db5bee Mon Sep 17 00:00:00 2001 From: oskin1 Date: Tue, 16 Jul 2019 22:07:25 +0300 Subject: [PATCH 03/27] Inputs specification in wallet API allowed. --- .../org/ergoplatform/api/WalletApiRoute.scala | 30 +-- .../local/TransactionGenerator.scala | 8 +- .../nodeView/wallet/ErgoWalletActor.scala | 189 ++++++++++-------- .../nodeView/wallet/ErgoWalletReader.scala | 5 +- .../wallet/requests/PaymentRequest.scala | 6 +- .../wallet/requests/RequestsHolder.scala | 18 +- .../api/routes/WalletApiRouteSpec.scala | 12 +- .../nodeView/wallet/ErgoWalletSpec.scala | 16 +- .../serialization/JsonSerializationSpec.scala | 2 +- .../scala/org/ergoplatform/utils/Stubs.scala | 2 +- .../utils/generators/WalletGenerators.scala | 4 +- 11 files changed, 165 insertions(+), 127 deletions(-) diff --git a/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala index 4e6ecdf536..ddaa3bb1b1 100644 --- a/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala +++ b/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala @@ -155,7 +155,7 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e private def withFee(requests: Seq[TransactionRequest], feeOpt: Option[Long]): Seq[TransactionRequest] = { requests :+ PaymentRequest(Pay2SAddress(ergoSettings.chainSettings.monetary.feeProposition), - feeOpt.getOrElse(ergoSettings.walletSettings.defaultTransactionFee), None, None) + feeOpt.getOrElse(ergoSettings.walletSettings.defaultTransactionFee), Seq.empty, Map.empty) } private def withWalletOp[T](op: ErgoWalletReader => Future[T])(toRoute: T => Route): Route = { @@ -166,15 +166,15 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e withWalletOp(op)(ApiResponse.apply[T]) } - private def generateTransaction(requests: Seq[TransactionRequest]): Route = { - withWalletOp(_.generateTransaction(requests)) { + private def generateTransaction(requests: Seq[TransactionRequest], inputsRaw: Seq[String]): Route = { + withWalletOp(_.generateTransaction(requests, inputsRaw)) { case Failure(e) => BadRequest(s"Bad request $requests. ${Option(e.getMessage).getOrElse(e.toString)}") case Success(tx) => ApiResponse(tx) } } - private def sendTransaction(requests: Seq[TransactionRequest]): Route = { - withWalletOp(_.generateTransaction(requests)) { + private def sendTransaction(requests: Seq[TransactionRequest], inputsRaw: Seq[String]): Route = { + withWalletOp(_.generateTransaction(requests, inputsRaw)) { case Failure(e) => BadRequest(s"Bad request $requests. ${Option(e.getMessage).getOrElse(e.toString)}") case Success(tx) => nodeViewActorRef ! LocallyGeneratedTransaction[ErgoTransaction](tx) @@ -183,29 +183,29 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e } def sendTransactionR: Route = (path("transaction" / "send") & post - & entity(as[RequestsHolder]))(holder => sendTransaction(holder.requestsWithFee)) + & entity(as[RequestsHolder]))(holder => sendTransaction(holder.withFee, holder.inputsRaw)) def generateTransactionR: Route = (path("transaction" / "generate") & post - & entity(as[RequestsHolder]))(holder => generateTransaction(holder.requestsWithFee)) + & entity(as[RequestsHolder]))(holder => generateTransaction(holder.withFee, holder.inputsRaw)) def generatePaymentTransactionR: Route = (path( "payment" / "generate") & post - & entity(as[Seq[PaymentRequest]]) & fee) { (requests, feeOpt) => - generateTransaction(withFee(requests, feeOpt)) + & entity(as[RequestsHolder])) { holder => + generateTransaction(holder.withFee, holder.inputsRaw) } def sendPaymentTransactionR: Route = (path("payment" / "send") & post - & entity(as[Seq[PaymentRequest]]) & fee) { (requests, feeOpt) => - sendTransaction(withFee(requests, feeOpt)) + & entity(as[RequestsHolder])) { holder => + sendTransaction(holder.withFee, holder.inputsRaw) } def generateAssetIssueTransactionR: Route = (path("assets" / "generate") & post - & entity(as[Seq[AssetIssueRequest]]) & fee) { (requests, feeOpt) => - generateTransaction(withFee(requests, feeOpt)) + & entity(as[RequestsHolder])) { holder => + generateTransaction(holder.withFee, holder.inputsRaw) } def sendAssetIssueTransactionR: Route = (path("assets" / "issue") & post - & entity(as[Seq[AssetIssueRequest]]) & fee) { (requests, feeOpt) => - sendTransaction(withFee(requests, feeOpt)) + & entity(as[RequestsHolder])) { holder => + sendTransaction(holder.withFee, holder.inputsRaw) } def balancesR: Route = (path("balances") & get) { diff --git a/src/main/scala/org/ergoplatform/local/TransactionGenerator.scala b/src/main/scala/org/ergoplatform/local/TransactionGenerator.scala index 7bae4db98e..2c8b453a81 100644 --- a/src/main/scala/org/ergoplatform/local/TransactionGenerator.scala +++ b/src/main/scala/org/ergoplatform/local/TransactionGenerator.scala @@ -87,24 +87,24 @@ class TransactionGenerator(viewHolder: ActorRef, val approximateBoxSize = 200 val minimalErgoAmount = approximateBoxSize * (parameters.minValuePerByte + Parameters.MinValueStep) val feeAmount = Math.max(minimalErgoAmount, settings.nodeSettings.minimalFeeAmount) - val feeReq = PaymentRequest(Pay2SAddress(feeProp), feeAmount, None, None) + val feeReq = PaymentRequest(Pay2SAddress(feeProp), feeAmount, Seq.empty, Map.empty) val payloadReq: Future[Option[TransactionRequest]] = wallet.confirmedBalances.map { balances => Random.nextInt(100) match { case i if i < 70 => - Some(PaymentRequest(randProposition, math.min(randAmount, balances.balance - feeReq.value), None, None)) + Some(PaymentRequest(randProposition, math.min(randAmount, balances.balance - feeReq.value), Seq.empty, Map.empty)) case i if i < 95 && balances.assetBalances.nonEmpty => val tokenToSpend = balances.assetBalances.toSeq(Random.nextInt(balances.assetBalances.size)) val tokenAmountToSpend = tokenToSpend._2 / 4 val assets = Seq(IdUtils.decodedTokenId(tokenToSpend._1) -> tokenAmountToSpend) - Some(PaymentRequest(randProposition, minimalErgoAmount, Some(assets), None)) + Some(PaymentRequest(randProposition, minimalErgoAmount, assets, Map.empty)) case _ => val assetInfo = genNewAssetInfo Some(AssetIssueRequest(randProposition, assetInfo._1, assetInfo._2, assetInfo._3, assetInfo._4)) } } payloadReq.flatMap { payloadReqOpt => - wallet.generateTransaction(payloadReqOpt.toSeq :+ feeReq) + wallet.generateTransaction(payloadReqOpt.toSeq :+ feeReq, Seq.empty) } } diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index b8ff550a04..6f56053d05 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -4,10 +4,11 @@ import java.io.File import java.util import akka.actor.{Actor, ActorRef} +import cats.Traverse import org.ergoplatform.ErgoBox._ import org.ergoplatform._ import org.ergoplatform.modifiers.ErgoFullBlock -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} +import org.ergoplatform.modifiers.mempool.{ErgoBoxSerializer, ErgoTransaction, UnsignedErgoTransaction} import org.ergoplatform.nodeView.ErgoContext import org.ergoplatform.nodeView.state.{ErgoStateContext, ErgoStateReader} import org.ergoplatform.nodeView.wallet.persistence._ @@ -23,6 +24,7 @@ import scorex.core.VersionTag import scorex.core.network.NodeViewSynchronizer.ReceivableMessages.ChangedState import scorex.core.utils.ScorexEncoding import scorex.crypto.hash.Digest32 +import scorex.util.encode.Base16 import scorex.util.{ModifierId, ScorexLogging, bytesToId, idToBytes} import sigmastate.Values.{ByteArrayConstant, IntConstant} import sigmastate.eval.Extensions._ @@ -37,6 +39,8 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) with ScorexLogging with ScorexEncoding { + import cats.implicits._ + import ErgoWalletActor._ import IdUtils._ @@ -222,8 +226,8 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) case WatchFor(address) => storage.addTrackedAddress(address) - case GenerateTransaction(requests) => - sender() ! generateTransactionWithOutputs(requests) + case GenerateTransaction(requests, inputsRaw) => + sender() ! generateTransactionWithOutputs(requests, inputsRaw) case DeriveKey(encodedPath) => withWalletLockHandler(sender()) { @@ -301,90 +305,119 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) */ private def extractAllInputs(tx: ErgoTransaction): Seq[EncodedBoxId] = tx.inputs.map(x => encodedBoxId(x.boxId)) - private def requestsToBoxCandidates(requests: Seq[TransactionRequest]): Try[Seq[ErgoBoxCandidate]] = Try { - requests.map { - case PaymentRequest(address, value, assets, registers) => - new ErgoBoxCandidate(value, address.script, height, assets.getOrElse(Seq.empty).toColl, registers.getOrElse(Map.empty)) - case AssetIssueRequest(addressOpt, amount, name, description, decimals) => - val firstInput = inputsFor( - requests - .collect { case pr: PaymentRequest => pr.value } - .sum - ).headOption.getOrElse(throw new Exception("Can't issue asset with no inputs")) - val assetId = Digest32 !@@ firstInput.id - val nonMandatoryRegisters = scala.Predef.Map( - R4 -> ByteArrayConstant(name.getBytes("UTF-8")), - R5 -> ByteArrayConstant(description.getBytes("UTF-8")), - R6 -> IntConstant(decimals) - ) - val lockWithAddress = (addressOpt orElse publicKeys.headOption) - .getOrElse(throw new Exception("No address available for box locking")) - val minimalErgoAmount = - BoxUtils.minimalErgoAmountSimulated(lockWithAddress.script, Colls.fromItems(assetId -> amount), nonMandatoryRegisters, parameters) - new ErgoBoxCandidate(minimalErgoAmount, lockWithAddress.script, height, Colls.fromItems(assetId -> amount), nonMandatoryRegisters) - case other => throw new Exception(s"Unknown TransactionRequest type: $other") + private def requestsToBoxCandidates(requests: Seq[TransactionRequest]): Try[Seq[ErgoBoxCandidate]] = + Traverse[List].sequence { + requests.toList + .map { + case PaymentRequest(address, value, assets, registers) => + Success(new ErgoBoxCandidate(value, address.script, height, assets.toColl, registers)) + case AssetIssueRequest(addressOpt, amount, name, description, decimals) => + val firstInputOpt = inputsFor( + requests + .collect { case pr: PaymentRequest => pr.value } + .sum + ).headOption + firstInputOpt + .fold[Try[ErgoBox]](Failure(new Exception("Can't issue asset with no inputs")))(Success(_)) + .flatMap { firstInput => + val assetId = Digest32 !@@ firstInput.id + val nonMandatoryRegisters = scala.Predef.Map( + R4 -> ByteArrayConstant(name.getBytes("UTF-8")), + R5 -> ByteArrayConstant(description.getBytes("UTF-8")), + R6 -> IntConstant(decimals) + ) + (addressOpt orElse publicKeys.headOption) + .fold[Try[ErgoAddress]](Failure(new Exception("No address available for box locking")))(Success(_)) + .map { lockWithAddress => + val minimalErgoAmount = + BoxUtils.minimalErgoAmountSimulated( + lockWithAddress.script, + Colls.fromItems(assetId -> amount), + nonMandatoryRegisters, + parameters + ) + new ErgoBoxCandidate( + minimalErgoAmount, + lockWithAddress.script, + height, + Colls.fromItems(assetId -> amount), + nonMandatoryRegisters + ) + } + } + case other => + Failure(new Exception(s"Unknown TransactionRequest type: $other")) + } } - } /** * Generates new transaction according to a given requests using available boxes. */ - private def generateTransactionWithOutputs(requests: Seq[TransactionRequest]): Try[ErgoTransaction] = + private def generateTransactionWithOutputs(requests: Seq[TransactionRequest], + inputsRaw: Seq[String]): Try[ErgoTransaction] = proverOpt match { case Some(prover) => - requestsToBoxCandidates(requests).flatMap { payTo => - require(prover.pubKeys.nonEmpty, "No public keys in the prover to extract change address from") - require(requests.count(_.isInstanceOf[AssetIssueRequest]) <= 1, "Too many asset issue requests") - require(payTo.forall(c => c.value >= BoxUtils.minimalErgoAmountSimulated(c, parameters)), "Minimal ERG value not met") - require(payTo.forall(_.additionalTokens.forall(_._2 >= 0)), "Negative asset value") - - val assetIssueBox = payTo - .zip(requests) - .filter(_._2.isInstanceOf[AssetIssueRequest]) - .map(_._1) - .headOption - - val targetBalance = payTo - .map(_.value) - .sum - - val targetAssets = payTo - .filterNot(bx => assetIssueBox.contains(bx)) - .foldLeft(Map.empty[ModifierId, Long]) { case (acc, bx) => - // TODO optimize: avoid toArray and use mapFirst - val boxTokens = bx.additionalTokens.toArray.map(t => bytesToId(t._1) -> t._2).toMap - AssetUtils.mergeAssets(boxTokens, acc) - } - - boxSelector.select( - registry.readCertainUnspentBoxes.toIterator, onChainFilter, targetBalance, targetAssets).map { r => - val inputs = r.boxes.toIndexedSeq - - val changeAddress = storage.readChangeAddress - .map(_.pubkey) - .getOrElse { - log.warn("Change address not specified. Using random address from wallet.") - prover.pubKeys(Random.nextInt(prover.pubKeys.size)) + Traverse[List] + .sequence { + inputsRaw + .toList + .map(in => Base16.decode(in).flatMap(ErgoBoxSerializer.parseBytesTry)) + } + .flatMap { inputs => + requestsToBoxCandidates(requests).flatMap { payTo => + require(prover.pubKeys.nonEmpty, "No public keys in the prover to extract change address from") + require(requests.count(_.isInstanceOf[AssetIssueRequest]) <= 1, "Too many asset issue requests") + require(payTo.forall(c => c.value >= BoxUtils.minimalErgoAmountSimulated(c, parameters)), "Minimal ERG value not met") + require(payTo.forall(_.additionalTokens.forall(_._2 >= 0)), "Negative asset value") + + val assetIssueBox = payTo + .zip(requests) + .filter(_._2.isInstanceOf[AssetIssueRequest]) + .map(_._1) + .headOption + + val targetBalance = payTo + .map(_.value) + .sum + + val targetAssets = payTo + .filterNot(bx => assetIssueBox.contains(bx)) + .foldLeft(Map.empty[ModifierId, Long]) { case (acc, bx) => + // TODO optimize: avoid toArray and use mapFirst + val boxTokens = bx.additionalTokens.toArray.map(t => bytesToId(t._1) -> t._2).toMap + AssetUtils.mergeAssets(boxTokens, acc) + } + + boxSelector.select( + registry.readCertainUnspentBoxes.toIterator, onChainFilter, targetBalance, targetAssets).map { r => + val inputs = r.boxes.toIndexedSeq + + val changeAddress = storage.readChangeAddress + .map(_.pubkey) + .getOrElse { + log.warn("Change address not specified. Using random address from wallet.") + prover.pubKeys(Random.nextInt(prover.pubKeys.size)) + } + + val changeBoxCandidates = r.changeBoxes.map { case (ergChange, tokensChange) => + val assets = tokensChange.map(t => Digest32 @@ idToBytes(t._1) -> t._2).toIndexedSeq + new ErgoBoxCandidate(ergChange, changeAddress, height, assets.toColl) + } + + val unsignedTx = new UnsignedErgoTransaction( + inputs.map(_.id).map(id => new UnsignedInput(id)), + IndexedSeq(), + (payTo ++ changeBoxCandidates).toIndexedSeq + ) + + prover.sign(unsignedTx, inputs, IndexedSeq(), stateContext) + .fold(e => Failure(new Exception(s"Failed to sign boxes: $inputs", e)), tx => Success(tx)) + } match { + case Some(txTry) => txTry.map(ErgoTransaction.apply) + case None => Failure(new Exception(s"No enough boxes to assemble a transaction for $payTo")) } - - val changeBoxCandidates = r.changeBoxes.map { case (ergChange, tokensChange) => - val assets = tokensChange.map(t => Digest32 @@ idToBytes(t._1) -> t._2).toIndexedSeq - new ErgoBoxCandidate(ergChange, changeAddress, height, assets.toColl) } - - val unsignedTx = new UnsignedErgoTransaction( - inputs.map(_.id).map(id => new UnsignedInput(id)), - IndexedSeq(), - (payTo ++ changeBoxCandidates).toIndexedSeq - ) - - prover.sign(unsignedTx, inputs, IndexedSeq(), stateContext) - .fold(e => Failure(new Exception(s"Failed to sign boxes: $inputs", e)), tx => Success(tx)) - } match { - case Some(txTry) => txTry.map(ErgoTransaction.apply) - case None => Failure(new Exception(s"No enough boxes to assemble a transaction for $payTo")) } - } case None => Failure(new Exception("Wallet is locked")) @@ -526,7 +559,7 @@ object ErgoWalletActor { final case class Rollback(version: VersionTag, height: Int) - final case class GenerateTransaction(requests: Seq[TransactionRequest]) + final case class GenerateTransaction(requests: Seq[TransactionRequest], inputsRaw: Seq[String]) final case class ReadBalances(chainStatus: ChainStatus) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala index fb2269ac71..119936b2d0 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala @@ -75,7 +75,8 @@ trait ErgoWalletReader extends VaultReader { def trackedAddresses: Future[Seq[ErgoAddress]] = (walletActor ? ReadTrackedAddresses).mapTo[Seq[ErgoAddress]] - def generateTransaction(requests: Seq[TransactionRequest]): Future[Try[ErgoTransaction]] = - (walletActor ? GenerateTransaction(requests)).mapTo[Try[ErgoTransaction]] + def generateTransaction(requests: Seq[TransactionRequest], + inputsRaw: Seq[String] = Seq.empty): Future[Try[ErgoTransaction]] = + (walletActor ? GenerateTransaction(requests, inputsRaw)).mapTo[Try[ErgoTransaction]] } diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/requests/PaymentRequest.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/PaymentRequest.scala index d6e077e46e..5e5e156fa8 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/requests/PaymentRequest.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/PaymentRequest.scala @@ -15,8 +15,8 @@ import sigmastate.Values.EvaluatedValue */ case class PaymentRequest(address: ErgoAddress, value: Long, - assets: Option[Seq[(ErgoBox.TokenId, Long)]], - registers: Option[Map[NonMandatoryRegisterId, EvaluatedValue[_ <: SType]]]) + assets: Seq[(ErgoBox.TokenId, Long)], + registers: Map[NonMandatoryRegisterId, EvaluatedValue[_ <: SType]]) extends TransactionRequest class PaymentRequestEncoder(settings: ErgoSettings) extends Encoder[PaymentRequest] { @@ -46,7 +46,7 @@ class PaymentRequestDecoder(settings: ErgoSettings) extends Decoder[PaymentReque value <- cursor.downField("value").as[Long] assets <- cursor.downField("assets").as[Option[Seq[(ErgoBox.TokenId, Long)]]] registers <- cursor.downField("registers").as[Option[Map[NonMandatoryRegisterId, EvaluatedValue[SType]]]] - } yield PaymentRequest(address, value, assets, registers) + } yield PaymentRequest(address, value, assets.toSeq.flatten, registers.getOrElse(Map.empty)) } } diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/requests/RequestsHolder.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/RequestsHolder.scala index 5be0c86212..cfc1fecd05 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/requests/RequestsHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/requests/RequestsHolder.scala @@ -7,13 +7,15 @@ import org.ergoplatform.nodeView.wallet.ErgoAddressJsonEncoder import org.ergoplatform.settings.ErgoSettings import org.ergoplatform.{ErgoAddress, ErgoAddressEncoder, ErgoScriptPredef, Pay2SAddress} -case class RequestsHolder(requests: Seq[TransactionRequest], fee: Long) +case class RequestsHolder(requests: Seq[TransactionRequest], + feeOpt: Option[Long] = None, + inputsRaw: Seq[String] = Seq.empty) (implicit val addressEncoder: ErgoAddressEncoder) { // Add separate payment request with fee. - def requestsWithFee: Seq[TransactionRequest] = { - requests :+ PaymentRequest(Pay2SAddress(ErgoScriptPredef.feeProposition()), fee, None, None) - } + def withFee: Seq[TransactionRequest] = requests ++ feeOpt + .map(PaymentRequest(Pay2SAddress(ErgoScriptPredef.feeProposition()), _, Seq.empty, Map.empty)) + .toSeq } @@ -25,7 +27,8 @@ class RequestsHolderEncoder(settings: ErgoSettings) extends Encoder[RequestsHold def apply(holder: RequestsHolder): Json = { Json.obj( "requests" -> holder.requests.asJson, - "fee" -> holder.fee.asJson + "fee" -> holder.feeOpt.asJson, + "inputsRaw" -> holder.inputsRaw.asJson ) } @@ -39,8 +42,9 @@ class RequestsHolderDecoder(settings: ErgoSettings) extends Decoder[RequestsHold def apply(cursor: HCursor): Decoder.Result[RequestsHolder] = { for { requests <- cursor.downField("requests").as[Seq[TransactionRequest]] - fee <- cursor.downField("fee").as[Long] - } yield RequestsHolder(requests, fee) + fee <- cursor.downField("fee").as[Option[Long]] + inputs <- cursor.downField("inputsRaw").as[Seq[String]] + } yield RequestsHolder(requests, fee, inputs) } } diff --git a/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala b/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala index e6d88630b3..88b6d24c16 100644 --- a/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala @@ -35,9 +35,9 @@ class WalletApiRouteSpec extends FlatSpec implicit val ergoAddressEncoder: ErgoAddressEncoder = new ErgoAddressEncoder(ergoSettings.chainSettings.addressPrefix) implicit val addressJsonDecoder: Decoder[ErgoAddress] = ErgoAddressJsonEncoder(settings).decoder - val paymentRequest = PaymentRequest(Pay2SAddress(Constants.FalseLeaf), 100L, None, None) + val paymentRequest = PaymentRequest(Pay2SAddress(Constants.FalseLeaf), 100L, Seq.empty, Map.empty) val assetIssueRequest = AssetIssueRequest(Pay2SAddress(Constants.FalseLeaf), 100L, "TEST", "Test", 8) - val requestsHolder = RequestsHolder((0 to 10).flatMap(_ => Seq(paymentRequest, assetIssueRequest)), 10000L) + val requestsHolder = RequestsHolder((0 to 10).flatMap(_ => Seq(paymentRequest, assetIssueRequest)), Some(10000L)) val scriptSource: String = """ |{ @@ -81,14 +81,14 @@ class WalletApiRouteSpec extends FlatSpec } it should "generate payment transaction" in { - Post(prefix + "/payment/generate", Seq(paymentRequest).asJson) ~> route ~> check { + Post(prefix + "/payment/generate", RequestsHolder(Seq(paymentRequest)).asJson) ~> route ~> check { status shouldBe StatusCodes.OK Try(responseAs[ErgoTransaction]) shouldBe 'success } } it should "generate asset issue transaction" in { - Post(prefix + "/assets/generate", Seq(assetIssueRequest).asJson) ~> route ~> check { + Post(prefix + "/assets/generate", RequestsHolder(Seq(assetIssueRequest)).asJson) ~> route ~> check { status shouldBe StatusCodes.OK Try(responseAs[ErgoTransaction]) shouldBe 'success } @@ -102,14 +102,14 @@ class WalletApiRouteSpec extends FlatSpec } it should "generate & send payment transaction" in { - Post(prefix + "/payment/send", Seq(paymentRequest).asJson) ~> route ~> check { + Post(prefix + "/payment/send", RequestsHolder(Seq(paymentRequest)).asJson) ~> route ~> check { status shouldBe StatusCodes.OK responseAs[String] should not be empty } } it should "generate & send asset issue transaction" in { - Post(prefix + "/assets/issue", Seq(assetIssueRequest).asJson) ~> route ~> check { + Post(prefix + "/assets/issue", RequestsHolder(Seq(assetIssueRequest)).asJson) ~> route ~> check { status shouldBe StatusCodes.OK responseAs[String] should not be empty } diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala index a5d2ebf51f..24a3b4770b 100644 --- a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala @@ -39,7 +39,7 @@ class ErgoWalletSpec extends PropSpec with WalletTestOps { val tokenDescription: String = s"ERG description" val tokenDecimals: Int = 9 val feeAmount = availableAmount / 4 - val feeReq = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), feeAmount, None, None) + val feeReq = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), feeAmount, Seq.empty, Map.empty) val req = AssetIssueRequest(address, emissionAmount, tokenName, tokenDescription, tokenDecimals) val tx = await(wallet.generateTransaction(Seq(feeReq, req))).get log.info(s"Generated transaction $tx") @@ -65,8 +65,8 @@ class ErgoWalletSpec extends PropSpec with WalletTestOps { val sumToSpend = snap.balance / (addresses.length + 1) val req = - PaymentRequest(addresses.head, sumToSpend, Some(assetsToSpend), None) +: - addresses.tail.map(a => PaymentRequest(a, sumToSpend, None, None)) + PaymentRequest(addresses.head, sumToSpend, assetsToSpend, Map.empty) +: + addresses.tail.map(a => PaymentRequest(a, sumToSpend, Seq.empty, Map.empty)) log.info(s"Confirmed balance $snap") log.info(s"Payment request $req") val tx = await(wallet.generateTransaction(req)).get @@ -80,8 +80,8 @@ class ErgoWalletSpec extends PropSpec with WalletTestOps { waitForScanning(block) val newSnap = getConfirmedBalances val newSumToSpend = newSnap.balance / addresses.length - val req2 = PaymentRequest(addresses.head, newSumToSpend, Some(assetsToSpend), None) +: - addresses.tail.map(a => PaymentRequest(a, newSumToSpend, None, None)) + val req2 = PaymentRequest(addresses.head, newSumToSpend, assetsToSpend, Map.empty) +: + addresses.tail.map(a => PaymentRequest(a, newSumToSpend, Seq.empty, Map.empty)) log.info(s"New balance $newSnap") log.info(s"Payment requests 2 $req2") val tx2 = await(wallet.generateTransaction(req2)).get @@ -597,7 +597,7 @@ class ErgoWalletSpec extends PropSpec with WalletTestOps { //pay out all the wallet balance: val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq assetToSpend should not be empty - val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance, Some(assetToSpend), None) + val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance, assetToSpend, Map.empty) val tx1 = await(wallet.generateTransaction(Seq(req1))).get tx1.outputs.size shouldBe 1 @@ -607,7 +607,7 @@ class ErgoWalletSpec extends PropSpec with WalletTestOps { //change == 1: val assetToSpend2 = assetToSpend.map { case (tokenId, tokenValue) => (tokenId, tokenValue - 1) } val assetToReturn = assetToSpend.map { case (tokenId, _) => (tokenId, 1L) } - val req2 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance - 1, Some(assetToSpend2), None) + val req2 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance - 1, assetToSpend2, Map.empty) val tx2 = await(wallet.generateTransaction(Seq(req2))).get tx2.outputs.size shouldBe 2 @@ -643,7 +643,7 @@ class ErgoWalletSpec extends PropSpec with WalletTestOps { blocking(Thread.sleep(100)) val requestWithTotalAmount = PaymentRequest( - ErgoAddressEncoder(0: Byte).fromProposition(pubKey).get, totalAvailableAmount, None, None) + ErgoAddressEncoder(0: Byte).fromProposition(pubKey).get, totalAvailableAmount, Seq.empty, Map.empty) val requestWithCertainAmount = requestWithTotalAmount.copy(value = certainAmount) val uncertainTxTry = await(wallet.generateTransaction(Seq(requestWithTotalAmount))) diff --git a/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala b/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala index 2828f9f7e7..0fad840aaa 100644 --- a/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala +++ b/src/test/scala/org/ergoplatform/serialization/JsonSerializationSpec.scala @@ -57,7 +57,7 @@ class JsonSerializationSpec extends ErgoPropertyTest with WalletGenerators with restored.address shouldEqual request.address restored.value shouldEqual request.value restored.registers shouldEqual request.registers - Inspectors.forAll(restored.assets.getOrElse(Seq.empty).zip(request.assets.getOrElse(Seq.empty))) { + Inspectors.forAll(restored.assets.zip(request.assets)) { case ((restoredToken, restoredValue), (requestToken, requestValue)) => restoredToken shouldEqual requestToken restoredValue shouldEqual requestValue diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index d0ff67ae78..7958b618a9 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -169,7 +169,7 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with case ReadTrackedAddresses => sender ! trackedAddresses - case GenerateTransaction(_) => + case GenerateTransaction(_, _) => val input = ErgoTransactionGenerators.inputGen.sample.value val tx = ErgoTransaction(IndexedSeq(input), IndexedSeq(ergoBoxCandidateGen.sample.value)) sender ! Success(tx) diff --git a/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala b/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala index 440b8d143b..61a86c0226 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/WalletGenerators.scala @@ -83,8 +83,8 @@ trait WalletGenerators extends ErgoTransactionGenerators { def paymentRequestGen: Gen[PaymentRequest] = { for { value <- Gen.choose(1L, 100000L) - assets <- Gen.option(additionalTokensGen) - registers <- Gen.option(additionalRegistersGen) + assets <- additionalTokensGen + registers <- additionalRegistersGen } yield PaymentRequest(Pay2SAddress(Constants.FalseLeaf), value, assets, registers) } From 47c9b4ab0e8999ace349bf5d98a51855f6614201 Mon Sep 17 00:00:00 2001 From: oskin1 Date: Wed, 17 Jul 2019 12:18:16 +0300 Subject: [PATCH 04/27] User-provided inputs used in selector. --- .../nodeView/wallet/ErgoWalletActor.scala | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index 6f56053d05..09ec92c752 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -388,31 +388,21 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) AssetUtils.mergeAssets(boxTokens, acc) } - boxSelector.select( - registry.readCertainUnspentBoxes.toIterator, onChainFilter, targetBalance, targetAssets).map { r => - val inputs = r.boxes.toIndexedSeq - - val changeAddress = storage.readChangeAddress - .map(_.pubkey) - .getOrElse { - log.warn("Change address not specified. Using random address from wallet.") - prover.pubKeys(Random.nextInt(prover.pubKeys.size)) + val inputBoxes = if (inputs.nonEmpty) { + inputs + .map { box => + TrackedBox(box.transactionId, box.index, None, None, None, box, BoxCertainty.Certain) } + .toIterator + } else { + registry.readCertainUnspentBoxes.toIterator + } - val changeBoxCandidates = r.changeBoxes.map { case (ergChange, tokensChange) => - val assets = tokensChange.map(t => Digest32 @@ idToBytes(t._1) -> t._2).toIndexedSeq - new ErgoBoxCandidate(ergChange, changeAddress, height, assets.toColl) - } + val selectionOpt = boxSelector.select(inputBoxes, onChainFilter, targetBalance, targetAssets) - val unsignedTx = new UnsignedErgoTransaction( - inputs.map(_.id).map(id => new UnsignedInput(id)), - IndexedSeq(), - (payTo ++ changeBoxCandidates).toIndexedSeq - ) + val makeTx = prepareTransaction(prover, payTo) - prover.sign(unsignedTx, inputs, IndexedSeq(), stateContext) - .fold(e => Failure(new Exception(s"Failed to sign boxes: $inputs", e)), tx => Success(tx)) - } match { + selectionOpt.map(makeTx) match { case Some(txTry) => txTry.map(ErgoTransaction.apply) case None => Failure(new Exception(s"No enough boxes to assemble a transaction for $payTo")) } @@ -423,6 +413,32 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) Failure(new Exception("Wallet is locked")) } + private def prepareTransaction(prover: ErgoProvingInterpreter, payTo: Seq[ErgoBoxCandidate]) + (r: BoxSelector.BoxSelectionResult): Try[ErgoLikeTransaction] = { + val inputs = r.boxes.toIndexedSeq + + val changeAddress = storage.readChangeAddress + .map(_.pubkey) + .getOrElse { + log.warn("Change address not specified. Using random address from wallet.") + prover.pubKeys(Random.nextInt(prover.pubKeys.size)) + } + + val changeBoxCandidates = r.changeBoxes.map { case (ergChange, tokensChange) => + val assets = tokensChange.map(t => Digest32 @@ idToBytes(t._1) -> t._2).toIndexedSeq + new ErgoBoxCandidate(ergChange, changeAddress, height, assets.toColl) + } + + val unsignedTx = new UnsignedErgoTransaction( + inputs.map(_.id).map(id => new UnsignedInput(id)), + IndexedSeq(), + (payTo ++ changeBoxCandidates).toIndexedSeq + ) + + prover.sign(unsignedTx, inputs, IndexedSeq(), stateContext) + .fold(e => Failure(new Exception(s"Failed to sign boxes: $inputs", e)), tx => Success(tx)) + } + /** * Updates indexes according to a given wallet-critical data. */ From 67017cea08198a3918e4d8e11fa794ee88256ab7 Mon Sep 17 00:00:00 2001 From: oskin1 Date: Wed, 17 Jul 2019 12:38:34 +0300 Subject: [PATCH 05/27] Curried function application fixed. --- .../org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index 09ec92c752..925bf88d42 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -400,7 +400,7 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) val selectionOpt = boxSelector.select(inputBoxes, onChainFilter, targetBalance, targetAssets) - val makeTx = prepareTransaction(prover, payTo) + val makeTx = prepareTransaction(prover, payTo) _ selectionOpt.map(makeTx) match { case Some(txTry) => txTry.map(ErgoTransaction.apply) From ac284ef82f96dac4d85ef8d5b7e74655f147c379 Mon Sep 17 00:00:00 2001 From: oskin1 Date: Wed, 17 Jul 2019 12:52:13 +0300 Subject: [PATCH 06/27] Effectful code refactoring. --- .../nodeView/wallet/ErgoWalletActor.scala | 68 ++++++++++--------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index 4ed5aa3c13..0468899d87 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -313,42 +313,46 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) Success(new ErgoBoxCandidate(value, address.script, height, assets.toColl, registers)) case AssetIssueRequest(addressOpt, amount, name, description, decimals, registers) => // Check that auxiliary registers do not try to rewrite registers R0...R6 - if (registers.exists(_.forall(_._1.number < 7))) { - throw new Exception("Additional registers contain R0...R6") + val registersCheck = if (registers.exists(_.forall(_._1.number < 7))) { + Failure(new Exception("Additional registers contain R0...R6")) + } else { + Success(()) } - val firstInputOpt = inputsFor( - requests - .collect { case pr: PaymentRequest => pr.value } - .sum - ).headOption - firstInputOpt - .fold[Try[ErgoBox]](Failure(new Exception("Can't issue asset with no inputs")))(Success(_)) - .flatMap { firstInput => - val assetId = Digest32 !@@ firstInput.id - val nonMandatoryRegisters = scala.Predef.Map( - R4 -> ByteArrayConstant(name.getBytes("UTF-8")), - R5 -> ByteArrayConstant(description.getBytes("UTF-8")), - R6 -> IntConstant(decimals) - ) ++ registers.getOrElse(Map()) - (addressOpt orElse publicKeys.headOption) - .fold[Try[ErgoAddress]](Failure(new Exception("No address available for box locking")))(Success(_)) - .map { lockWithAddress => - val minimalErgoAmount = - BoxUtils.minimalErgoAmountSimulated( + registersCheck.flatMap { _ => + val firstInputOpt = inputsFor( + requests + .collect { case pr: PaymentRequest => pr.value } + .sum + ).headOption + firstInputOpt + .fold[Try[ErgoBox]](Failure(new Exception("Can't issue asset with no inputs")))(Success(_)) + .flatMap { firstInput => + val assetId = Digest32 !@@ firstInput.id + val nonMandatoryRegisters = scala.Predef.Map( + R4 -> ByteArrayConstant(name.getBytes("UTF-8")), + R5 -> ByteArrayConstant(description.getBytes("UTF-8")), + R6 -> IntConstant(decimals) + ) ++ registers.getOrElse(Map()) + (addressOpt orElse publicKeys.headOption) + .fold[Try[ErgoAddress]](Failure(new Exception("No address available for box locking")))(Success(_)) + .map { lockWithAddress => + val minimalErgoAmount = + BoxUtils.minimalErgoAmountSimulated( + lockWithAddress.script, + Colls.fromItems(assetId -> amount), + nonMandatoryRegisters, + parameters + ) + new ErgoBoxCandidate( + minimalErgoAmount, lockWithAddress.script, + height, Colls.fromItems(assetId -> amount), - nonMandatoryRegisters, - parameters + nonMandatoryRegisters ) - new ErgoBoxCandidate( - minimalErgoAmount, - lockWithAddress.script, - height, - Colls.fromItems(assetId -> amount), - nonMandatoryRegisters - ) - } - } + } + } + } case other => Failure(new Exception(s"Unknown TransactionRequest type: $other")) } From 85bf3018e3345ef1a1ca1c76c7c4d18062a48b43 Mon Sep 17 00:00:00 2001 From: oskin1 Date: Wed, 17 Jul 2019 13:48:37 +0300 Subject: [PATCH 07/27] Transaction creation with user-specified inputs spec. --- .../nodeView/wallet/ErgoWalletActor.scala | 4 +- .../nodeView/wallet/ErgoWalletSpec.scala | 43 ++++++++++++++++++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index 0468899d87..a32a5c4168 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -398,8 +398,8 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) val inputBoxes = if (inputs.nonEmpty) { inputs - .map { box => - TrackedBox(box.transactionId, box.index, None, None, None, box, BoxCertainty.Certain) + .map { box => // declare fake inclusion height in order to confirm the box is onchain + TrackedBox(box.transactionId, box.index, Some(1), None, None, box, BoxCertainty.Certain) } .toIterator } else { diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala index 24a3b4770b..d27e86e494 100644 --- a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala @@ -1,7 +1,7 @@ package org.ergoplatform.nodeView.wallet import org.ergoplatform._ -import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.modifiers.mempool.{ErgoBoxSerializer, ErgoTransaction} import org.ergoplatform.nodeView.state.{ErgoStateContext, VotingData} import org.ergoplatform.nodeView.wallet.IdUtils._ import org.ergoplatform.nodeView.wallet.persistence.RegistryIndex @@ -12,6 +12,7 @@ import org.ergoplatform.wallet.interpreter.ErgoInterpreter import org.scalatest.PropSpec import scorex.crypto.authds.ADKey import scorex.crypto.hash.Blake2b256 +import scorex.util.encode.Base16 import sigmastate.Values.ByteArrayConstant import sigmastate._ import sigmastate.eval._ @@ -49,6 +50,46 @@ class ErgoWalletSpec extends PropSpec with WalletTestOps { } } + property("Generate transaction with user-defined input") { + withFixture { implicit w => + val pubKey = getPublicKeys.head.pubkey + val genesisBlock = makeGenesisBlock(pubKey, randomNewAsset) + val genesisTx = genesisBlock.transactions.head + val initialBoxes = boxesAvailable(genesisBlock, pubKey) + + val boxesToUseEncoded = initialBoxes.map { box => + Base16.encode(ErgoBoxSerializer.toBytes(box)) + } + + applyBlock(genesisBlock) shouldBe 'success + waitForScanning(genesisBlock) + + val confirmedBalance = getConfirmedBalances.balance + + //pay out all the wallet balance: + val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq + assetToSpend should not be empty + val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance, assetToSpend, Map.empty) + + val tx1 = await(wallet.generateTransaction(Seq(req1), boxesToUseEncoded)).get + tx1.outputs.size shouldBe 1 + tx1.outputs.head.value shouldBe confirmedBalance + toAssetMap(tx1.outputs.head.additionalTokens.toArray) shouldBe toAssetMap(assetToSpend) + + //change == 1: + val assetToSpend2 = assetToSpend.map { case (tokenId, tokenValue) => (tokenId, tokenValue - 1) } + val assetToReturn = assetToSpend.map { case (tokenId, _) => (tokenId, 1L) } + val req2 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance - 1, assetToSpend2, Map.empty) + + val tx2 = await(wallet.generateTransaction(Seq(req2))).get + tx2.outputs.size shouldBe 2 + tx2.outputs.head.value shouldBe confirmedBalance - 1 + toAssetMap(tx2.outputs.head.additionalTokens.toArray) shouldBe toAssetMap(assetToSpend2) + tx2.outputs(1).value shouldBe 1 + toAssetMap(tx2.outputs(1).additionalTokens.toArray) shouldBe toAssetMap(assetToReturn) + } + } + property("Generate transaction with multiple inputs") { withFixture { implicit w => val addresses = getPublicKeys From 21e1464a3236a46ad73f7b6b46b706809870768a Mon Sep 17 00:00:00 2001 From: oskin1 Date: Wed, 17 Jul 2019 16:52:59 +0300 Subject: [PATCH 08/27] Redundant routes removed. --- src/main/resources/api/openapi.yaml | 150 +----------------- .../org/ergoplatform/api/WalletApiRoute.scala | 24 --- .../api/routes/WalletApiRouteSpec.scala | 28 ---- 3 files changed, 5 insertions(+), 197 deletions(-) diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index f454449973..19b064a958 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -254,7 +254,6 @@ components: type: object required: - requests - - fee properties: requests: description: Sequence of transaction requests @@ -268,6 +267,11 @@ components: type: integer format: int64 example: 1000000 + inputsRaw: + description: List of inputs to be used in serialized form + type: array + items: + - $ref: '#/components/schemas/SerializedBox' SourceHolder: type: object @@ -2235,150 +2239,6 @@ paths: schema: $ref: '#/components/schemas/ApiError' - /wallet/payment/generate: - post: - security: - - ApiKeyAuth: [api_key] - summary: Generate payment transaction - operationId: walletPaymentTransactionGenerate - tags: - - wallet - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PaymentRequest' - responses: - '200': - description: Generated Ergo transaction - content: - application/json: - schema: - $ref: '#/components/schemas/ErgoTransaction' - '400': - description: Bad payment request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - - /wallet/payment/send: - post: - security: - - ApiKeyAuth: [api_key] - summary: Generate and send payment transaction - operationId: walletPaymentTransactionGenerateAndSend - tags: - - wallet - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/PaymentRequest' - responses: - '200': - description: Identifier of an Ergo transaction generated - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionId' - '400': - description: Bad payment request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - - /wallet/assets/generate: - post: - security: - - ApiKeyAuth: [api_key] - summary: Generate asset issue transaction - operationId: walletAssetIssueTransactionGenerate - tags: - - wallet - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/AssetIssueRequest' - responses: - '200': - description: Generated Ergo transaction - content: - application/json: - schema: - $ref: '#/components/schemas/ErgoTransaction' - '400': - description: Bad asset issue request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - - /wallet/assets/issue: - post: - security: - - ApiKeyAuth: [api_key] - summary: Generate and send asset issue transaction - operationId: walletAssetIssueTransactionGenerateAndSend - tags: - - wallet - requestBody: - required: true - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/AssetIssueRequest' - responses: - '200': - description: Identifier of an Ergo transaction generated - content: - application/json: - schema: - $ref: '#/components/schemas/TransactionId' - '400': - description: Bad asset issue request - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - default: - description: Error - content: - application/json: - schema: - $ref: '#/components/schemas/ApiError' - /mining/candidate: get: security: diff --git a/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala index ddaa3bb1b1..0778171169 100644 --- a/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala +++ b/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala @@ -48,11 +48,7 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e unspentBoxesR ~ boxesR ~ generateTransactionR ~ - generatePaymentTransactionR ~ - generateAssetIssueTransactionR ~ sendTransactionR ~ - sendPaymentTransactionR ~ - sendAssetIssueTransactionR ~ p2shAddressR ~ p2sAddressR ~ initWalletR ~ @@ -188,26 +184,6 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e def generateTransactionR: Route = (path("transaction" / "generate") & post & entity(as[RequestsHolder]))(holder => generateTransaction(holder.withFee, holder.inputsRaw)) - def generatePaymentTransactionR: Route = (path( "payment" / "generate") & post - & entity(as[RequestsHolder])) { holder => - generateTransaction(holder.withFee, holder.inputsRaw) - } - - def sendPaymentTransactionR: Route = (path("payment" / "send") & post - & entity(as[RequestsHolder])) { holder => - sendTransaction(holder.withFee, holder.inputsRaw) - } - - def generateAssetIssueTransactionR: Route = (path("assets" / "generate") & post - & entity(as[RequestsHolder])) { holder => - generateTransaction(holder.withFee, holder.inputsRaw) - } - - def sendAssetIssueTransactionR: Route = (path("assets" / "issue") & post - & entity(as[RequestsHolder])) { holder => - sendTransaction(holder.withFee, holder.inputsRaw) - } - def balancesR: Route = (path("balances") & get) { withWallet(_.confirmedBalances) } diff --git a/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala b/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala index 88b6d24c16..ee4a1d6364 100644 --- a/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala @@ -80,20 +80,6 @@ class WalletApiRouteSpec extends FlatSpec } } - it should "generate payment transaction" in { - Post(prefix + "/payment/generate", RequestsHolder(Seq(paymentRequest)).asJson) ~> route ~> check { - status shouldBe StatusCodes.OK - Try(responseAs[ErgoTransaction]) shouldBe 'success - } - } - - it should "generate asset issue transaction" in { - Post(prefix + "/assets/generate", RequestsHolder(Seq(assetIssueRequest)).asJson) ~> route ~> check { - status shouldBe StatusCodes.OK - Try(responseAs[ErgoTransaction]) shouldBe 'success - } - } - it should "generate & send arbitrary transaction" in { Post(prefix + "/transaction/send", requestsHolder.asJson) ~> route ~> check { status shouldBe StatusCodes.OK @@ -101,20 +87,6 @@ class WalletApiRouteSpec extends FlatSpec } } - it should "generate & send payment transaction" in { - Post(prefix + "/payment/send", RequestsHolder(Seq(paymentRequest)).asJson) ~> route ~> check { - status shouldBe StatusCodes.OK - responseAs[String] should not be empty - } - } - - it should "generate & send asset issue transaction" in { - Post(prefix + "/assets/issue", RequestsHolder(Seq(assetIssueRequest)).asJson) ~> route ~> check { - status shouldBe StatusCodes.OK - responseAs[String] should not be empty - } - } - it should "generate valid P2SAddress form source" in { val suffix = "/p2s_address" val assertion = (json: Json) => { From 46ddf82640b58dfe1a37b9f1efa906c70f00f6be Mon Sep 17 00:00:00 2001 From: oskin1 Date: Wed, 17 Jul 2019 17:54:04 +0300 Subject: [PATCH 09/27] Opeanpi schema fixed. --- src/main/resources/api/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index 19b064a958..71317e1692 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -271,7 +271,7 @@ components: description: List of inputs to be used in serialized form type: array items: - - $ref: '#/components/schemas/SerializedBox' + $ref: '#/components/schemas/SerializedBox' SourceHolder: type: object From 20b5d9712ffea83ae818035973340d4fa0293fbe Mon Sep 17 00:00:00 2001 From: oskin1 Date: Thu, 18 Jul 2019 14:06:52 +0300 Subject: [PATCH 10/27] Simplified wallet method added. --- src/main/resources/api/openapi.yaml | 36 +++++++++++++++++++ .../org/ergoplatform/api/WalletApiRoute.scala | 16 ++++----- .../api/routes/WalletApiRouteSpec.scala | 7 ++++ 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index 71317e1692..9eea349234 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -2239,6 +2239,42 @@ paths: schema: $ref: '#/components/schemas/ApiError' + /wallet/payment/send: + post: + security: + - ApiKeyAuth: [api_key] + summary: Generate and send payment transaction (default fee is used) + operationId: walletPaymentTransactionGenerateAndSend + tags: + - wallet + requestBody: + required: true + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PaymentRequest' + responses: + '200': + description: Identifier of an Ergo transaction generated + content: + application/json: + schema: + $ref: '#/components/schemas/TransactionId' + '400': + description: Bad payment request + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + default: + description: Error + content: + application/json: + schema: + $ref: '#/components/schemas/ApiError' + /mining/candidate: get: security: diff --git a/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala index 0778171169..ff5f785fc0 100644 --- a/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala +++ b/src/main/scala/org/ergoplatform/api/WalletApiRoute.scala @@ -48,6 +48,7 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e unspentBoxesR ~ boxesR ~ generateTransactionR ~ + sendPaymentTransactionR ~ sendTransactionR ~ p2shAddressR ~ p2sAddressR ~ @@ -64,13 +65,6 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e private val loadMaxKeys: Int = 100 - private val fee: Directive1[Option[Long]] = entity(as[Json]).flatMap { p => - Try(p.hcursor.downField("fee").as[Long]) match { - case Success(Right(value)) => provide(Some(value)) - case _ => provide(None) - } - } - private val source: Directive1[String] = entity(as[Json]).flatMap { p => p.hcursor.downField("source").as[String] .fold(_ => reject, s => provide(s)) @@ -149,9 +143,9 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e } } - private def withFee(requests: Seq[TransactionRequest], feeOpt: Option[Long]): Seq[TransactionRequest] = { + private def withFee(requests: Seq[TransactionRequest]): Seq[TransactionRequest] = { requests :+ PaymentRequest(Pay2SAddress(ergoSettings.chainSettings.monetary.feeProposition), - feeOpt.getOrElse(ergoSettings.walletSettings.defaultTransactionFee), Seq.empty, Map.empty) + ergoSettings.walletSettings.defaultTransactionFee, Seq.empty, Map.empty) } private def withWalletOp[T](op: ErgoWalletReader => Future[T])(toRoute: T => Route): Route = { @@ -184,6 +178,10 @@ case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, e def generateTransactionR: Route = (path("transaction" / "generate") & post & entity(as[RequestsHolder]))(holder => generateTransaction(holder.withFee, holder.inputsRaw)) + def sendPaymentTransactionR: Route = (path("payment" / "send") & post + & entity(as[Seq[PaymentRequest]])) { requests => + sendTransaction(withFee(requests), Seq.empty) + } def balancesR: Route = (path("balances") & get) { withWallet(_.confirmedBalances) } diff --git a/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala b/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala index ee4a1d6364..c9461df448 100644 --- a/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/api/routes/WalletApiRouteSpec.scala @@ -87,6 +87,13 @@ class WalletApiRouteSpec extends FlatSpec } } + it should "generate & send payment transaction" in { + Post(prefix + "/payment/send", Seq(paymentRequest).asJson) ~> route ~> check { + status shouldBe StatusCodes.OK + responseAs[String] should not be empty + } + } + it should "generate valid P2SAddress form source" in { val suffix = "/p2s_address" val assertion = (json: Json) => { From 119af2ae14943921bab68f70b111835f59d778a0 Mon Sep 17 00:00:00 2001 From: oskin1 Date: Fri, 19 Jul 2019 15:47:30 +0300 Subject: [PATCH 11/27] Scorex version updated. --- build.sbt | 2 +- lock.sbt | 4 ++-- .../org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 81c5ed5c2f..0fc7dd22fc 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ lazy val commonSettings = Seq( licenses := Seq("CC0" -> url("https://creativecommons.org/publicdomain/zero/1.0/legalcode")) ) -val scorexVersion = "6ffeafc8-SNAPSHOT" +val scorexVersion = "fa8cf3dc-SNAPSHOT" val sigmaStateVersion = "master-0e75c9b0-SNAPSHOT" val ergoWalletVersion = "master-351dd620-SNAPSHOT" diff --git a/lock.sbt b/lock.sbt index e5ceaf068a..b95dcff9ba 100644 --- a/lock.sbt +++ b/lock.sbt @@ -66,7 +66,7 @@ dependencyOverrides in ThisBuild ++= Seq( "org.scodec" % "scodec-bits_2.12" % "1.1.6", "org.scorexfoundation" % "avl-iodb_2.12" % "0.2.15", "org.scorexfoundation" % "iodb_2.12" % "0.3.2", - "org.scorexfoundation" % "scorex-core_2.12" % "6ffeafc8-SNAPSHOT", + "org.scorexfoundation" % "scorex-core_2.12" % "fa8cf3dc-SNAPSHOT", "org.scorexfoundation" % "scorex-util_2.12" % "0.1.4", "org.scorexfoundation" % "scrypto_2.12" % "2.1.6", "org.scorexfoundation" % "sigma-api_2.12" % "master-0e75c9b0-SNAPSHOT", @@ -87,4 +87,4 @@ dependencyOverrides in ThisBuild ++= Seq( "org.typelevel" % "spire_2.12" % "0.14.1", "org.whispersystems" % "curve25519-java" % "0.5.0" ) -// LIBRARY_DEPENDENCIES_HASH 912e526d70e45a563ed390e8200629117318d55c +// LIBRARY_DEPENDENCIES_HASH c3ac010b4460e7554a5a5bde789ad25be89bf17d diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index de46a431e4..8c19c5bffa 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -134,7 +134,7 @@ class ErgoWalletActor(settings: ErgoSettings, boxSelector: BoxSelector) case Rollback(version: VersionTag, height: Int) => // remove postponed blocks which were rolled back. storage.readLatestPostponedBlockHeight.foreach { latestHeight => - (height to latestHeight).foreach(storage.removeBlock) + storage.removeBlocks(height, latestHeight) } registry.rollback(version).fold( e => log.error(s"Failed to rollback wallet registry to version $version due to: $e"), _ => ()) From a9c0d99e8ad91006744e722d8101a076a7d2ad8e Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 23 Jul 2019 16:27:07 +0300 Subject: [PATCH 12/27] updating Scorex version --- build.sbt | 4 ++-- lock.sbt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 8a1cfbb36f..916469e1e6 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbt._ lazy val commonSettings = Seq( organization := "org.ergoplatform", name := "ergo", - version := "3.0.4", + version := "3.0.5-SNAPSHOT", scalaVersion := "2.12.8", resolvers ++= Seq("Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/", "SonaType" at "https://oss.sonatype.org/content/groups/public", @@ -14,7 +14,7 @@ lazy val commonSettings = Seq( licenses := Seq("CC0" -> url("https://creativecommons.org/publicdomain/zero/1.0/legalcode")) ) -val scorexVersion = "fa8cf3dc-SNAPSHOT" +val scorexVersion = "3696cb2b-SNAPSHOT" val sigmaStateVersion = "master-0e75c9b0-SNAPSHOT" val ergoWalletVersion = "master-351dd620-SNAPSHOT" diff --git a/lock.sbt b/lock.sbt index b95dcff9ba..9ed3c5a6ba 100644 --- a/lock.sbt +++ b/lock.sbt @@ -66,7 +66,7 @@ dependencyOverrides in ThisBuild ++= Seq( "org.scodec" % "scodec-bits_2.12" % "1.1.6", "org.scorexfoundation" % "avl-iodb_2.12" % "0.2.15", "org.scorexfoundation" % "iodb_2.12" % "0.3.2", - "org.scorexfoundation" % "scorex-core_2.12" % "fa8cf3dc-SNAPSHOT", + "org.scorexfoundation" % "scorex-core_2.12" % "3696cb2b-SNAPSHOT", "org.scorexfoundation" % "scorex-util_2.12" % "0.1.4", "org.scorexfoundation" % "scrypto_2.12" % "2.1.6", "org.scorexfoundation" % "sigma-api_2.12" % "master-0e75c9b0-SNAPSHOT", @@ -87,4 +87,4 @@ dependencyOverrides in ThisBuild ++= Seq( "org.typelevel" % "spire_2.12" % "0.14.1", "org.whispersystems" % "curve25519-java" % "0.5.0" ) -// LIBRARY_DEPENDENCIES_HASH c3ac010b4460e7554a5a5bde789ad25be89bf17d +// LIBRARY_DEPENDENCIES_HASH 4cfd10bbdcbdef82e174d80ae2658b99780aa818 From 1b11243eab5210f9fb96654ddc459473a85f723e Mon Sep 17 00:00:00 2001 From: catena Date: Tue, 23 Jul 2019 19:03:05 +0300 Subject: [PATCH 13/27] Fixing tests --- src/main/resources/reference.conf | 2 +- src/main/scala/org/ergoplatform/local/ErgoMiner.scala | 3 ++- .../org/ergoplatform/nodeView/mempool/ScriptsSpec.scala | 4 ++++ .../ergoplatform/settings/ErgoSettingsSpecification.scala | 6 +++--- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index 2983a4a9bf..b539dd3474 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -27,7 +27,7 @@ ergo { mining = false # Node will only include transactions with lower scipt complexity to own blocks - maxScriptComplexity = 100 + maxScriptComplexity = 1323 # Use external miner, native miner is used if set to `false` useExternalMiner = true diff --git a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala index 130839bd91..7ece23fb8a 100644 --- a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala @@ -477,7 +477,8 @@ object ErgoMiner extends ScorexLogging { current -> invalidTxs } } - case _ => + case Failure(e) => + log.debug(s"Do not incude transaction ${tx.id} due to ${e.getMessage}") loop(mempoolTxs.tail, acc, lastFeeTx, invalidTxs :+ tx.id) } case _ => // mempool is empty diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala index 7980ddc7c0..e20e4b97f8 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala @@ -24,6 +24,10 @@ class ScriptsSpec extends ErgoPropertyTest { val fixedBox: ErgoBox = ergoBoxGen(fromString("1 == 1"), heightGen = 0).sample.get implicit lazy val context: IRContext = new RuntimeIRContext + property("scripts complexity") { + defaultMinerPk.toSigmaProp.treeWithSegregation.complexity should be <= settings.nodeSettings.maxScriptComplexity + ErgoScriptPredef.rewardOutputScript(delta, defaultMinerPk).complexity should be <= settings.nodeSettings.maxScriptComplexity + } property("simple operations without cryptography") { // true/false diff --git a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala index 66d84b95ed..5f6197a0c7 100644 --- a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala +++ b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala @@ -15,7 +15,7 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { property("should read default settings") { val settings = ErgoSettings.read() settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 1000, - poPoWBootstrap = false, 10, mining = false, 100, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, 1323, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, keepVersions = 200, mempoolCapacity = 100000, blacklistCapacity = 100000, mempoolCleanupDuration = 10.seconds, minimalFeeAmount = 0) } @@ -23,14 +23,14 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { property("should read user settings from json file") { val settings = ErgoSettings.read(Args(Some("src/test/resources/settings.json"), None)) settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 12, - poPoWBootstrap = false, 10, mining = false, 100, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, 1323, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 10.seconds, minimalFeeAmount = 0) } property("should read user settings from HOCON file") { val settings = ErgoSettings.read(Args(Some("src/test/resources/settings.conf"), None)) settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 13, - poPoWBootstrap = false, 10, mining = false, 100, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, 1323, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 10.seconds, minimalFeeAmount = 0) } From a79a4736e9978c3f31d8ac1f85461db6d2d8fa00 Mon Sep 17 00:00:00 2001 From: catena Date: Wed, 24 Jul 2019 15:14:23 +0300 Subject: [PATCH 14/27] 100 => 1323 --- src/test/scala/org/ergoplatform/tools/ChainGenerator.scala | 2 +- src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala | 2 +- src/test/scala/org/ergoplatform/utils/Stubs.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index 65ba1697e8..63038a15fb 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -60,7 +60,7 @@ object ChainGenerator extends TestKit(ActorSystem()) with App with ErgoTestHelpe val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, - -1, poPoWBootstrap = false, minimalSuffix, mining = false, 100, miningDelay, useExternalMiner = false, + -1, poPoWBootstrap = false, minimalSuffix, mining = false, 1323, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val ms = settings.chainSettings.monetary.copy( minerRewardDelay = RewardDelay diff --git a/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala b/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala index 37dc065554..7da3741292 100644 --- a/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala +++ b/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala @@ -42,7 +42,7 @@ trait HistoryTestHelpers extends ErgoPropertyTest { val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(stateType, verifyTransactions, blocksToKeep, - PoPoWBootstrap, minimalSuffix, mining = false, 100, miningDelay, useExternalMiner = false, miningPubKeyHex = None, + PoPoWBootstrap, minimalSuffix, mining = false, 1323, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val scorexSettings: ScorexSettings = null val testingSettings: TestingSettings = null diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 2a7f798e84..5adaf7eca7 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -251,7 +251,7 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(stateType, verifyTransactions, blocksToKeep, - PoPoWBootstrap, minimalSuffix, mining = false, 100, miningDelay, useExternalMiner = false, miningPubKeyHex = None, + PoPoWBootstrap, minimalSuffix, mining = false, 1323, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val scorexSettings: ScorexSettings = null val testingSettings: TestingSettings = null From 99bf1e7bd1d7db3eacb9d1f447a03935ac5d1038 Mon Sep 17 00:00:00 2001 From: catena Date: Thu, 25 Jul 2019 14:02:17 +0300 Subject: [PATCH 15/27] maxScriptComplexity => maxTransactionComplexity --- src/main/resources/reference.conf | 4 ++-- src/main/scala/org/ergoplatform/local/ErgoMiner.scala | 8 ++++---- .../org/ergoplatform/nodeView/state/UtxoStateReader.scala | 7 +++---- src/main/scala/org/ergoplatform/settings/Constants.scala | 3 +++ .../ergoplatform/settings/NodeConfigurationSettings.scala | 4 ++-- .../org/ergoplatform/nodeView/mempool/ScriptsSpec.scala | 4 ++-- .../ergoplatform/settings/ErgoSettingsSpecification.scala | 6 +++--- .../scala/org/ergoplatform/tools/ChainGenerator.scala | 2 +- .../scala/org/ergoplatform/utils/HistoryTestHelpers.scala | 2 +- src/test/scala/org/ergoplatform/utils/Stubs.scala | 2 +- 10 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/main/resources/reference.conf b/src/main/resources/reference.conf index b539dd3474..6c30fe3625 100644 --- a/src/main/resources/reference.conf +++ b/src/main/resources/reference.conf @@ -26,8 +26,8 @@ ergo { # Is the node is doing mining mining = false - # Node will only include transactions with lower scipt complexity to own blocks - maxScriptComplexity = 1323 + # Node will only include transactions to own blocks with lower complexity + maxTransactionComplexity = 100000 # Use external miner, native miner is used if set to `false` useExternalMiner = true diff --git a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala index 7ece23fb8a..2abff705d2 100644 --- a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala @@ -60,7 +60,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, private val protocolVersion = ergoSettings.chainSettings.protocolVersion private val powScheme = ergoSettings.chainSettings.powScheme private val externalMinerMode = ergoSettings.nodeSettings.useExternalMiner - private val maxScriptComplexity: Int = ergoSettings.nodeSettings.maxScriptComplexity + private val maxTransactionComplexity: Int = ergoSettings.nodeSettings.maxTransactionComplexity // shared mutable state private var isMining = false @@ -325,7 +325,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, val (txs, toEliminate) = ErgoMiner.collectTxs(minerPk, state.stateContext.currentParameters.maxBlockCost, state.stateContext.currentParameters.maxBlockSize, - maxScriptComplexity, + maxTransactionComplexity, state, upcomingContext, pool.getAllPrioritized, @@ -424,7 +424,7 @@ object ErgoMiner extends ScorexLogging { def collectTxs(minerPk: ProveDlog, maxBlockCost: Long, maxBlockSize: Long, - maxScriptComplexity: Int, + maxTransactionComplexity: Int, us: UtxoStateReader, upcomingContext: ErgoStateContext, mempoolTxsIn: Iterable[ErgoTransaction], @@ -447,7 +447,7 @@ object ErgoMiner extends ScorexLogging { case Some(tx) => implicit val verifier: ErgoInterpreter = ErgoInterpreter(us.stateContext.currentParameters) // check validity and calculate transaction cost - us.validateWithCost(tx, Some(upcomingContext), maxScriptComplexity) match { + us.validateWithCost(tx, Some(upcomingContext), maxTransactionComplexity) match { case Success(costConsumed) => val newTxs = fixTxsConflicts((tx, costConsumed) +: acc) val newBoxes = newTxs.flatMap(_._1.outputs) diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala index 49f03cc7d5..2b31db6de4 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala @@ -37,10 +37,9 @@ trait UtxoStateReader extends ErgoStateReader with TransactionValidation[ErgoTra val context = stateContextOpt.getOrElse(stateContext) tx.statelessValidity.flatMap { _ => val boxesToSpend = tx.inputs.flatMap(i => boxById(i.boxId)) - boxesToSpend.foreach { b => - if (b.ergoTree.complexity > complexityLimit) { - throw new Exception(s"Box ${Algos.encode(b.id)} have too complex script: ${b.ergoTree.complexity}") - } + val txComplexity = boxesToSpend.map(_.ergoTree.complexity).sum + if (txComplexity > complexityLimit) { + throw new Exception(s"Transaction $tx have too high complexity $txComplexity") } tx.statefulValidity( boxesToSpend, diff --git a/src/main/scala/org/ergoplatform/settings/Constants.scala b/src/main/scala/org/ergoplatform/settings/Constants.scala index 057c7e96d0..4257ff3902 100644 --- a/src/main/scala/org/ergoplatform/settings/Constants.scala +++ b/src/main/scala/org/ergoplatform/settings/Constants.scala @@ -59,4 +59,7 @@ object Constants { val MaxExtensionSize: Int = 32 * 1024 // Maximum extension size during bytes parsing. Allows to move MaxExtensionSize to Parameters in future val MaxExtensionSizeMax: Int = 1024 * 1024 + // Default limit for transaction complexity to be included into block. + val DefaultComplexityLimit: Int = 100000 + } diff --git a/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala b/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala index 8e03de256f..dbde74dd6c 100644 --- a/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/NodeConfigurationSettings.scala @@ -17,7 +17,7 @@ case class NodeConfigurationSettings(stateType: StateType, poPoWBootstrap: Boolean, minimalSuffix: Int, mining: Boolean, - maxScriptComplexity: Int, + maxTransactionComplexity: Int, miningDelay: FiniteDuration, useExternalMiner: Boolean, miningPubKeyHex: Option[String], @@ -40,7 +40,7 @@ trait NodeConfigurationReaders extends StateTypeReaders with ModifierIdReader { cfg.as[Boolean](s"$path.PoPoWBootstrap"), cfg.as[Int](s"$path.minimalSuffix"), cfg.as[Boolean](s"$path.mining"), - cfg.as[Int](s"$path.maxScriptComplexity"), + cfg.as[Int](s"$path.maxTransactionComplexity"), cfg.as[FiniteDuration](s"$path.miningDelay"), cfg.as[Boolean](s"$path.useExternalMiner"), cfg.as[Option[String]](s"$path.miningPubKeyHex"), diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala index e20e4b97f8..bb94c67751 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala @@ -25,8 +25,8 @@ class ScriptsSpec extends ErgoPropertyTest { implicit lazy val context: IRContext = new RuntimeIRContext property("scripts complexity") { - defaultMinerPk.toSigmaProp.treeWithSegregation.complexity should be <= settings.nodeSettings.maxScriptComplexity - ErgoScriptPredef.rewardOutputScript(delta, defaultMinerPk).complexity should be <= settings.nodeSettings.maxScriptComplexity + defaultMinerPk.toSigmaProp.treeWithSegregation.complexity should be <= settings.nodeSettings.maxTransactionComplexity + ErgoScriptPredef.rewardOutputScript(delta, defaultMinerPk).complexity should be <= settings.nodeSettings.maxTransactionComplexity } property("simple operations without cryptography") { diff --git a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala index 5f6197a0c7..13d0227d12 100644 --- a/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala +++ b/src/test/scala/org/ergoplatform/settings/ErgoSettingsSpecification.scala @@ -15,7 +15,7 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { property("should read default settings") { val settings = ErgoSettings.read() settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 1000, - poPoWBootstrap = false, 10, mining = false, 1323, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, Constants.DefaultComplexityLimit, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, keepVersions = 200, mempoolCapacity = 100000, blacklistCapacity = 100000, mempoolCleanupDuration = 10.seconds, minimalFeeAmount = 0) } @@ -23,14 +23,14 @@ class ErgoSettingsSpecification extends ErgoPropertyTest { property("should read user settings from json file") { val settings = ErgoSettings.read(Args(Some("src/test/resources/settings.json"), None)) settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 12, - poPoWBootstrap = false, 10, mining = false, 1323, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, Constants.DefaultComplexityLimit, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 10.seconds, minimalFeeAmount = 0) } property("should read user settings from HOCON file") { val settings = ErgoSettings.read(Args(Some("src/test/resources/settings.conf"), None)) settings.nodeSettings shouldBe NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, 13, - poPoWBootstrap = false, 10, mining = false, 1323, 1.second, useExternalMiner = false, miningPubKeyHex = None, + poPoWBootstrap = false, 10, mining = false, Constants.DefaultComplexityLimit, 1.second, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 10.seconds, minimalFeeAmount = 0) } diff --git a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala index 63038a15fb..529d300c4a 100644 --- a/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala +++ b/src/test/scala/org/ergoplatform/tools/ChainGenerator.scala @@ -60,7 +60,7 @@ object ChainGenerator extends TestKit(ActorSystem()) with App with ErgoTestHelpe val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(StateType.Utxo, verifyTransactions = true, - -1, poPoWBootstrap = false, minimalSuffix, mining = false, 1323, miningDelay, useExternalMiner = false, + -1, poPoWBootstrap = false, minimalSuffix, mining = false, Constants.DefaultComplexityLimit, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val ms = settings.chainSettings.monetary.copy( minerRewardDelay = RewardDelay diff --git a/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala b/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala index 7da3741292..83baeede7f 100644 --- a/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala +++ b/src/test/scala/org/ergoplatform/utils/HistoryTestHelpers.scala @@ -42,7 +42,7 @@ trait HistoryTestHelpers extends ErgoPropertyTest { val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(stateType, verifyTransactions, blocksToKeep, - PoPoWBootstrap, minimalSuffix, mining = false, 1323, miningDelay, useExternalMiner = false, miningPubKeyHex = None, + PoPoWBootstrap, minimalSuffix, mining = false, Constants.DefaultComplexityLimit, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val scorexSettings: ScorexSettings = null val testingSettings: TestingSettings = null diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 5adaf7eca7..b2d12342ce 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -251,7 +251,7 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with val miningDelay = 1.second val minimalSuffix = 2 val nodeSettings: NodeConfigurationSettings = NodeConfigurationSettings(stateType, verifyTransactions, blocksToKeep, - PoPoWBootstrap, minimalSuffix, mining = false, 1323, miningDelay, useExternalMiner = false, miningPubKeyHex = None, + PoPoWBootstrap, minimalSuffix, mining = false, Constants.DefaultComplexityLimit, miningDelay, useExternalMiner = false, miningPubKeyHex = None, offlineGeneration = false, 200, 100000, 100000, 1.minute, 1000000) val scorexSettings: ScorexSettings = null val testingSettings: TestingSettings = null From 3e0b0889ec8416c017f44721e87596eefc7b2fc9 Mon Sep 17 00:00:00 2001 From: catena Date: Thu, 25 Jul 2019 16:16:04 +0300 Subject: [PATCH 16/27] Test for complex transaction inclusion --- .../ergoplatform/mining/ErgoMinerSpec.scala | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala index 94c912d92d..0a71cd3219 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala @@ -1,6 +1,6 @@ package org.ergoplatform.mining -import akka.actor.{Actor, ActorRef, ActorSystem, PoisonPill} +import akka.actor.{Actor, ActorRef, ActorSystem} import akka.pattern.ask import akka.testkit.{TestKit, TestProbe} import akka.util.Timeout @@ -17,13 +17,15 @@ import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader import org.ergoplatform.nodeView.state._ import org.ergoplatform.nodeView.wallet._ import org.ergoplatform.nodeView.{ErgoNodeViewRef, ErgoReadersHolderRef} -import org.ergoplatform.settings.{Args, ErgoSettings} +import org.ergoplatform.settings.ErgoSettings import org.ergoplatform.utils.ErgoTestHelpers import org.ergoplatform.utils.generators.ValidBlocksGenerators import org.ergoplatform.{ErgoBox, ErgoBoxCandidate, ErgoScriptPredef, Input} import org.scalatest.FlatSpec import scorex.core.NodeViewHolder.ReceivableMessages.LocallyGeneratedTransaction import scorex.core.network.NodeViewSynchronizer.ReceivableMessages.SemanticallySuccessfulModifier +import sigmastate.SigmaAnd +import sigmastate.Values.{ErgoTree, SigmaPropConstant} import sigmastate.basics.DLogProtocol import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.utxo.CostTable.Cost @@ -53,6 +55,75 @@ class ErgoMinerSpec extends FlatSpec with ErgoTestHelpers with ValidBlocksGenera empty.copy(nodeSettings = nodeSettings, chainSettings = chainSettings) } + it should "not include too complex transactions" in new TestKit(ActorSystem()) { + val testProbe = new TestProbe(system) + system.eventStream.subscribe(testProbe.ref, newBlock) + val ergoSettings: ErgoSettings = defaultSettings.copy(directory = createTempDir.getAbsolutePath) + val complexScript: ErgoTree = (0 until 100).foldLeft(SigmaAnd(SigmaPropConstant(defaultMinerPk), SigmaPropConstant(defaultMinerPk))) { (l, _) => + SigmaAnd(SigmaPropConstant(defaultMinerPk), l) + } + complexScript.complexity shouldBe 28077 + + + val nodeViewHolderRef: ActorRef = ErgoNodeViewRef(ergoSettings, timeProvider) + val readersHolderRef: ActorRef = ErgoReadersHolderRef(nodeViewHolderRef) + + val minerRef: ActorRef = ErgoMinerRef( + ergoSettings, + nodeViewHolderRef, + readersHolderRef, + timeProvider, + Some(defaultMinerSecret) + ) + expectNoMessage(1 second) + val r: Readers = await((readersHolderRef ? GetReaders).mapTo[Readers]) + + val history: ErgoHistoryReader = r.h + val startBlock: Option[Header] = history.bestHeaderOpt + + minerRef ! StartMining + + testProbe.expectMsgClass(newBlockDuration, newBlock) + + + val boxToSpend: ErgoBox = r.h.bestFullBlockOpt.get.transactions.last.outputs.last + boxToSpend.propositionBytes shouldBe ErgoScriptPredef.rewardOutputScript(emission.settings.minerRewardDelay, defaultMinerPk).bytes + + val input = Input(boxToSpend.id, emptyProverResult) + + // create transaction with output with complex proposition + val output = new ErgoBoxCandidate(boxToSpend.value / 10, complexScript, r.s.stateContext.currentHeight) + val outputs = (0 until 10).map(_ => output) + val unsignedTx = new UnsignedErgoTransaction(IndexedSeq(input), IndexedSeq(), outputs) + val tx = defaultProver.sign(unsignedTx, IndexedSeq(boxToSpend), IndexedSeq(), r.s.stateContext).get + nodeViewHolderRef ! LocallyGeneratedTransaction[ErgoTransaction](ErgoTransaction(tx)) + expectNoMessage(1 seconds) + await((readersHolderRef ? GetReaders).mapTo[Readers]).m.size shouldBe 1 + testProbe.expectMsgClass(newBlockDuration, newBlock) + testProbe.expectMsgClass(newBlockDuration, newBlock) + testProbe.expectMsgClass(newBlockDuration, newBlock) + await((readersHolderRef ? GetReaders).mapTo[Readers]).m.size shouldBe 0 + + // try to spend all the boxes with complex scripts + val complexInputs = tx.outputs.map(o => Input(o.id, emptyProverResult)) + val complexOut = new ErgoBoxCandidate(tx.outputs.map(_.value).sum, complexScript, r.s.stateContext.currentHeight) + val unsignedComplexTx = new UnsignedErgoTransaction(complexInputs, IndexedSeq(), IndexedSeq(complexOut)) + val complexTx = defaultProver.sign(unsignedComplexTx, tx.outputs, IndexedSeq(), r.s.stateContext).get + tx.outputs.map(_.ergoTree.complexity).sum should be > ergoSettings.nodeSettings.maxTransactionComplexity + nodeViewHolderRef ! LocallyGeneratedTransaction[ErgoTransaction](ErgoTransaction(complexTx)) + expectNoMessage(1 seconds) + await((readersHolderRef ? GetReaders).mapTo[Readers]).m.size shouldBe 1 + testProbe.expectMsgClass(newBlockDuration, newBlock) + testProbe.expectMsgClass(newBlockDuration, newBlock) + testProbe.expectMsgClass(newBlockDuration, newBlock) + // complex tx was removed from mempool + await((readersHolderRef ? GetReaders).mapTo[Readers]).m.size shouldBe 0 + // complex tx was not included + val state = await((readersHolderRef ? GetReaders).mapTo[Readers]).s.asInstanceOf[UtxoState] + tx.outputs.foreach(o => state.boxById(o.id) should not be None) + complexTx.outputs.foreach(o => state.boxById(o.id) shouldBe None) + } + it should "not freeze while mempool is full" in new TestKit(ActorSystem()) { // generate amount of transactions, twice more than can fit in one block val desiredSize: Int = ((parameters.maxBlockCost / Cost.DlogDeclaration) * 2).toInt From 50fcc766bf04f136e42d4124f9e9cedabacc7675 Mon Sep 17 00:00:00 2001 From: catena Date: Fri, 26 Jul 2019 12:44:46 +0300 Subject: [PATCH 17/27] Update src/main/scala/org/ergoplatform/local/ErgoMiner.scala Co-Authored-By: Alexander Slesarenko --- src/main/scala/org/ergoplatform/local/ErgoMiner.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala index 2abff705d2..1914d52345 100644 --- a/src/main/scala/org/ergoplatform/local/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/local/ErgoMiner.scala @@ -478,7 +478,7 @@ object ErgoMiner extends ScorexLogging { } } case Failure(e) => - log.debug(s"Do not incude transaction ${tx.id} due to ${e.getMessage}") + log.debug(s"Do not include transaction ${tx.id} due to ${e.getMessage}") loop(mempoolTxs.tail, acc, lastFeeTx, invalidTxs :+ tx.id) } case _ => // mempool is empty From 73b68f1d02d9cb5d39eb6645d887e8e000806d2d Mon Sep 17 00:00:00 2001 From: catena Date: Fri, 26 Jul 2019 12:48:19 +0300 Subject: [PATCH 18/27] Use default complexity limit for founders box transaction --- .../ergoplatform/nodeView/state/UtxoStateSpecification.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala index 4f3cdcfae4..a41815a835 100644 --- a/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala +++ b/src/test/scala/org/ergoplatform/nodeView/state/UtxoStateSpecification.scala @@ -44,7 +44,7 @@ class UtxoStateSpecification extends ErgoPropertyTest with ErgoTransactionGenera val newBoxes = IndexedSeq(newFoundersBox, rewardBox) val unsignedTx = new UnsignedErgoTransaction(inputs, IndexedSeq(), newBoxes) val tx: ErgoTransaction = ErgoTransaction(defaultProver.sign(unsignedTx, IndexedSeq(foundersBox), emptyDataBoxes, us.stateContext).get) - us.validateWithCost(tx, None, Int.MaxValue).get should be <= 100000L + us.validateWithCost(tx, None, Constants.DefaultComplexityLimit).get should be <= 100000L val block1 = validFullBlock(Some(lastBlock), us, Seq(ErgoTransaction(tx))) us = us.applyModifier(block1).get foundersBox = tx.outputs.head From e490bebd419e3de0287a4a86d8901b43ebdbdbf0 Mon Sep 17 00:00:00 2001 From: catena Date: Fri, 26 Jul 2019 16:56:39 +0300 Subject: [PATCH 19/27] Fix unstable test --- .../scala/org/ergoplatform/mining/ErgoMinerSpec.scala | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala index 0a71cd3219..aa9567ae6c 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala @@ -110,9 +110,13 @@ class ErgoMinerSpec extends FlatSpec with ErgoTestHelpers with ValidBlocksGenera val unsignedComplexTx = new UnsignedErgoTransaction(complexInputs, IndexedSeq(), IndexedSeq(complexOut)) val complexTx = defaultProver.sign(unsignedComplexTx, tx.outputs, IndexedSeq(), r.s.stateContext).get tx.outputs.map(_.ergoTree.complexity).sum should be > ergoSettings.nodeSettings.maxTransactionComplexity - nodeViewHolderRef ! LocallyGeneratedTransaction[ErgoTransaction](ErgoTransaction(complexTx)) - expectNoMessage(1 seconds) - await((readersHolderRef ? GetReaders).mapTo[Readers]).m.size shouldBe 1 + // ensure that complex transaction was included into mempool + var mempoolSize = 0 + while (mempoolSize == 0) { + nodeViewHolderRef ! LocallyGeneratedTransaction[ErgoTransaction](ErgoTransaction(complexTx)) + mempoolSize = await((readersHolderRef ? GetReaders).mapTo[Readers]).m.size + } + testProbe.expectMsgClass(newBlockDuration, newBlock) testProbe.expectMsgClass(newBlockDuration, newBlock) testProbe.expectMsgClass(newBlockDuration, newBlock) From ce1ad8b683bb89101e738086d30078e0e75b9b02 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Mon, 5 Aug 2019 21:53:47 +0300 Subject: [PATCH 20/27] maxComplexity --- .../org/ergoplatform/nodeView/mempool/ScriptsSpec.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala index bb94c67751..678cb4ae70 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ScriptsSpec.scala @@ -25,8 +25,9 @@ class ScriptsSpec extends ErgoPropertyTest { implicit lazy val context: IRContext = new RuntimeIRContext property("scripts complexity") { - defaultMinerPk.toSigmaProp.treeWithSegregation.complexity should be <= settings.nodeSettings.maxTransactionComplexity - ErgoScriptPredef.rewardOutputScript(delta, defaultMinerPk).complexity should be <= settings.nodeSettings.maxTransactionComplexity + val maxComplexity = settings.nodeSettings.maxTransactionComplexity + defaultMinerPk.toSigmaProp.treeWithSegregation.complexity should be <= maxComplexity + ErgoScriptPredef.rewardOutputScript(delta, defaultMinerPk).complexity should be <= maxComplexity } property("simple operations without cryptography") { From 8ce5333959138489baae26f0e14eb224ed498de6 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 6 Aug 2019 14:06:56 +0300 Subject: [PATCH 21/27] generateTx desc update --- src/main/resources/api/openapi.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index acbb4381fb..44e2383a64 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -2182,7 +2182,9 @@ paths: requestBody: description: This API method receives a sequence of requests as an input. Each request will produce an output of the resulting transaction (with fee output created automatically). Currently supported types of requests are - payment and asset issuance requests. + payment and asset issuance requests. An example for such a transaction with two requests is provided below. + Please note that for the payment request "assets" and "registers" fields are not needed, and if "fee" is missed, fee + will be set to 1000000 (0.001 Erg). For asset issuance request, "registers" field is not needed. required: true content: application/json: From 11dccfc325cf22a666157f2824929841ec30d323 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 6 Aug 2019 18:44:08 +0300 Subject: [PATCH 22/27] fee removed from request schema --- src/main/resources/api/openapi.yaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index 44e2383a64..135b832832 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -303,11 +303,6 @@ components: $ref: '#/components/schemas/Asset' registers: $ref: '#/components/schemas/Registers' - fee: - description: Optional, default transaction fee from settings will be used if not defined - type: integer - format: int64 - example: 1000000 AssetIssueRequest: description: Request for generation of asset issue transaction @@ -2248,7 +2243,7 @@ paths: post: security: - ApiKeyAuth: [api_key] - summary: Generate and send payment transaction (default fee is used) + summary: Generate and send payment transaction (default fee of 0.001 Erg is used) operationId: walletPaymentTransactionGenerateAndSend tags: - wallet From 5086adebe94d930e5e7bb3653a484a5f85d72a0a Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 6 Aug 2019 19:07:19 +0300 Subject: [PATCH 23/27] generateTx desc updated --- src/main/resources/api/openapi.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index 135b832832..8a04f93ac5 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -2177,9 +2177,13 @@ paths: requestBody: description: This API method receives a sequence of requests as an input. Each request will produce an output of the resulting transaction (with fee output created automatically). Currently supported types of requests are - payment and asset issuance requests. An example for such a transaction with two requests is provided below. - Please note that for the payment request "assets" and "registers" fields are not needed, and if "fee" is missed, fee - will be set to 1000000 (0.001 Erg). For asset issuance request, "registers" field is not needed. + payment and asset issuance requests. An example for a transaction with requests of both kinds is provided below. + Please note that for the payment request "assets" and "registers" fields are not needed. + For asset issuance request, "registers" field is not needed. + + You may specify boxes to spend by providing them in "inputsRaw". Please note you need to have strict equality between + input and output total amounts of Ergs in this case. If you want wallet to pick up the boxes, + leave "inputsRaw" empty. required: true content: application/json: @@ -2214,6 +2218,7 @@ paths: tags: - wallet requestBody: + description: See description of /wallet/transaction/generate required: true content: application/json: From cedcdf4537138cfc6a533f95cc001f0a90a47b60 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 6 Aug 2019 19:23:00 +0300 Subject: [PATCH 24/27] version increased --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 956f972241..15c9cdb254 100644 --- a/build.sbt +++ b/build.sbt @@ -4,7 +4,7 @@ import sbt._ lazy val commonSettings = Seq( organization := "org.ergoplatform", name := "ergo", - version := "3.0.5", + version := "3.0.6", scalaVersion := "2.12.8", resolvers ++= Seq("Sonatype Releases" at "https://oss.sonatype.org/content/repositories/releases/", "SonaType" at "https://oss.sonatype.org/content/groups/public", From ccd68926144f36ec718b9b4e178c7643dd2747a4 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 6 Aug 2019 20:50:32 +0300 Subject: [PATCH 25/27] openjdk for tests --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 918a309efb..183e30081d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ branches: - v2.2-candidate - sigma454-type-check-on-script-deser jdk: -- oraclejdk9 +- openjdk9 scala: - 2.12.8 script: From 5e58e27970cab8b5d3994bc7d4e46a0ab11667f2 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 6 Aug 2019 21:14:34 +0300 Subject: [PATCH 26/27] sudo required --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 183e30081d..cefedbc573 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: scala # Use container-based infrastructure -sudo: false +sudo: required branches: only: - v2.1 From 33a3dcbd523266ac10561790e6fe6a8d4570822d Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Tue, 6 Aug 2019 22:10:04 +0300 Subject: [PATCH 27/27] unused imports, example in readme --- README.md | 2 +- src/main/scala/org/ergoplatform/settings/ErgoSettings.scala | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c02351739c..094490818a 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ To run specific Ergo version `` as a service with custom config `/path/ -v /path/on/host/system/to/myergo.conf:/etc/myergo.conf \ ergoplatform/ergo: -- -c /etc/myergo.conf -Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v2.2.0`. +Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v3.0.5`. This will connect to Ergo mainnet or testnet respecting your configuration passed in `myergo.conf` and network flag `--`. Every default config value would be overwritten with corresponding value in `myergo.conf`. diff --git a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala index b255417e9d..ad2552eccb 100644 --- a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala @@ -2,13 +2,9 @@ package org.ergoplatform.settings import java.io.{File, FileOutputStream} import java.nio.channels.Channels - -import cats.instances.stream import com.typesafe.config.{Config, ConfigFactory} import net.ceedubs.ficus.Ficus._ import net.ceedubs.ficus.readers.ArbitraryTypeReader._ -import org.apache.commons.io.IOUtils -import org.apache.tools.ant.util.ResourceUtils import org.ergoplatform.mining.groupElemFromBytes import org.ergoplatform.nodeView.state.StateType.Digest import org.ergoplatform.{ErgoAddressEncoder, ErgoApp, P2PKAddress} @@ -16,9 +12,9 @@ import scorex.core.settings.{ScorexSettings, SettingsReaders} import scorex.util.ScorexLogging import scorex.util.encode.Base16 import sigmastate.basics.DLogProtocol.ProveDlog - import scala.util.Try + case class ErgoSettings(directory: String, chainSettings: ChainSettings, testingSettings: TestingSettings,