From 3bfede797a0a3bea86d751195b0b57cbb4a8075a Mon Sep 17 00:00:00 2001 From: pragmaxim Date: Sat, 17 Apr 2021 18:34:23 +0200 Subject: [PATCH 01/15] removing redundant timeProvider --- .../scala/org/ergoplatform/mining/ErgoMiner.scala | 2 +- .../ergoplatform/mining/ErgoMiningThread.scala | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala index 1d9e75a801..e52daf55b1 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala @@ -479,7 +479,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, private def startInternalMiner(candidateBlock: CandidateBlock): Unit = { secretKeyOpt match { case Some(sk) => - miningThreads += ErgoMiningThread(ergoSettings, self, candidateBlock, sk.w, timeProvider)(context) + miningThreads += ErgoMiningThread(ergoSettings, self, candidateBlock, sk.w)(context) miningThreads.foreach(_ ! candidateBlock) case None => log.warn("Trying to start native miner while secret key is not ready") diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala index 11fb6e25bf..3031f89a8c 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiningThread.scala @@ -11,8 +11,7 @@ import scala.concurrent.ExecutionContext class ErgoMiningThread(ergoSettings: ErgoSettings, minerRef: ActorRef, startCandidate: CandidateBlock, - sk: PrivateKey, - timeProvider: NetworkTimeProvider) extends Actor with ScorexLogging { + sk: PrivateKey) extends Actor with ScorexLogging { implicit val ec: ExecutionContext = context.dispatcher @@ -55,20 +54,18 @@ class ErgoMiningThread(ergoSettings: ErgoSettings, object ErgoMiningThread { - def props(ergoSettings: ErgoSettings, + private def props(ergoSettings: ErgoSettings, minerRef: ActorRef, startCandidate: CandidateBlock, - sk: BigInt, - timeProvider: NetworkTimeProvider): Props = - Props(new ErgoMiningThread(ergoSettings, minerRef, startCandidate, sk, timeProvider)) + sk: BigInt): Props = + Props(new ErgoMiningThread(ergoSettings, minerRef, startCandidate, sk)) def apply(ergoSettings: ErgoSettings, minerRef: ActorRef, startCandidate: CandidateBlock, - sk: BigInt, - timeProvider: NetworkTimeProvider) + sk: BigInt) (implicit context: ActorRefFactory): ActorRef = - context.actorOf(props(ergoSettings, minerRef, startCandidate, sk, timeProvider)) + context.actorOf(props(ergoSettings, minerRef, startCandidate, sk)) case object MineBlock From 0f3705b17901ba8a415ebc6ac1ca85ae661f81d8 Mon Sep 17 00:00:00 2001 From: pragmaxim Date: Mon, 19 Apr 2021 12:31:38 +0200 Subject: [PATCH 02/15] avoid passing futures as actor messages in Miner --- .../http/api/MiningApiRoute.scala | 6 +- .../org/ergoplatform/mining/ErgoMiner.scala | 62 +++++++++++-------- .../ergoplatform/mining/ErgoMinerSpec.scala | 18 +----- .../scala/org/ergoplatform/utils/Stubs.scala | 5 +- 4 files changed, 44 insertions(+), 47 deletions(-) diff --git a/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala index 80d31518e4..1462e986e8 100644 --- a/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/MiningApiRoute.scala @@ -37,7 +37,7 @@ case class MiningApiRoute(miner: ActorRef, */ def candidateR: Route = (path("candidate") & pathEndOrSingleSlash & get) { val prepareCmd = ErgoMiner.PrepareCandidate(Seq.empty) - val candidateF = (miner ? prepareCmd).mapTo[Future[WorkMessage]].flatten + val candidateF = miner.askWithStatus(prepareCmd).mapTo[WorkMessage] ApiResponse(candidateF) } @@ -49,13 +49,13 @@ case class MiningApiRoute(miner: ActorRef, & post & entity(as[Seq[ErgoTransaction]]) & withAuth) { txs => val prepareCmd = ErgoMiner.PrepareCandidate(txs) - val candidateF = (miner ? prepareCmd).mapTo[Future[WorkMessage]].flatten + val candidateF = miner.askWithStatus(prepareCmd).mapTo[WorkMessage] ApiResponse(candidateF) } def solutionR: Route = (path("solution") & post & entity(as[AutolykosSolution])) { solution => val result = if (ergoSettings.nodeSettings.useExternalMiner) { - (miner ? solution).mapTo[Future[Unit]].flatten + miner.askWithStatus(solution).mapTo[Unit] } else { Future.failed(new Exception("External miner support is inactive")) } diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala index e52daf55b1..16b5152c53 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala @@ -1,7 +1,7 @@ package org.ergoplatform.mining import akka.actor.{Actor, ActorRef, ActorRefFactory, PoisonPill, Props} -import akka.pattern.ask +import akka.pattern.{StatusReply, ask} import akka.util.Timeout import com.google.common.primitives.Longs import org.ergoplatform.ErgoBox.TokenId @@ -38,7 +38,6 @@ import scala.annotation.tailrec import scala.collection.mutable import scala.collection.mutable.ArrayBuffer import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} @@ -285,32 +284,41 @@ class ErgoMiner(ergoSettings: ErgoSettings, } private def mining: Receive = { - case PrepareCandidate(_, _) if !ergoSettings.nodeSettings.mining => - sender() ! Future.failed(new Exception("Candidate creation is not supported when mining is disabled")) + case PrepareCandidate(_, reply) if !ergoSettings.nodeSettings.mining => + if (reply) { + sender() ! StatusReply.error("Candidate creation is not supported when mining is disabled") + } case PrepareCandidate(txsToInclude, reply) => - val candF: Future[CandidateCache] = if (candidateGenerating) { - Future.failed(new Exception("Skipping candidate generation, one is already in progress")) + val msgSender = if (reply) Some(sender()) else None + if (candidateGenerating) { + msgSender.foreach(_ ! StatusReply.error("Skipping candidate generation, one is already in progress")) } else { candidateGenerating = true - val f = if (cachedFor(txsToInclude)) { - candidateOpt.fold[Future[CandidateCache]]( - Future.failed(new Exception("Failed to create candidate")))(Future.successful) + if (cachedFor(txsToInclude)) { + msgSender.foreach { s => + s ! candidateOpt.fold[StatusReply[WorkMessage]]( + StatusReply.error("Failed to create candidate") + )(c => StatusReply.success(c.externalVersion)) + } + candidateGenerating = false } else { log.info("Generating new candidate requested by external miner") - val readersR = (readersHolderRef ? GetReaders).mapTo[Readers] - readersR.flatMap { - case Readers(h, s: UtxoStateReader, m, _) => - Future.fromTry(generateCandidate(h, m, s, txsToInclude)) - case _ => - Future.failed(new Exception("Invalid readers state, mining is possible in UTXO mode only")) - } + (readersHolderRef ? GetReaders).mapTo[Readers] + .onComplete { + case Success(Readers(h, s: UtxoStateReader, m, _)) => + val candidateVersionReply = + generateCandidate(h, m, s, txsToInclude).fold( + ex => StatusReply.error(ex), + candidate => StatusReply.success(candidate.externalVersion) + ) + candidateGenerating = false + msgSender.foreach(_ ! candidateVersionReply) + case _ => + candidateGenerating = false + msgSender.foreach(_ ! StatusReply.error("Invalid readers state, mining is possible in UTXO mode only")) + } } - f.onComplete(_ => candidateGenerating = false) - f - } - if (reply) { - sender() ! candF.map(_.externalVersion) } // solution found externally (by e.g. GPU miner) @@ -325,14 +333,14 @@ class ErgoMiner(ergoSettings: ErgoSettings, preSolution } log.info("Got solution: " + solution) - val result: Future[Unit] = + val result: StatusReply[Unit] = if (solvedBlock.nonEmpty) { log.info("Duplicate solution: " + solution) refreshCandidate() - Future.failed(new Exception("Solution already submitted")) + StatusReply.error("Solution already submitted") } else if (publicKeyOpt.isEmpty) { log.warn("Got a solution, but no pubkey is set") - Future.failed(new Exception("No pubkey is set")) + StatusReply.error("No pubkey is set") } else { candidateOpt.map { c => val newBlock = completeBlock(c.candidateBlock, solution) @@ -342,13 +350,13 @@ class ErgoMiner(ergoSettings: ErgoSettings, case Some(Success(newBlock)) => sendToNodeView(newBlock) solvedBlock = Some(newBlock.header) - Future.successful(()) + StatusReply.success(()) case Some(Failure(exception)) => refreshCandidate() - Future.failed(new Exception(s"Invalid block mined: ${exception.getMessage}", exception)) + StatusReply.error(new Exception(s"Invalid block mined: ${exception.getMessage}", exception)) case None => refreshCandidate() - Future.failed(new Exception("Invalid miner state")) + StatusReply.error("Invalid miner state") } } log.debug(s"Processed solution $solution with the result result $result") diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala index 6333dcc0fc..548e1ab32a 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala @@ -29,10 +29,8 @@ import sigmastate.basics.DLogProtocol.DLogProverInput import sigmastate.utxo.CostTable import scala.annotation.tailrec -import scala.concurrent.Future import scala.concurrent.duration._ import scala.language.postfixOps -import scala.util.{Failure, Success, Try} class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGenerators with Eventually { @@ -41,12 +39,9 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen val newBlockSignal: Class[SemanticallySuccessfulModifier[_]] = classOf[SemanticallySuccessfulModifier[_]] val newBlockDelay: FiniteDuration = 30 seconds - @tailrec private def getWorkMessage(minerRef: ActorRef, mandatoryTransactions: Seq[ErgoTransaction]): WorkMessage = { - Try(await((minerRef ? PrepareCandidate(mandatoryTransactions)).mapTo[Future[WorkMessage]].flatten)) match { - case Success(wm) => wm - case Failure(_) => getWorkMessage(minerRef, mandatoryTransactions) - } + implicit val patienceConfig: PatienceConfig = PatienceConfig(1.seconds, 50.millis) + eventually(await(minerRef.askWithStatus(PrepareCandidate(mandatoryTransactions)).mapTo[WorkMessage])) } val defaultSettings: ErgoSettings = { @@ -70,7 +65,6 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen } complexScript.complexity shouldBe 28077 - val nodeViewHolderRef: ActorRef = ErgoNodeViewRef(ergoSettings, timeProvider) val readersHolderRef: ActorRef = ErgoReadersHolderRef(nodeViewHolderRef) @@ -84,9 +78,6 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen 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(newBlockDelay, newBlockSignal) @@ -286,7 +277,7 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen val passiveMiner: ActorRef = minerRef - val wm = await((passiveMiner ? PrepareCandidate(Seq.empty)).mapTo[Future[WorkMessage]].flatten) + val wm = await(passiveMiner.askWithStatus(PrepareCandidate(Seq.empty)).mapTo[WorkMessage]) wm.isInstanceOf[WorkMessage] shouldBe true system.terminate() } @@ -309,9 +300,6 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen 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(newBlockDelay, newBlockSignal) diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 6519409e23..c2827e9df6 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -1,6 +1,7 @@ package org.ergoplatform.utils import akka.actor.{Actor, ActorRef, ActorSystem, Props} +import akka.pattern.StatusReply import org.bouncycastle.util.BigIntegers import org.ergoplatform.mining.{AutolykosSolution, ErgoMiner, WorkMessage} import org.ergoplatform.modifiers.ErgoFullBlock @@ -99,9 +100,9 @@ trait Stubs extends ErgoGenerators with ErgoTestHelpers with ChainGenerator with class MinerStub extends Actor { def receive: Receive = { case ErgoMiner.PrepareCandidate(_, reply) => if (reply) { - sender() ! Future.successful(externalCandidateBlock) + sender() ! StatusReply.success(externalCandidateBlock) } - case _: AutolykosSolution => sender() ! Future.successful(()) + case _: AutolykosSolution => sender() ! StatusReply.success(()) case ErgoMiner.ReadMinerPk => sender() ! Some(pk) } } From 2ca504f7be546a2ffaf84195c6a6e4c937c188f0 Mon Sep 17 00:00:00 2001 From: pragmaxim Date: Tue, 20 Apr 2021 13:36:01 +0200 Subject: [PATCH 03/15] more ErgoMiner logs --- src/main/scala/org/ergoplatform/ErgoApp.scala | 1 + src/main/scala/org/ergoplatform/mining/ErgoMiner.scala | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/scala/org/ergoplatform/ErgoApp.scala b/src/main/scala/org/ergoplatform/ErgoApp.scala index 2ccf4e004c..6af5fadd06 100644 --- a/src/main/scala/org/ergoplatform/ErgoApp.scala +++ b/src/main/scala/org/ergoplatform/ErgoApp.scala @@ -135,6 +135,7 @@ class ErgoApp(args: Args) extends ScorexLogging { // Useful for local blockchains (devnet) if (ergoSettings.nodeSettings.mining && ergoSettings.nodeSettings.offlineGeneration) { require(minerRefOpt.isDefined, "Miner does not exist but mining = true in config") + log.info(s"Starting mining with offlineGeneration") minerRefOpt.get ! StartMining } diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala index 16b5152c53..7152a76eb9 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala @@ -94,9 +94,10 @@ class ErgoMiner(ergoSettings: ErgoSettings, override def preStart(): Unit = { if (secretKeyOpt.isEmpty && !externalMinerMode) { + log.info("Trying to use secret key from wallet for mining, wallet must be unlocked.") self ! QueryWallet(secret = true) } else if (publicKeyOpt.isEmpty) { // mining pubKey is needed in both modes - log.info("Trying to use key from wallet for mining") + log.info("Trying to use public key from wallet for mining") self ! QueryWallet(secret = false) } context.system.eventStream.subscribe(self, classOf[SemanticallySuccessfulModifier[_]]) @@ -208,7 +209,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, * This block could be either genesis or generated by another node. */ case SemanticallySuccessfulModifier(mod: ErgoFullBlock) if shouldStartMine(mod) => - log.info("Starting mining") + log.info("Starting mining triggered by incoming block") self ! StartMining /** From 684c1c00321f28459bf8724d0b7b5deac6cb08ac Mon Sep 17 00:00:00 2001 From: pragmaxim Date: Tue, 20 Apr 2021 20:24:00 +0200 Subject: [PATCH 04/15] improving ErgoMiner logs for easier manual testing --- .../org/ergoplatform/mining/ErgoMiner.scala | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala index 7152a76eb9..2463c66e0d 100644 --- a/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala +++ b/src/main/scala/org/ergoplatform/mining/ErgoMiner.scala @@ -158,6 +158,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, private def startMining: Receive = { case StartMining if candidateOpt.isEmpty => if (secretKeyOpt.isDefined || externalMinerMode) { + log.info(s"Preparing new candidate") refreshCandidate() } context.system.scheduler.scheduleOnce(1.seconds, self, StartMining)(context.system.dispatcher) @@ -168,10 +169,10 @@ class ErgoMiner(ergoSettings: ErgoSettings, case Some(_) => isMining = true if (!externalMinerMode) { - log.info("Starting native miner") + log.info("Starting native miner and passing it candidate") startInternalMiner(candidate.candidateBlock) } else { - log.info("Ready to serve external miner") + log.info("Preparing new candidate for external miner") // Refresh candidate block if it was formed before NVH state restore in response to API requests refreshCandidate() } @@ -179,6 +180,8 @@ class ErgoMiner(ergoSettings: ErgoSettings, log.warn("Got start mining command while public key is not ready") } } + case StartMining => + // Miner is busy or mining disabled } private def needNewCandidate(b: ErgoFullBlock): Boolean = { @@ -198,7 +201,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, * Stop all current threads and re-run them with newly produced candidate. */ case SemanticallySuccessfulModifier(mod: ErgoFullBlock) if isMining && needNewCandidate(mod) => - log.info(s"Producing new candidate on getting new block at ${mod.height}") + log.info(s"Preparing new candidate on getting new block at ${mod.height}") refreshCandidate() /** @@ -304,7 +307,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, } candidateGenerating = false } else { - log.info("Generating new candidate requested by external miner") + log.info("Generating new candidate requested by miner") (readersHolderRef ? GetReaders).mapTo[Readers] .onComplete { case Success(Readers(h, s: UtxoStateReader, m, _)) => @@ -336,7 +339,7 @@ class ErgoMiner(ergoSettings: ErgoSettings, log.info("Got solution: " + solution) val result: StatusReply[Unit] = if (solvedBlock.nonEmpty) { - log.info("Duplicate solution: " + solution) + log.info("Preparing new candidate due to duplicate solution: " + solution) refreshCandidate() StatusReply.error("Solution already submitted") } else if (publicKeyOpt.isEmpty) { @@ -353,9 +356,11 @@ class ErgoMiner(ergoSettings: ErgoSettings, solvedBlock = Some(newBlock.header) StatusReply.success(()) case Some(Failure(exception)) => + log.warn(s"Preparing new candidate due to invalid block ${exception.getMessage}") refreshCandidate() StatusReply.error(new Exception(s"Invalid block mined: ${exception.getMessage}", exception)) case None => + log.warn("Preparing new candidate due to invalid miner state") refreshCandidate() StatusReply.error("Invalid miner state") } @@ -478,7 +483,6 @@ class ErgoMiner(ergoSettings: ErgoSettings, }.flatten private def refreshCandidate(): Unit = { - log.info("Requesting candidate") candidateOpt = None candidateGenerating = false self ! PrepareCandidate(Seq.empty, reply = false) From ae1ec45c01dd5942864cd91a3837f16731e4ea1f Mon Sep 17 00:00:00 2001 From: NoTelos Date: Tue, 11 May 2021 01:09:19 -0700 Subject: [PATCH 05/15] Add CLI help and usage information A new CLI arg parsing library is used as argyle cannot print help/usage information --- build.sbt | 2 +- src/main/scala/org/ergoplatform/ErgoApp.scala | 39 ++++++++++++++----- .../org/ergoplatform/settings/Args.scala | 6 +-- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/build.sbt b/build.sbt index 266e86b6e7..2175b4779e 100644 --- a/build.sbt +++ b/build.sbt @@ -56,7 +56,7 @@ libraryDependencies ++= Seq( "com.iheart" %% "ficus" % "1.4.7", "ch.qos.logback" % "logback-classic" % "1.2.3", "com.google.guava" % "guava" % "21.0", - "com.joefkelley" %% "argyle" % "1.0.0", + "com.github.scopt" %% "scopt" % "4.0.1", "org.scala-lang.modules" %% "scala-async" % "0.9.7" % "test", "com.storm-enroute" %% "scalameter" % "0.8.+" % "test", diff --git a/src/main/scala/org/ergoplatform/ErgoApp.scala b/src/main/scala/org/ergoplatform/ErgoApp.scala index 2ccf4e004c..85ead8cf42 100644 --- a/src/main/scala/org/ergoplatform/ErgoApp.scala +++ b/src/main/scala/org/ergoplatform/ErgoApp.scala @@ -192,16 +192,37 @@ class ErgoApp(args: Args) extends ScorexLogging { object ErgoApp extends ScorexLogging { - import com.joefkelley.argyle._ - - val argParser: Arg[Args] = ( - optional[String]("--config", "-c") and - optionalOneOf[NetworkType](NetworkType.all.map(x => s"--${x.verboseName}" -> x): _*) - ).to[Args] + import scopt.OParser + + val builder = OParser.builder[Args] + val argParser = { + import builder._ + OParser.sequence( + programName("ergo"), + opt[String]("config") + .abbr("c") + .action((x, c) => c.copy(userConfigPathOpt = Some(x))) + .text("location of ergo node configuration") + .optional(), + opt[Unit]("devnet") + .action((_, c) => c.copy(networkTypeOpt = Some(NetworkType.DevNet))) + .text("set network to devnet") + .optional(), + opt[Unit]("testnet") + .action((_, c) => c.copy(networkTypeOpt = Some(NetworkType.TestNet))) + .text("set network to testnet") + .optional(), + opt[Unit]("mainnet") + .action((_, c) => c.copy(networkTypeOpt = Some(NetworkType.MainNet))) + .text("set network to mainnet") + .optional(), + help("help").text("prints this usage text") + ) + } - def main(args: Array[String]): Unit = argParser.parse(args) match { - case Success(argsParsed) => new ErgoApp(argsParsed).run() - case Failure(e) => throw e + def main(args: Array[String]): Unit = OParser.parse(argParser, args, Args()) match { + case Some(argsParsed) => new ErgoApp(argsParsed).run() + case None => // Error message will be displayed when arguments are bad } def forceStopApplication(code: Int = 1): Nothing = sys.exit(code) diff --git a/src/main/scala/org/ergoplatform/settings/Args.scala b/src/main/scala/org/ergoplatform/settings/Args.scala index 35411da46f..f12fd9a1c6 100644 --- a/src/main/scala/org/ergoplatform/settings/Args.scala +++ b/src/main/scala/org/ergoplatform/settings/Args.scala @@ -1,7 +1,7 @@ package org.ergoplatform.settings - -final case class Args(userConfigPathOpt: Option[String], - networkTypeOpt: Option[NetworkType]) +final case class Args( + userConfigPathOpt: Option[String] = None, + networkTypeOpt: Option[NetworkType] = None) object Args { def empty: Args = Args(None, None) From 808e373763ebf2fa0295518cd1e45050c3e4a804 Mon Sep 17 00:00:00 2001 From: NoTelos Date: Wed, 12 May 2021 16:28:55 -0700 Subject: [PATCH 06/15] Use scopt object oriented DSL for CLI arg parsing --- src/main/scala/org/ergoplatform/ErgoApp.scala | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/main/scala/org/ergoplatform/ErgoApp.scala b/src/main/scala/org/ergoplatform/ErgoApp.scala index 85ead8cf42..be8332445e 100644 --- a/src/main/scala/org/ergoplatform/ErgoApp.scala +++ b/src/main/scala/org/ergoplatform/ErgoApp.scala @@ -192,35 +192,28 @@ class ErgoApp(args: Args) extends ScorexLogging { object ErgoApp extends ScorexLogging { - import scopt.OParser - - val builder = OParser.builder[Args] - val argParser = { - import builder._ - OParser.sequence( - programName("ergo"), + val argParser = new scopt.OptionParser[Args]("ergo") { opt[String]("config") .abbr("c") .action((x, c) => c.copy(userConfigPathOpt = Some(x))) .text("location of ergo node configuration") - .optional(), + .optional() opt[Unit]("devnet") .action((_, c) => c.copy(networkTypeOpt = Some(NetworkType.DevNet))) .text("set network to devnet") - .optional(), + .optional() opt[Unit]("testnet") .action((_, c) => c.copy(networkTypeOpt = Some(NetworkType.TestNet))) .text("set network to testnet") - .optional(), + .optional() opt[Unit]("mainnet") .action((_, c) => c.copy(networkTypeOpt = Some(NetworkType.MainNet))) .text("set network to mainnet") - .optional(), + .optional() help("help").text("prints this usage text") - ) } - def main(args: Array[String]): Unit = OParser.parse(argParser, args, Args()) match { + def main(args: Array[String]): Unit = argParser.parse(args, Args()) match { case Some(argsParsed) => new ErgoApp(argsParsed).run() case None => // Error message will be displayed when arguments are bad } From f5fb72d39eca3bb2d7c3f36aca42256952188bcc Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Thu, 13 May 2021 11:22:50 +0300 Subject: [PATCH 07/15] version set to 4.0.11 --- README.md | 2 +- src/main/resources/api/openapi.yaml | 2 +- src/main/resources/mainnet.conf | 2 +- src/main/resources/testnet.conf | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d1e09ed80c..4e1095ed31 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ To run specific Ergo version `` as a service with custom config `/path/ -e MAX_HEAP=3G \ 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, `v4.0.10`. +Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v4.0.11`. This will connect to the Ergo mainnet or testnet following your configuration passed in `myergo.conf` and network flag `--`. Every default config value would be overwritten with corresponding value in `myergo.conf`. `MAX_HEAP` variable can be used to control how much memory can the node consume. diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index 6157269640..d065ed37b0 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.2" info: - version: "4.0.10" + version: "4.0.11" title: Ergo Node API description: API docs for Ergo Node. Models are shared between all Ergo products contact: diff --git a/src/main/resources/mainnet.conf b/src/main/resources/mainnet.conf index 24aee4fb23..f7ea4d47e0 100644 --- a/src/main/resources/mainnet.conf +++ b/src/main/resources/mainnet.conf @@ -38,7 +38,7 @@ scorex { network { magicBytes = [1, 0, 2, 4] bindAddress = "0.0.0.0:9030" - nodeName = "ergo-mainnet-4.0.10" + nodeName = "ergo-mainnet-4.0.11" nodeName = ${?NODENAME} knownPeers = [ "213.239.193.208:9030", diff --git a/src/main/resources/testnet.conf b/src/main/resources/testnet.conf index d2a2e60332..b9cc22eeed 100644 --- a/src/main/resources/testnet.conf +++ b/src/main/resources/testnet.conf @@ -52,7 +52,7 @@ scorex { network { magicBytes = [2, 0, 0, 1] bindAddress = "0.0.0.0:9020" - nodeName = "ergo-testnet-4.0.10" + nodeName = "ergo-testnet-4.0.11" nodeName = ${?NODENAME} knownPeers = [ "213.239.193.208:9020" From 11a16c64935a7ce049602adeb38dcc2ac0841d26 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 18 May 2021 01:33:40 +0300 Subject: [PATCH 08/15] zero-fee-tx: optional fees --- .../transactions/TransactionBuilder.scala | 48 +++++++++++-------- .../transactions/TransactionBuilderSpec.scala | 31 ++++++++++-- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala index 309705cc41..ad036c2088 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala @@ -49,17 +49,17 @@ object TransactionBuilder { require(inputs.distinct.size == inputs.size, s"There should be no duplicate inputs") } - /** Creates unsigned transaction from given inputs and outputs adding outputs with miner's fee and change * Runs required checks ensuring that resulted transaction will be successfully validated by a node. * - * @param inputs - input boxes - * @param dataInputs - data inputs + * @param inputs - input boxes + * @param dataInputs - data inputs * @param outputCandidates - output candidate boxes - * @param currentHeight - current height (used in miner's fee box and change box) - * @param feeAmount - fee amount to put in miner's fee box - * @param changeAddress - address where to send change from the input boxes - * @param minChangeValue - minimum change value to send, otherwise add to miner's fee + * @param currentHeight - current height (used in miner's fee box and change box) + * @param createFeeOutput - optional fee amount to put in a new miner's fee box, which will be + * created by this method. If None, then feeOut is not created. + * @param changeAddress - address where to send change from the input boxes + * @param minChangeValue - minimum change value to send, otherwise add to miner's fee * @param minerRewardDelay - reward delay to encode in miner's fee box * @return unsigned transaction */ @@ -68,7 +68,7 @@ object TransactionBuilder { dataInputs: IndexedSeq[DataInput], outputCandidates: Seq[ErgoBoxCandidate], currentHeight: Int, - feeAmount: Long, + createFeeOutput: Option[Long], changeAddress: ErgoAddress, minChangeValue: Long, minerRewardDelay: Int, @@ -80,7 +80,8 @@ object TransactionBuilder { // TODO: implement all appropriate checks from ErgoTransaction.validateStatefull - require(feeAmount > 0, s"expected fee amount > 0, got $feeAmount") + val feeAmount = createFeeOutput.getOrElse(0L) + require(createFeeOutput.fold(true)(_ > 0), s"expected fee amount > 0, got $feeAmount") val inputTotal = inputs.map(_.value).sum val outputSum = outputCandidates.map(_.value).sum val outputTotal = outputSum + feeAmount @@ -110,17 +111,24 @@ object TransactionBuilder { val changeBoxes = selection.changeBoxes val changeBoxesHaveTokens = changeBoxes.exists(_.tokens.nonEmpty) - val noChange = changeAmt < minChangeValue && !changeBoxesHaveTokens - - // if computed changeAmt is too small give it to miner as tips - val actualFee = if (noChange) feeAmount + changeAmt else feeAmount - val feeOut = new ErgoBoxCandidate( - actualFee, - ErgoScriptPredef.feeProposition(minerRewardDelay), - currentHeight - ) + val changeGoesToFee = changeAmt < minChangeValue && !changeBoxesHaveTokens + + require(!changeGoesToFee || (changeAmt == 0 || createFeeOutput.isDefined), + s"""When change=$changeAmt < minChangeValue=$minChangeValue it is added to miner's fee, + |it this case createFeeOutput should be defined + |""".stripMargin) + + val feeOutOpt = createFeeOutput.map { fee => + // if computed changeAmt is too small give it to miner as tips + val actualFee = if (changeGoesToFee) fee + changeAmt else fee + new ErgoBoxCandidate( + actualFee, + ErgoScriptPredef.feeProposition(minerRewardDelay), + currentHeight + ) + } - val addedChangeOut = if (!noChange) { + val addedChangeOut = if (!changeGoesToFee) { val script = changeAddress.script changeBoxes.map { cb => new ErgoBoxCandidate(cb.value, script, currentHeight, tokensMapToColl(cb.tokens)) @@ -129,7 +137,7 @@ object TransactionBuilder { Seq() } - val finalOutputCandidates = outputCandidates ++ Seq(feeOut) ++ addedChangeOut + val finalOutputCandidates = outputCandidates ++ feeOutOpt ++ addedChangeOut new UnsignedErgoLikeTransaction( inputs.map(b => new UnsignedInput(b.id)), diff --git a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/transactions/TransactionBuilderSpec.scala b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/transactions/TransactionBuilderSpec.scala index 3a1e7f4103..0bb37b27f7 100644 --- a/ergo-wallet/src/test/scala/org/ergoplatform/wallet/transactions/TransactionBuilderSpec.scala +++ b/ergo-wallet/src/test/scala/org/ergoplatform/wallet/transactions/TransactionBuilderSpec.scala @@ -5,12 +5,13 @@ import sigmastate.Values.SigmaPropValue import sigmastate.eval._ import sigmastate.eval.Extensions._ import sigmastate.helpers.TestingHelpers._ +import sigmastate.utils.Helpers._ import org.ergoplatform._ import org.scalatest.Matchers import org.ergoplatform.ErgoBox.TokenId import org.ergoplatform.wallet.TokensMap -import scala.util.{Success, Try} +import scala.util.{Success, Try, Failure} import scorex.crypto.hash.Digest32 import org.ergoplatform.wallet.mnemonic.Mnemonic import org.ergoplatform.wallet.secrets.ExtendedSecretKey @@ -51,17 +52,17 @@ class TransactionBuilderSpec extends WalletTestHelpers with Matchers { def transaction(inputBox: ErgoBox, outBox: ErgoBoxCandidate, + fee: Option[Long] = Some(minBoxValue), burnTokens: TokensMap = Map.empty): Try[UnsignedErgoLikeTransaction] = { val ins = IndexedSeq(inputBox) val outs = IndexedSeq(outBox) val changeAddress = P2PKAddress(rootSecret.privateInput.publicImage) - val fee = minBoxValue val res = buildUnsignedTx( inputs = ins, dataInputs = IndexedSeq(), outputCandidates = outs, currentHeight = currentHeight, - feeAmount = fee, + createFeeOutput = fee, changeAddress = changeAddress, minChangeValue = minChangeValue, minerRewardDelay = minerRewardDelay, @@ -100,4 +101,28 @@ class TransactionBuilderSpec extends WalletTestHelpers with Matchers { val remainingTokens = Map(tid1 -> 600L, tid2 -> 1200L) TransactionBuilder.collTokensToMap(out2.additionalTokens) shouldBe remainingTokens } + + property("no fees") { + val inputBox = box(minBoxValue) + val tokenId = Digest32 @@ inputBox.id + val outBox = boxCandidate(minBoxValue, Seq(tokenId -> 100L)) + val res = transaction(inputBox, outBox, fee = None) + + res shouldBe a[Success[_]] + val tx = res.get + tx.outputCandidates.size shouldBe 1 + tx.outputCandidates(0) shouldEqual outBox + } + + property("change goes to fee, but no outFee box") { + val inputBox = box(minBoxValue + minBoxValue / 2) + val tokenId = Digest32 @@ inputBox.id + val outBox = boxCandidate(minBoxValue, Seq(tokenId -> 100L)) + val res = transaction(inputBox, outBox, fee = None) + + assertExceptionThrown( + res.getOrThrow, + t => t.getMessage.contains("createFeeOutput should be defined")) + } + } From 84bc4f14ad669735058a1aba8bfb4a3d5d61b3f8 Mon Sep 17 00:00:00 2001 From: pragmaxim Date: Tue, 18 May 2021 10:00:35 +0200 Subject: [PATCH 09/15] increase akka-http-server timeout --- src/main/resources/application.conf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index fe82d031a2..97c77b1821 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -242,6 +242,14 @@ ergo { } } +akka { + http { + server { + request-timeout = 1 minute + } + } +} + scorex { # Execution context for all tasks, except of akka actors. From 323dd2ce3149db3758df03f7cf68242bae53bc85 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Thu, 20 May 2021 13:05:38 +0300 Subject: [PATCH 10/15] Update ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala Co-authored-by: Denys Zadorozhnyi --- .../ergoplatform/wallet/transactions/TransactionBuilder.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala index ad036c2088..1dea5eb371 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/wallet/transactions/TransactionBuilder.scala @@ -115,7 +115,7 @@ object TransactionBuilder { require(!changeGoesToFee || (changeAmt == 0 || createFeeOutput.isDefined), s"""When change=$changeAmt < minChangeValue=$minChangeValue it is added to miner's fee, - |it this case createFeeOutput should be defined + |in this case createFeeOutput should be defined |""".stripMargin) val feeOutOpt = createFeeOutput.map { fee => From f56e3df8814a5253e4fd1deb2008204a47054b2e Mon Sep 17 00:00:00 2001 From: Kostis Karantias Date: Fri, 21 May 2021 20:05:28 +0300 Subject: [PATCH 11/15] Make docker build use reproducible build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f6124c02c1..302697bd89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ COPY benchmarks/build.sbt benchmarks/build.sbt RUN sbt update COPY . ./ RUN sbt assembly -RUN mv `find . -name ergo-*.jar` ergo.jar +RUN mv `find target/scala-*/stripped/ -name ergo-*.jar` ergo.jar FROM openjdk:11-jre-slim RUN adduser --disabled-password --home /home/ergo --uid 9052 --gecos "ErgoPlatform" ergo && \ From 966c2bf46700d1f7a0721f2913d243283040ba4f Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Sun, 23 May 2021 15:49:23 +0300 Subject: [PATCH 12/15] Scorex version updated to a6fe1419 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 4871d07541..aec156f208 100644 --- a/build.sbt +++ b/build.sbt @@ -32,7 +32,7 @@ val circeVersion = "0.13.0" val akkaVersion = "2.6.10" val akkaHttpVersion = "10.2.1" -val scorexVersion = "master-7d7d9bb7-SNAPSHOT" +val scorexVersion = "master-a6fe1419-SNAPSHOT" val sigmaStateVersion = "4.0.3" // for testing current sigmastate build (see sigmastate-ergo-it jenkins job) From 5754fb65b4a0016244bb4bb45a9939ddb5594154 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Sun, 23 May 2021 17:01:19 +0300 Subject: [PATCH 13/15] main branch for Scorex dep, new magic for the testnet --- build.sbt | 2 +- src/main/resources/testnet.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sbt b/build.sbt index aec156f208..d5864b9faf 100644 --- a/build.sbt +++ b/build.sbt @@ -32,7 +32,7 @@ val circeVersion = "0.13.0" val akkaVersion = "2.6.10" val akkaHttpVersion = "10.2.1" -val scorexVersion = "master-a6fe1419-SNAPSHOT" +val scorexVersion = "main-a6fe1419-SNAPSHOT" val sigmaStateVersion = "4.0.3" // for testing current sigmastate build (see sigmastate-ergo-it jenkins job) diff --git a/src/main/resources/testnet.conf b/src/main/resources/testnet.conf index b9cc22eeed..01f440c696 100644 --- a/src/main/resources/testnet.conf +++ b/src/main/resources/testnet.conf @@ -50,7 +50,7 @@ ergo { scorex { network { - magicBytes = [2, 0, 0, 1] + magicBytes = [2, 0, 0, 2] bindAddress = "0.0.0.0:9020" nodeName = "ergo-testnet-4.0.11" nodeName = ${?NODENAME} From 8c561864ccec68dd119da0677140ba2298d6f3fc Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Sun, 23 May 2021 17:15:50 +0300 Subject: [PATCH 14/15] 37.156.20.158:9020 added to testnet seed nodes --- src/main/resources/testnet.conf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/resources/testnet.conf b/src/main/resources/testnet.conf index 01f440c696..c729ea7be1 100644 --- a/src/main/resources/testnet.conf +++ b/src/main/resources/testnet.conf @@ -55,7 +55,8 @@ scorex { nodeName = "ergo-testnet-4.0.11" nodeName = ${?NODENAME} knownPeers = [ - "213.239.193.208:9020" + "213.239.193.208:9020", + "37.156.20.158:9020" ] } restApi { From cbe4ba2172014bc91ac43c86731630635048ccf2 Mon Sep 17 00:00:00 2001 From: Alex Chepurnoy Date: Mon, 24 May 2021 13:38:26 +0300 Subject: [PATCH 15/15] log improved for secret storage failure case --- .../org/ergoplatform/nodeView/wallet/ErgoWalletService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala index 03b3420b16..6fd8e839ea 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala @@ -238,7 +238,7 @@ class ErgoWalletServiceImpl extends ErgoWalletService with ErgoWalletSupport { log.info("Trying to read wallet in secure mode ..") JsonSecretStorage.readFile(secretStorageSettings).fold( e => { - log.warn(s"Failed to read wallet. Manual initialization is required to sign transactions. Cause: ${e.getCause}") + log.warn(s"Failed to read wallet. Manual initialization is required. Details: ", e) state }, secretStorage => {