From 5ecb5206a0abe9d1a1b0872b59832a13a4ff2579 Mon Sep 17 00:00:00 2001 From: jellymlg Date: Sat, 18 Feb 2023 17:02:58 +0100 Subject: [PATCH 1/8] fixed log format --- src/main/scala/org/ergoplatform/local/MempoolAuditor.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala index 6578613c5e..800f3974a7 100644 --- a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala +++ b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala @@ -101,15 +101,15 @@ class MempoolAuditor(nodeViewHolderRef: ActorRef, val stateToCheck = utxoState.withUnconfirmedTransactions(toBroadcast) toBroadcast.foreach { unconfirmedTx => if (unconfirmedTx.transaction.inputIds.forall(inputBoxId => stateToCheck.boxById(inputBoxId).isDefined)) { - log.info(s"Rebroadcasting $unconfirmedTx") + log.info(s"Rebroadcasting ${unconfirmedTx.id}") broadcastTx(unconfirmedTx) } else { - log.info(s"Not rebroadcasting $unconfirmedTx as not all the inputs are in place") + log.info(s"Not rebroadcasting ${unconfirmedTx.id} as not all the inputs are in place") } } case _ => toBroadcast.foreach { unconfirmedTx => - log.warn(s"Rebroadcasting $unconfirmedTx while state is not ready or not UTXO set") + log.warn(s"Rebroadcasting ${unconfirmedTx.id} while state is not ready or not UTXO set") broadcastTx(unconfirmedTx) } } From 65b20b3b4944b4f9466e7417a26e386a3154aa5d Mon Sep 17 00:00:00 2001 From: jellymlg Date: Sat, 18 Feb 2023 17:03:54 +0100 Subject: [PATCH 2/8] reworked OrderedTxPool and made test --- .../nodeView/mempool/ErgoMemPool.scala | 14 +--- .../nodeView/mempool/OrderedTxPool.scala | 76 +++++++------------ .../nodeView/mempool/ErgoMemPoolSpec.scala | 35 +++++++++ 3 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala index 8e5431c369..601a50a383 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala @@ -102,7 +102,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } private def updateStatsOnRemoval(tx: ErgoTransaction): MemPoolStatistics = { - val wtx = pool.transactionsRegistry.get(tx.id) + val wtx = pool.getWtx(tx.id) wtx.map(wgtx => stats.add(System.currentTimeMillis(), wgtx)) .getOrElse(MemPoolStatistics(System.currentTimeMillis(), 0, System.currentTimeMillis())) } @@ -133,14 +133,8 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, pool.get(unconfirmedTransactionId) match { case Some(utx) => invalidate(utx) case None => - log.warn(s"pool.get failed for $unconfirmedTransactionId") - pool.orderedTransactions.valuesIterator.find(_.id == unconfirmedTransactionId) match { - case Some(utx) => - invalidate(utx) - case None => - log.warn(s"Can't invalidate transaction $unconfirmedTransactionId as it is not in the pool") - this - } + log.warn(s"Can't invalidate transaction $unconfirmedTransactionId as it is not in the pool") + this } } @@ -181,7 +175,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, val doubleSpendingTotalWeight = doubleSpendingWtxs.map(_.weight).sum / doubleSpendingWtxs.size if (ownWtx.weight > doubleSpendingTotalWeight) { val doubleSpendingTxs = doubleSpendingWtxs.map(wtx => pool.orderedTransactions(wtx)).toSeq - val p = pool.put(unconfirmedTransaction, feeF).remove(doubleSpendingTxs) + val p = pool.remove(doubleSpendingTxs).put(unconfirmedTransaction, feeF) val updPool = new ErgoMemPool(p, stats, sortingOption) updPool -> new ProcessingOutcome.Accepted(unconfirmedTransaction, validationStartTime) } else { diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala index 127edd8201..c5b316e45f 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala @@ -12,13 +12,11 @@ import scala.collection.immutable.TreeMap * An immutable pool of transactions of limited size with priority management and blacklisting support. * * @param orderedTransactions - collection containing transactions ordered by `tx.weight` - * @param transactionsRegistry - mapping `tx.id` -> `WeightedTxId(tx.id,tx.weight)` required for getting transaction by its `id` * @param invalidatedTxIds - invalidated transaction ids in bloom filters * @param outputs - mapping `box.id` -> `WeightedTxId(tx.id,tx.weight)` required for getting a transaction by its output box * @param inputs - mapping `box.id` -> `WeightedTxId(tx.id,tx.weight)` required for getting a transaction by its input box id */ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTransaction], - val transactionsRegistry: TreeMap[ModifierId, WeightedTxId], val invalidatedTxIds: ApproximateCacheLike[String], val outputs: TreeMap[BoxId, WeightedTxId], val inputs: TreeMap[BoxId, WeightedTxId]) @@ -43,14 +41,11 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr def size: Int = orderedTransactions.size - def get(id: ModifierId): Option[UnconfirmedTransaction] = { - transactionsRegistry.get(id).flatMap { wtx => - orderedTransactions.get(wtx) match { - case s@Some(_) => s - case None => log.warn(s"Found $id in registry but not ordered transactions"); None - } - } - } + def getWtx(id: ModifierId): Option[WeightedTxId] = + orderedTransactions.keySet.find(_.id == id) + + def get(id: ModifierId): Option[UnconfirmedTransaction] = + orderedTransactions.get(WeightedTxId(id, 0, 0, 0)) /** @@ -67,25 +62,23 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr def put(unconfirmedTx: UnconfirmedTransaction, feeFactor: Int): OrderedTxPool = { val tx = unconfirmedTx.transaction - val newPool = transactionsRegistry.get(tx.id) match { - case Some(wtx) => - new OrderedTxPool( - orderedTransactions.updated(wtx, unconfirmedTx), - transactionsRegistry, - invalidatedTxIds, - outputs, - inputs - ) - case None => - val wtx = weighted(tx, feeFactor) - new OrderedTxPool( - orderedTransactions.updated(wtx, unconfirmedTx), - transactionsRegistry.updated(wtx.id, wtx), - invalidatedTxIds, - outputs ++ tx.outputs.map(_.id -> wtx), - inputs ++ tx.inputs.map(_.boxId -> wtx) - ).updateFamily(tx, wtx.weight, System.currentTimeMillis(), 0) + val newPool = if(contains(tx.id)) { + new OrderedTxPool( + orderedTransactions.updated(getWtx(tx.id).get, unconfirmedTx), + invalidatedTxIds, + outputs, + inputs + ) + }else { + val wtx = weighted(tx, feeFactor) + new OrderedTxPool( + orderedTransactions.updated(wtx, unconfirmedTx), + invalidatedTxIds, + outputs ++ tx.outputs.map(_.id -> wtx), + inputs ++ tx.inputs.map(_.boxId -> wtx) + ).updateFamily(tx, wtx.weight, System.currentTimeMillis(), 0) } + if (newPool.orderedTransactions.size > mempoolCapacity) { val victim = newPool.orderedTransactions.last._2 newPool.remove(victim) @@ -104,11 +97,10 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr * @param tx - Transaction to remove */ def remove(tx: ErgoTransaction): OrderedTxPool = { - transactionsRegistry.get(tx.id) match { + getWtx(tx.id) match { case Some(wtx) => new OrderedTxPool( orderedTransactions - wtx, - transactionsRegistry - tx.id, invalidatedTxIds, outputs -- tx.outputs.map(_.id), inputs -- tx.inputs.map(_.boxId) @@ -124,27 +116,16 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr */ def invalidate(unconfirmedTx: UnconfirmedTransaction): OrderedTxPool = { val tx = unconfirmedTx.transaction - transactionsRegistry.get(tx.id) match { + getWtx(tx.id) match { case Some(wtx) => new OrderedTxPool( orderedTransactions - wtx, - transactionsRegistry - tx.id, invalidatedTxIds.put(tx.id), outputs -- tx.outputs.map(_.id), inputs -- tx.inputs.map(_.boxId) ).updateFamily(tx, -wtx.weight, System.currentTimeMillis(), depth = 0) case None => - if (orderedTransactions.valuesIterator.exists(utx => utx.id == tx.id)) { - new OrderedTxPool( - orderedTransactions.filter(_._2.id != tx.id), - transactionsRegistry - tx.id, - invalidatedTxIds.put(tx.id), - outputs -- tx.outputs.map(_.id), - inputs -- tx.inputs.map(_.boxId) - ) - } else { - new OrderedTxPool(orderedTransactions, transactionsRegistry, invalidatedTxIds.put(tx.id), outputs, inputs) - } + new OrderedTxPool(orderedTransactions, invalidatedTxIds.put(tx.id), outputs, inputs) } } @@ -164,9 +145,8 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr * @param id - transaction id * @return - true, if transaction is in the pool or invalidated earlier, false otherwise */ - def contains(id: ModifierId): Boolean = { - transactionsRegistry.contains(id) - } + def contains(id: ModifierId): Boolean = + orderedTransactions.contains(WeightedTxId(id, 0, 0, 0)) def isInvalidated(id: ModifierId): Boolean = invalidatedTxIds.mightContain(id) @@ -194,14 +174,13 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr } else { val uniqueTxIds: Set[WeightedTxId] = tx.inputs.flatMap(input => this.outputs.get(input.boxId)).toSet - val parentTxs = uniqueTxIds.flatMap(wtx => this.orderedTransactions.get(wtx).map(ut => wtx -> ut)) + val parentTxs = uniqueTxIds.flatMap(wtx => orderedTransactions.get(wtx).map(ut => wtx -> ut)) parentTxs.foldLeft(this) { case (pool, (wtx, ut)) => val parent = ut.transaction val newWtx = WeightedTxId(wtx.id, wtx.weight + weight, wtx.feePerFactor, wtx.created) val newPool = new OrderedTxPool( pool.orderedTransactions - wtx + (newWtx -> ut), - pool.transactionsRegistry.updated(parent.id, newWtx), invalidatedTxIds, parent.outputs.foldLeft(pool.outputs)((newOutputs, box) => newOutputs.updated(box.id, newWtx)), parent.inputs.foldLeft(pool.inputs)((newInputs, inp) => newInputs.updated(inp.boxId, newWtx)) @@ -241,7 +220,6 @@ object OrderedTxPool { val frontCacheExpiration = cacheSettings.invalidModifiersCacheExpiration new OrderedTxPool( TreeMap.empty[WeightedTxId, UnconfirmedTransaction], - TreeMap.empty[ModifierId, WeightedTxId], ExpiringApproximateCache.empty(frontCacheSize, frontCacheExpiration), TreeMap.empty[BoxId, WeightedTxId], TreeMap.empty[BoxId, WeightedTxId])(settings) diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index c68d279f50..6c74462b33 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -392,6 +392,41 @@ class ErgoMemPoolSpec extends AnyFlatSpec updPool.get(utx3.id).get.lastCheckedTime shouldBe (now + 1) } + it should "accept double-spending transaction if it is paying more than one already sitting in the pool" in { + val (us, bh) = createUtxoState(extendedParameters) + val genesis = validFullBlock(None, us, bh) + val wus = WrappedUtxoState(us, bh, stateConstants, extendedParameters).applyModifier(genesis)(_ => ()).get + + val input = wus.takeBoxes(100).collectFirst { + case box if box.ergoTree == TrueLeaf.toSigmaProp.treeWithSegregation => box + }.get + + val txCount = 5 + val txs: Array[UnconfirmedTransaction] = Array.ofDim(txCount) + + for(i <- 0 until txCount) { + val out = new ErgoBoxCandidate(input.value, settings.chainSettings.monetary.feeProposition, creationHeight = 0) + val txLike = ErgoTransaction( + IndexedSeq(new Input(input.id, new ProverResult(Array.emptyByteArray, + ContextExtension(Map((1: Byte) -> ByteArrayConstant(Array.fill(1 + txCount - i)(0: Byte)))))) + ), IndexedSeq(out) + ) + txs(i) = UnconfirmedTransaction(ErgoTransaction(txLike.inputs, txLike.outputCandidates), None) + } + + val pool = ErgoMemPool.empty(settings) + + val endPool = txs.foldLeft(pool) { case (p, tx) => + val (newP, txoutcome) = p.process(tx, us) + txoutcome.isInstanceOf[ProcessingOutcome.Accepted] shouldBe true + newP + } + + endPool.pool.orderedTransactions.size shouldBe 1 + endPool.pool.inputs.contains(input.id) shouldBe true + endPool.pool.outputs.size shouldBe 1 + endPool.pool.outputs.contains(txs.last.transaction.outputs(0).id) shouldBe true + } } From 5fa469d91085b479465017a608d8c35dfe187f05 Mon Sep 17 00:00:00 2001 From: jellymlg Date: Tue, 28 Feb 2023 00:41:57 +0100 Subject: [PATCH 3/8] try --- .../mempool/ErgoMemPoolBenchmark.scala | 2 +- .../mempool/MempoolPerformanceBench.scala | 5 +- .../http/api/ErgoBaseApiRoute.scala | 4 +- .../http/api/TransactionsApiRoute.scala | 6 +- .../http/api/WalletApiRoute.scala | 6 +- .../ergoplatform/local/CleanupWorker.scala | 3 +- .../ergoplatform/local/MempoolAuditor.scala | 4 +- .../mining/CandidateGenerator.scala | 4 +- .../network/ErgoNodeViewSynchronizer.scala | 7 +- .../nodeView/ErgoNodeViewHolder.scala | 6 +- .../nodeView/mempool/ErgoMemPool.scala | 65 +++----- .../nodeView/mempool/ErgoMemPoolReader.scala | 10 +- .../nodeView/mempool/HistogramStats.scala | 8 +- .../nodeView/mempool/MemPoolStatistics.scala | 8 +- .../nodeView/mempool/OrderedTxPool.scala | 142 +++++++----------- .../mempool/UnconfirmedTransaction.scala | 44 +++++- .../nodeView/state/UtxoStateReader.scala | 4 +- .../http/routes/TransactionApiRouteSpec.scala | 3 +- .../local/MempoolAuditorSpec.scala | 2 +- .../ergoplatform/mining/ErgoMinerSpec.scala | 12 +- .../nodeView/mempool/ErgoMemPoolSpec.scala | 64 ++++---- .../viewholder/ErgoNodeViewHolderSpec.scala | 2 +- .../wallet/ErgoWalletServiceSpec.scala | 4 +- .../org/ergoplatform/sanity/ErgoSanity.scala | 8 +- .../utils/MempoolTestHelpers.scala | 6 +- .../scala/org/ergoplatform/utils/Stubs.scala | 4 +- .../NodeViewSynchronizerTests.scala | 4 +- .../properties/mempool/MemoryPoolTest.scala | 4 +- .../MempoolFilterPerformanceTest.scala | 4 +- .../mempool/MempoolRemovalTest.scala | 4 +- .../mempool/MempoolTransactionsTest.scala | 7 +- 31 files changed, 219 insertions(+), 237 deletions(-) rename src/main/scala/org/ergoplatform/{modifiers => nodeView}/mempool/UnconfirmedTransaction.scala (59%) diff --git a/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolBenchmark.scala b/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolBenchmark.scala index 1e8802027a..cd4176b592 100644 --- a/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolBenchmark.scala +++ b/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolBenchmark.scala @@ -1,6 +1,6 @@ package org.ergoplatform.nodeView.mempool -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.utils.generators.ErgoTransactionGenerators import org.scalameter.KeyValue import org.scalameter.api._ diff --git a/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/MempoolPerformanceBench.scala b/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/MempoolPerformanceBench.scala index c15db166f0..f38a076d89 100644 --- a/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/MempoolPerformanceBench.scala +++ b/benchmarks/src/test/scala/org/ergoplatform/nodeView/mempool/MempoolPerformanceBench.scala @@ -1,6 +1,7 @@ package org.ergoplatform.nodeView.mempool -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool import org.ergoplatform.utils.generators.{ErgoGenerators, ErgoTransactionGenerators} import org.scalacheck.Gen import org.scalatest.propspec.AnyPropSpec @@ -15,5 +16,5 @@ class MempoolPerformanceBench extends AnyPropSpec override val memPoolGenerator: Gen[ErgoMemPool] = emptyMemPoolGen override val transactionGenerator: Gen[ErgoTransaction] = invalidErgoTransactionGen override val unconfirmedTxGenerator: Gen[UnconfirmedTransaction] = - invalidErgoTransactionGen.map(tx => UnconfirmedTransaction(tx, None)) + invalidErgoTransactionGen.map(tx => mempool.UnconfirmedTransaction(tx, None)) } diff --git a/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala index dafc0bf0a1..ca209145d8 100644 --- a/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/ErgoBaseApiRoute.scala @@ -3,9 +3,9 @@ package org.ergoplatform.http.api import akka.actor.ActorRef import akka.http.scaladsl.server.Route import akka.http.scaladsl.server.{Directive1, ValidationRejection} -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} -import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.nodeView.state.{ErgoStateReader, UtxoStateReader} import org.ergoplatform.settings.{Algos, ErgoSettings} import scorex.core.api.http.{ApiError, ApiRoute} diff --git a/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala index 3159f99f24..42db67d112 100644 --- a/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/TransactionsApiRoute.scala @@ -7,9 +7,9 @@ import akka.pattern.ask import io.circe.Json import io.circe.syntax._ import org.ergoplatform.ErgoBox.{BoxId, NonMandatoryRegisterId, TokenId} -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, ErgoTransactionSerializer, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.{ErgoTransaction, ErgoTransactionSerializer} import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} -import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.nodeView.mempool.HistogramStats.getFeeHistogram import org.ergoplatform.nodeView.state.{ErgoStateReader, UtxoStateReader} import org.ergoplatform.settings.{Algos, ErgoSettings} @@ -135,7 +135,7 @@ case class TransactionsApiRoute(readersHolder: ActorRef, val feeHistogramParameters: Directive[(Int, Long)] = parameters("bins".as[Int] ? 10, "maxtime".as[Long] ? (60*1000L)) def getFeeHistogramR: Route = (path("poolHistogram") & get & feeHistogramParameters) { (bins, maxtime) => - ApiResponse(getMemPool.map(p => getFeeHistogram(System.currentTimeMillis(), bins, maxtime, p.weightedTransactionIds(Int.MaxValue)).asJson)) + ApiResponse(getMemPool.map(p => getFeeHistogram(System.currentTimeMillis(), bins, maxtime, p.txTimesAndWeights).asJson)) } val feeRequestParameters: Directive[(Int, Int)] = parameters("waitTime".as[Int] ? 1, "txSize".as[Int] ? 100) diff --git a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala index c4ddea6377..ce6e2d1d42 100644 --- a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala @@ -7,7 +7,7 @@ import io.circe.syntax._ import io.circe.{Encoder, Json} import org.ergoplatform._ import org.ergoplatform.http.api.requests.HintExtractionRequest -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.wallet._ import org.ergoplatform.nodeView.wallet.requests._ @@ -25,6 +25,8 @@ import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} import akka.http.scaladsl.server.MissingQueryParamRejection +import org.ergoplatform.nodeView.mempool +import org.ergoplatform.nodeView.mempool.UnconfirmedTransaction case class WalletApiRoute(readersHolder: ActorRef, nodeViewActorRef: ActorRef, @@ -176,7 +178,7 @@ case class WalletApiRoute(readersHolder: ActorRef, requests, inputsRaw, dataInputsRaw, - tx => Future(Success(UnconfirmedTransaction(tx, source = None))), + tx => Future(Success(mempool.UnconfirmedTransaction(tx, source = None))), utx => ApiResponse(utx.transaction) ) } diff --git a/src/main/scala/org/ergoplatform/local/CleanupWorker.scala b/src/main/scala/org/ergoplatform/local/CleanupWorker.scala index 1cd15c4c3f..21bd047454 100644 --- a/src/main/scala/org/ergoplatform/local/CleanupWorker.scala +++ b/src/main/scala/org/ergoplatform/local/CleanupWorker.scala @@ -3,8 +3,7 @@ package org.ergoplatform.local import akka.actor.{Actor, ActorRef} import org.ergoplatform.local.CleanupWorker.RunCleanup import org.ergoplatform.local.MempoolAuditor.CleanupDone -import org.ergoplatform.modifiers.mempool.UnconfirmedTransaction -import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.nodeView.state.UtxoStateReader import org.ergoplatform.settings.NodeConfigurationSettings import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.{EliminateTransactions, RecheckedTransactions} diff --git a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala index 800f3974a7..b5a9db6452 100644 --- a/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala +++ b/src/main/scala/org/ergoplatform/local/MempoolAuditor.scala @@ -4,8 +4,8 @@ import akka.actor.SupervisorStrategy.{Restart, Stop} import akka.actor.{Actor, ActorInitializationException, ActorKilledException, ActorRef, ActorRefFactory, DeathPactException, OneForOneStrategy, Props} import org.ergoplatform.local.CleanupWorker.RunCleanup import org.ergoplatform.local.MempoolAuditor.CleanupDone -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.settings.ErgoSettings import scorex.core.network.Broadcast import scorex.core.network.NetworkController.ReceivableMessages.SendToNetwork diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index dbdab5b7f2..8345da4d5e 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -11,13 +11,13 @@ import org.ergoplatform.modifiers.history._ import org.ergoplatform.modifiers.history.extension.Extension import org.ergoplatform.modifiers.history.header.{Header, HeaderWithoutPow} import org.ergoplatform.modifiers.history.popow.NipopowAlgos -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages import ReceivableMessages.{ChangedHistory, ChangedMempool, ChangedState, NodeViewChange, FullBlockApplied} import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.history.ErgoHistory.Height import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoHistoryReader} -import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.nodeView.state.{ErgoState, ErgoStateContext, StateType, UtxoStateReader} import org.ergoplatform.settings.{ErgoSettings, ErgoValidationSettingsUpdate, Parameters} import org.ergoplatform.wallet.Constants.MaxAssetsPerBox diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index dff3b03c30..e0efc0faa9 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -3,14 +3,14 @@ package org.ergoplatform.network import akka.actor.SupervisorStrategy.{Restart, Stop} import akka.actor.{Actor, ActorInitializationException, ActorKilledException, ActorRef, ActorRefFactory, DeathPactException, OneForOneStrategy, Props} import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, ErgoTransactionSerializer, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.{ErgoTransaction, ErgoTransactionSerializer} import org.ergoplatform.modifiers.{BlockSection, NetworkObjectTypeId} import org.ergoplatform.nodeView.history.{ErgoSyncInfoV1, ErgoSyncInfoV2} import org.ergoplatform.nodeView.history._ import ErgoNodeViewSynchronizer.{CheckModifiersToDownload, IncomingTxInfo, TransactionProcessingCacheRecord} import org.ergoplatform.nodeView.ErgoNodeViewHolder.BlockAppliedTransactions import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoSyncInfo, ErgoSyncInfoMessageSpec} -import org.ergoplatform.nodeView.mempool.{ErgoMemPool, ErgoMemPoolReader} +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.settings.{Constants, ErgoSettings} import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages._ import org.ergoplatform.nodeView.ErgoNodeViewHolder._ @@ -19,6 +19,7 @@ import scorex.core.network.ModifiersStatus.Requested import scorex.core.{NodeViewModifier, idsToString} import scorex.core.network.NetworkController.ReceivableMessages.{PenalizePeer, SendToNetwork} import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages._ +import org.ergoplatform.nodeView.mempool import org.ergoplatform.nodeView.state.{ErgoStateReader, UtxoStateReader} import org.ergoplatform.nodeView.wallet.ErgoWalletReader import scorex.core.network.message.{InvSpec, MessageSpec, ModifiersSpec, RequestModifierSpec} @@ -692,7 +693,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } else { ErgoTransactionSerializer.parseBytesTry(bytes) match { case Success(tx) if id == tx.id => - val utx = UnconfirmedTransaction(tx, bytes, Some(remote)) + val utx = mempool.UnconfirmedTransaction(tx, bytes, Some(remote)) viewHolderRef ! TransactionFromRemote(utx) case _ => // Penalize peer and do nothing - it will be switched to correct state on CheckDelivery diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 5e13ab0d61..4a7f90a0e8 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -5,10 +5,10 @@ import akka.actor.{Actor, ActorRef, ActorSystem, OneForOneStrategy, Props} import org.ergoplatform.ErgoApp import org.ergoplatform.ErgoApp.CriticalSystemException import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.modifiers.{BlockSection, ErgoFullBlock, NetworkObjectTypeId} import org.ergoplatform.nodeView.history.ErgoHistory -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.ergoplatform.nodeView.mempool.ErgoMemPool.ProcessingOutcome import org.ergoplatform.nodeView.state._ import org.ergoplatform.nodeView.wallet.ErgoWallet @@ -374,7 +374,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti val rolledBackTxs = blocksRemoved .flatMap(extractTransactions) .filter(tx => !appliedTxs.exists(_.id == tx.id)) - .map(tx => UnconfirmedTransaction(tx, None)) + .map(tx => mempool.UnconfirmedTransaction(tx, None)) memPool.remove(appliedTxs).put(rolledBackTxs) } diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala index 601a50a383..733b427784 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala @@ -2,14 +2,12 @@ package org.ergoplatform.nodeView.mempool import org.ergoplatform.ErgoBox.BoxId import org.ergoplatform.mining.emission.EmissionRules -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.OrderedTxPool.WeightedTxId +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption import org.ergoplatform.nodeView.state.{ErgoState, UtxoState} import org.ergoplatform.settings.{ErgoSettings, MonetarySettings, NodeConfigurationSettings} import scorex.core.transaction.state.TransactionValidation -import scorex.util.{ModifierId, ScorexLogging, bytesToId} -import OrderedTxPool.weighted -import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption +import scorex.util.{ModifierId, ScorexLogging} import spire.syntax.all.cfor import scala.annotation.tailrec @@ -32,13 +30,9 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, (implicit settings: ErgoSettings) extends ErgoMemPoolReader with ScorexLogging { - import ErgoMemPool._ import EmissionRules.CoinsInOneErgo + import ErgoMemPool._ - /** - * When there's no reason to re-check transactions immediately, we assign fake cost to them - */ - private val FakeCost = 1000 private val nodeSettings: NodeConfigurationSettings = settings.nodeSettings private implicit val monetarySettings: MonetarySettings = settings.chainSettings.monetary @@ -54,12 +48,12 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } override def take(limit: Int): Iterable[UnconfirmedTransaction] = { - pool.orderedTransactions.values.take(limit) + pool.orderedTransactions.take(limit) } def random(limit: Int): Iterable[UnconfirmedTransaction] = { val result = mutable.WrappedArray.newBuilder[UnconfirmedTransaction] - val txSeq = pool.orderedTransactions.values.to[Vector] + val txSeq = pool.orderedTransactions.to[Vector] val total = txSeq.size val start = if (total <= limit) { 0 @@ -77,14 +71,14 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, result.result() } - override def getAll: Seq[UnconfirmedTransaction] = pool.orderedTransactions.values.toSeq + override def getAll: Seq[UnconfirmedTransaction] = pool.orderedTransactions.toSeq override def getAll(ids: Seq[ModifierId]): Seq[UnconfirmedTransaction] = ids.flatMap(pool.get) /** * Returns all transactions resided in pool sorted by weight in descending order */ - override def getAllPrioritized: Seq[UnconfirmedTransaction] = pool.orderedTransactions.values.toSeq + override def getAllPrioritized: Seq[UnconfirmedTransaction] = pool.orderedTransactions.toSeq /** * Method to put a transaction into the memory pool. Validation of the transactions against @@ -93,7 +87,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, * @return Success(updatedPool), if transaction successfully added to the pool, Failure(_) otherwise */ def put(unconfirmedTx: UnconfirmedTransaction): ErgoMemPool = { - val updatedPool = pool.put(unconfirmedTx, feeFactor(unconfirmedTx)) + val updatedPool = pool.put(unconfirmedTx) new ErgoMemPool(updatedPool, stats, sortingOption) } @@ -102,8 +96,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } private def updateStatsOnRemoval(tx: ErgoTransaction): MemPoolStatistics = { - val wtx = pool.getWtx(tx.id) - wtx.map(wgtx => stats.add(System.currentTimeMillis(), wgtx)) + pool.get(tx.id).map(tx => stats.add(System.currentTimeMillis(), tx)(sortingOption)) .getOrElse(MemPoolStatistics(System.currentTimeMillis(), 0, System.currentTimeMillis())) } @@ -148,14 +141,6 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, */ override def spentInputs: Iterator[BoxId] = pool.inputs.keysIterator - private def feeFactor(unconfirmedTransaction: UnconfirmedTransaction): Int = { - sortingOption match { - case SortingOption.FeePerByte => - unconfirmedTransaction.transactionBytes.map(_.length).getOrElse(unconfirmedTransaction.transaction.size) - case SortingOption.FeePerCycle => - unconfirmedTransaction.lastCost.getOrElse(FakeCost) - } - } // Check if transaction is double-spending inputs spent in the mempool. // If so, the new transacting is replacing older ones if it has bigger weight (fee/byte) than them on average. @@ -164,31 +149,30 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, validationStartTime: Long): (ErgoMemPool, ProcessingOutcome) = { val tx = unconfirmedTransaction.transaction - val doubleSpendingWtxs = tx.inputs.flatMap { inp => + val doubleSpendingIds = tx.inputs.flatMap { inp => pool.inputs.get(inp.boxId) }.toSet - val feeF = feeFactor(unconfirmedTransaction) + implicit val so: SortingOption = sortingOption - if (doubleSpendingWtxs.nonEmpty) { - val ownWtx = weighted(tx, feeF) - val doubleSpendingTotalWeight = doubleSpendingWtxs.map(_.weight).sum / doubleSpendingWtxs.size - if (ownWtx.weight > doubleSpendingTotalWeight) { - val doubleSpendingTxs = doubleSpendingWtxs.map(wtx => pool.orderedTransactions(wtx)).toSeq - val p = pool.remove(doubleSpendingTxs).put(unconfirmedTransaction, feeF) + if (doubleSpendingIds.nonEmpty) { + val doubleSpendingTxs = doubleSpendingIds.map(pool.get(_).get).toSeq + val doubleSpendingTotalWeight = doubleSpendingTxs.map(_.weight).sum / doubleSpendingTxs.size + if (unconfirmedTransaction.weight > doubleSpendingTotalWeight) { + val p = pool.remove(doubleSpendingTxs).put(unconfirmedTransaction) val updPool = new ErgoMemPool(p, stats, sortingOption) updPool -> new ProcessingOutcome.Accepted(unconfirmedTransaction, validationStartTime) } else { - this -> new ProcessingOutcome.DoubleSpendingLoser(doubleSpendingWtxs.map(_.id), validationStartTime) + this -> new ProcessingOutcome.DoubleSpendingLoser(doubleSpendingIds, validationStartTime) } } else { val poolSizeLimit = nodeSettings.mempoolCapacity if (pool.size == poolSizeLimit && - weighted(tx, feeF).weight <= pool.orderedTransactions.lastKey.weight) { + unconfirmedTransaction.weight <= pool.orderedTransactions.lastKey.weight) { val exc = new Exception("Transaction pays less than any other in the pool being full") this -> new ProcessingOutcome.Declined(exc, validationStartTime) } else { - val updPool = new ErgoMemPool(pool.put(unconfirmedTransaction, feeF), stats, sortingOption) + val updPool = new ErgoMemPool(pool.put(unconfirmedTransaction), stats, sortingOption) updPool -> new ProcessingOutcome.Accepted(unconfirmedTransaction, validationStartTime) } } @@ -266,7 +250,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } } - def weightedTransactionIds(limit: Int): Seq[WeightedTxId] = pool.orderedTransactions.keysIterator.take(limit).toSeq + def txTimesAndWeights: Seq[(Long,Long)] = pool.orderedTransactions.toSeq.map(uTx => uTx.createdTime -> uTx.weight(monetarySettings, sortingOption)) private def extractFee(tx: ErgoTransaction): Long = { tx.outputs @@ -303,13 +287,10 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, * @return average time for this transaction to be placed in block */ def getExpectedWaitTime(txFee : Long, txSize : Int): Long = { - // Create dummy transaction entry val feePerKb = txFee * 1024 / txSize - val dummyModifierId = bytesToId(Array.fill(32)(0.toByte)) - val wtx = WeightedTxId(dummyModifierId, feePerKb, feePerKb, 0) // Find position of entry in mempool - val posInPool = pool.orderedTransactions.keySet.until(wtx).size + val posInPool = pool.orderedTransactions.dropWhile(_.weight(monetarySettings, sortingOption) > feePerKb).size // Time since statistics measurement interval (needed to calculate average tx rate) val elapsed = System.currentTimeMillis() - stats.startMeasurement @@ -439,7 +420,7 @@ object ErgoMemPool extends ScorexLogging { case SortingOption.FeePerCycle => log.info("Sorting mempool by fee-per-cycle") } new ErgoMemPool( - OrderedTxPool.empty(settings), + OrderedTxPool.empty(settings, sortingOption), MemPoolStatistics(System.currentTimeMillis(), 0, System.currentTimeMillis()), sortingOption )(settings) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolReader.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolReader.scala index 884e7249e2..e0da84199e 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolReader.scala @@ -1,8 +1,7 @@ package org.ergoplatform.nodeView.mempool import org.ergoplatform.ErgoBox.BoxId -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.OrderedTxPool.WeightedTxId +import org.ergoplatform.modifiers.mempool.ErgoTransaction import scorex.core.NodeViewComponent import scorex.core.consensus.ContainsModifiers import scorex.util.ModifierId @@ -45,13 +44,12 @@ trait ErgoMemPoolReader extends NodeViewComponent with ContainsModifiers[ErgoTra def modifierById(modifierId: ModifierId): Option[ErgoTransaction] /** - * Returns transaction ids with weights. Weight depends on a fee a transaction is paying. - * Resulting transactions are sorted by weight in descending order. + * Returns an array of tuples representing transaction creation times with weights. Weight depends on a fee a transaction is paying. + * Resulting array is are sorted by weight in descending order. * - * @param limit - number of weighted transactions to return * @return an ordered sequence of transaction ids with weights */ - def weightedTransactionIds(limit: Int): Seq[WeightedTxId] + def txTimesAndWeights: Seq[(Long,Long)] /** * Get expected wait time for the transaction with specified fee and size diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/HistogramStats.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/HistogramStats.scala index bb25b0dbb1..6ef0815032 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/HistogramStats.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/HistogramStats.scala @@ -1,16 +1,14 @@ package org.ergoplatform.nodeView.mempool -import org.ergoplatform.nodeView.mempool.OrderedTxPool.WeightedTxId - object HistogramStats { - def getFeeHistogram(currTime: Long, nBins : Int, maxWaitTimeMsec: Long, wtxs : Seq[WeightedTxId]): Array[FeeHistogramBin] = { + def getFeeHistogram(currTime: Long, nBins : Int, maxWaitTimeMsec: Long, wtxs : Seq[(Long,Long)]): Array[FeeHistogramBin] = { val histogram = Array.fill(nBins + 1)(FeeHistogramBin(0,0)) val interval = maxWaitTimeMsec / nBins for (wtx <- wtxs) { - val waitTime = currTime - wtx.created + val waitTime = currTime - wtx._1 val bin = if (waitTime < maxWaitTimeMsec) (waitTime/interval).toInt else nBins - histogram.update(bin, FeeHistogramBin(histogram(bin).nTxns + 1, histogram(bin).totalFee + wtx.feePerFactor)) + histogram.update(bin, FeeHistogramBin(histogram(bin).nTxns + 1, histogram(bin).totalFee + wtx._2)) } histogram } diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/MemPoolStatistics.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/MemPoolStatistics.scala index c88f8415d2..37aa1dd6e2 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/MemPoolStatistics.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/MemPoolStatistics.scala @@ -1,6 +1,6 @@ package org.ergoplatform.nodeView.mempool -import org.ergoplatform.nodeView.mempool.OrderedTxPool.WeightedTxId +import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption /** @@ -24,7 +24,7 @@ case class MemPoolStatistics(startMeasurement: Long, * prune statistic. To avoid siutation when we do not have statistic at all, we actually keep data up to * 2*measurementIntervalMsec and periodically cut half of range. */ - def add(currTime: Long, wtx: WeightedTxId): MemPoolStatistics = { + def add(currTime: Long, utx: UnconfirmedTransaction)(implicit sortingOption: SortingOption): MemPoolStatistics = { val curTakenTx = takenTxns + 1 val (newTakenTx, newMeasurement, newSnapTxs, newSnapTime) = if (currTime - snapTime > MemPoolStatistics.measurementIntervalMsec) { @@ -36,10 +36,10 @@ case class MemPoolStatistics(startMeasurement: Long, } else { (curTakenTx, startMeasurement, snapTakenTxns, snapTime) } - val durationMinutes = ((currTime - wtx.created) / (60 * 1000)).toInt + val durationMinutes = ((currTime - utx.createdTime) / (60 * 1000)).toInt val newHist = if (durationMinutes < MemPoolStatistics.nHistogramBins) { - val (histx, hisfee) = (histogram(durationMinutes).nTxns + 1, histogram(durationMinutes).totalFee + wtx.feePerFactor) + val (histx, hisfee) = (histogram(durationMinutes).nTxns + 1, histogram(durationMinutes).totalFee + utx.feeFactor(sortingOption)) histogram.updated(durationMinutes, FeeHistogramBin(histx, hisfee)) } else { histogram diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala index c5b316e45f..235013ea71 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala @@ -1,28 +1,26 @@ package org.ergoplatform.nodeView.mempool import org.ergoplatform.ErgoBox.BoxId -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.OrderedTxPool.WeightedTxId +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption import org.ergoplatform.settings.{Algos, ErgoSettings, MonetarySettings} import scorex.util.{ModifierId, ScorexLogging} -import scala.collection.immutable.TreeMap +import scala.collection.immutable.{TreeMap, TreeSet} /** * An immutable pool of transactions of limited size with priority management and blacklisting support. * * @param orderedTransactions - collection containing transactions ordered by `tx.weight` * @param invalidatedTxIds - invalidated transaction ids in bloom filters - * @param outputs - mapping `box.id` -> `WeightedTxId(tx.id,tx.weight)` required for getting a transaction by its output box - * @param inputs - mapping `box.id` -> `WeightedTxId(tx.id,tx.weight)` required for getting a transaction by its input box id + * @param outputs - mapping `box.id` -> `ModifierId` required for getting a transaction by its output box + * @param inputs - mapping `box.id` -> `ModifierId` required for getting a transaction by its input box id */ -class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTransaction], +class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], val invalidatedTxIds: ApproximateCacheLike[String], - val outputs: TreeMap[BoxId, WeightedTxId], - val inputs: TreeMap[BoxId, WeightedTxId]) - (implicit settings: ErgoSettings) extends ScorexLogging { - - import OrderedTxPool.weighted + val outputs: TreeMap[BoxId, ModifierId], + val inputs: TreeMap[BoxId, ModifierId]) + (implicit settings: ErgoSettings, sortingOption: SortingOption) extends ScorexLogging { /** * When a transaction has a parent in the mempool, we update its weight, weight of parent's parents etc. @@ -41,11 +39,8 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr def size: Int = orderedTransactions.size - def getWtx(id: ModifierId): Option[WeightedTxId] = - orderedTransactions.keySet.find(_.id == id) - def get(id: ModifierId): Option[UnconfirmedTransaction] = - orderedTransactions.get(WeightedTxId(id, 0, 0, 0)) + orderedTransactions.find(_.id == id) /** @@ -57,30 +52,35 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr * thrown from the pool. * * @param unconfirmedTx - transaction to add + * @param feeFactor - fee factor override, used in tests * @return - modified pool */ - def put(unconfirmedTx: UnconfirmedTransaction, feeFactor: Int): OrderedTxPool = { + def put(unconfirmedTx: UnconfirmedTransaction, feeFactor: Option[Int] = None): OrderedTxPool = { val tx = unconfirmedTx.transaction - val newPool = if(contains(tx.id)) { - new OrderedTxPool( - orderedTransactions.updated(getWtx(tx.id).get, unconfirmedTx), - invalidatedTxIds, - outputs, - inputs - ) - }else { - val wtx = weighted(tx, feeFactor) - new OrderedTxPool( - orderedTransactions.updated(wtx, unconfirmedTx), - invalidatedTxIds, - outputs ++ tx.outputs.map(_.id -> wtx), - inputs ++ tx.inputs.map(_.boxId -> wtx) - ).updateFamily(tx, wtx.weight, System.currentTimeMillis(), 0) - } + if(feeFactor.isDefined) + unconfirmedTx._feeFactor = feeFactor.get + + val newPool = + get(tx.id) match { + case Some(uTx) => + new OrderedTxPool( + (orderedTransactions - uTx) + unconfirmedTx, + invalidatedTxIds, + outputs, + inputs + ) + case None => + new OrderedTxPool( + orderedTransactions + unconfirmedTx, + invalidatedTxIds, + outputs ++ tx.outputs.map(_.id -> tx.id), + inputs ++ tx.inputs.map(_.boxId -> tx.id) + ).updateFamily(tx, unconfirmedTx.weight, System.currentTimeMillis(), 0) + } if (newPool.orderedTransactions.size > mempoolCapacity) { - val victim = newPool.orderedTransactions.last._2 + val victim = newPool.orderedTransactions.last newPool.remove(victim) } else { newPool @@ -97,7 +97,7 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr * @param tx - Transaction to remove */ def remove(tx: ErgoTransaction): OrderedTxPool = { - getWtx(tx.id) match { + get(tx.id) match { case Some(wtx) => new OrderedTxPool( orderedTransactions - wtx, @@ -116,7 +116,7 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr */ def invalidate(unconfirmedTx: UnconfirmedTransaction): OrderedTxPool = { val tx = unconfirmedTx.transaction - getWtx(tx.id) match { + get(tx.id) match { case Some(wtx) => new OrderedTxPool( orderedTransactions - wtx, @@ -146,7 +146,7 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr * @return - true, if transaction is in the pool or invalidated earlier, false otherwise */ def contains(id: ModifierId): Boolean = - orderedTransactions.contains(WeightedTxId(id, 0, 0, 0)) + get(id).isDefined def isInvalidated(id: ModifierId): Boolean = invalidatedTxIds.mightContain(id) @@ -173,17 +173,16 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr this } else { - val uniqueTxIds: Set[WeightedTxId] = tx.inputs.flatMap(input => this.outputs.get(input.boxId)).toSet - val parentTxs = uniqueTxIds.flatMap(wtx => orderedTransactions.get(wtx).map(ut => wtx -> ut)) + val uniqueTxIds: Set[ModifierId] = tx.inputs.flatMap(input => outputs.get(input.boxId)).toSet + val parentTxs = uniqueTxIds.flatMap(get) - parentTxs.foldLeft(this) { case (pool, (wtx, ut)) => - val parent = ut.transaction - val newWtx = WeightedTxId(wtx.id, wtx.weight + weight, wtx.feePerFactor, wtx.created) + parentTxs.foldLeft(this) { case (pool, uTx) => + val parent = uTx.transaction val newPool = new OrderedTxPool( - pool.orderedTransactions - wtx + (newWtx -> ut), + pool.orderedTransactions - uTx + uTx.addWeight(weight), invalidatedTxIds, - parent.outputs.foldLeft(pool.outputs)((newOutputs, box) => newOutputs.updated(box.id, newWtx)), - parent.inputs.foldLeft(pool.inputs)((newInputs, inp) => newInputs.updated(inp.boxId, newWtx)) + parent.outputs.foldLeft(pool.outputs)((newOutputs, box) => newOutputs.updated(box.id, parent.id)), + parent.inputs.foldLeft(pool.inputs)((newInputs, inp) => newInputs.updated(inp.boxId, parent.id)) ) newPool.updateFamily(parent, weight, startTime, depth + 1) } @@ -193,59 +192,20 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedTxId, UnconfirmedTr object OrderedTxPool { - /** - * Weighted transaction id - * - * @param id - Transaction id - * @param weight - Weight of transaction - * @param feePerFactor - Transaction's fee per factor (byte or execution cost) - * @param created - Transaction creation time - */ - case class WeightedTxId(id: ModifierId, weight: Long, feePerFactor: Long, created: Long) { - // `id` depends on `weight` so we can use only the former for comparison. - override def equals(obj: Any): Boolean = obj match { - case that: WeightedTxId => that.id == id - case _ => false - } - - override def hashCode(): Int = id.hashCode() - } - - private implicit val ordWeight: Ordering[WeightedTxId] = Ordering[(Long, ModifierId)].on(x => (-x.weight, x.id)) private implicit val ordBoxId: Ordering[BoxId] = Ordering[String].on(b => Algos.encode(b)) - def empty(settings: ErgoSettings): OrderedTxPool = { + def empty(settings: ErgoSettings, sortingOption: SortingOption): OrderedTxPool = { val cacheSettings = settings.cacheSettings.mempool val frontCacheSize = cacheSettings.invalidModifiersCacheSize val frontCacheExpiration = cacheSettings.invalidModifiersCacheExpiration + implicit val ms: MonetarySettings = settings.chainSettings.monetary + implicit val ordWeight: Ordering[UnconfirmedTransaction] = + Ordering[(Long, ModifierId)].on(x => (-x.weight(ms, sortingOption), x.id)) new OrderedTxPool( - TreeMap.empty[WeightedTxId, UnconfirmedTransaction], + TreeSet.empty[UnconfirmedTransaction], ExpiringApproximateCache.empty(frontCacheSize, frontCacheExpiration), - TreeMap.empty[BoxId, WeightedTxId], - TreeMap.empty[BoxId, WeightedTxId])(settings) - } - - def weighted(unconfirmedTx: UnconfirmedTransaction, feeFactor: Int)(implicit ms: MonetarySettings): WeightedTxId = { - weighted(unconfirmedTx.transaction, feeFactor) - } - - /** - * Wrap transaction into an entity which is storing its mempool sorting weight also - * - * @param tx - transaction - * @param feeFactor - fee-related factor of the transaction `tx`, so size or cost - * @param ms - monetary settings to extract fee proposition from - * @return - transaction and its weight wrapped in `WeightedTxId` - */ - def weighted(tx: ErgoTransaction, feeFactor: Int)(implicit ms: MonetarySettings): WeightedTxId = { - val fee = tx.outputs - .filter(b => java.util.Arrays.equals(b.propositionBytes, ms.feePropositionBytes)) - .map(_.value) - .sum - - // We multiply by 1024 for better precision - val feePerFactor = fee * 1024 / feeFactor - // Weight is equal to feePerFactor here, however, it can be modified later when children transactions will arrive - WeightedTxId(tx.id, feePerFactor, feePerFactor, System.currentTimeMillis()) + TreeMap.empty[BoxId, ModifierId], + TreeMap.empty[BoxId, ModifierId] + )(settings, sortingOption) } } diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/UnconfirmedTransaction.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala similarity index 59% rename from src/main/scala/org/ergoplatform/modifiers/mempool/UnconfirmedTransaction.scala rename to src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala index bd18f338d8..f04d765bbf 100644 --- a/src/main/scala/org/ergoplatform/modifiers/mempool/UnconfirmedTransaction.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala @@ -1,5 +1,8 @@ -package org.ergoplatform.modifiers.mempool +package org.ergoplatform.nodeView.mempool +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption +import org.ergoplatform.settings.MonetarySettings import scorex.core.network.ConnectedPeer import scorex.util.{ModifierId, ScorexLogging} @@ -23,6 +26,45 @@ class UnconfirmedTransaction(val transaction: ErgoTransaction, def id: ModifierId = transaction.id + /** + * When there's no reason to re-check transactions immediately, we assign fake cost to them + */ + private val FakeCost = 1000 + + private[mempool] var _feeFactor: Int = -1 + def feeFactor(implicit sortingOption: SortingOption): Int = { + if(_feeFactor == -1) { + sortingOption match { + case SortingOption.FeePerByte => + _feeFactor = transactionBytes.map(_.length).getOrElse(transaction.size) + case SortingOption.FeePerCycle => + _feeFactor = lastCost.getOrElse(FakeCost) + } + } + _feeFactor + } + + private def feePerFactor(implicit ms: MonetarySettings, sortingOption: SortingOption): Long = { + val fee = transaction.outputs + .filter(b => java.util.Arrays.equals(b.propositionBytes, ms.feePropositionBytes)) + .map(_.value) + .sum + // We multiply by 1024 for better precision + fee * 1024 / feeFactor + } + + var _weight: Long = -1 + def weight(implicit ms: MonetarySettings, sortingOption: SortingOption): Long = { + if(_weight == -1) + _weight = feePerFactor + _weight + } + + def addWeight(weight: Long): UnconfirmedTransaction = { + _weight = weight + this + } + /** * Updates cost and last checked time of unconfirmed transaction */ diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala index 69db43c082..d0748a706d 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala @@ -3,8 +3,8 @@ package org.ergoplatform.nodeView.state import org.ergoplatform.ErgoBox import org.ergoplatform.mining.emission.EmissionRules import org.ergoplatform.modifiers.ErgoFullBlock -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.settings.Algos import org.ergoplatform.settings.Algos.HF import org.ergoplatform.wallet.boxes.ErgoBoxSerializer diff --git a/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala index 04d0e60ca1..3c844b3027 100644 --- a/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala @@ -9,8 +9,9 @@ import io.circe.Json import io.circe.syntax._ import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId} import org.ergoplatform.http.api.{ApiCodecs, TransactionsApiRoute} -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.ErgoReadersHolder.{GetDataFromHistory, GetReaders, Readers} +import org.ergoplatform.nodeView.mempool.UnconfirmedTransaction import org.ergoplatform.settings.Constants import org.ergoplatform.utils.Stubs import org.ergoplatform.{DataInput, ErgoBox, ErgoBoxCandidate, Input} diff --git a/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala b/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala index 09974da381..b7783a5f6c 100644 --- a/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala +++ b/src/test/scala/org/ergoplatform/local/MempoolAuditorSpec.scala @@ -3,10 +3,10 @@ package org.ergoplatform.local import akka.actor.{ActorRef, ActorSystem} import akka.testkit.{TestActorRef, TestProbe} import org.ergoplatform.ErgoAddressEncoder -import org.ergoplatform.modifiers.mempool.UnconfirmedTransaction import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages.{FailedTransaction, RecheckMempool, SuccessfulTransaction} import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.{LocallyGeneratedTransaction, RecheckedTransactions} import org.ergoplatform.nodeView.mempool.ErgoMemPool.ProcessingOutcome +import org.ergoplatform.nodeView.mempool.UnconfirmedTransaction import org.ergoplatform.nodeView.state.ErgoState import org.ergoplatform.nodeView.state.wrapped.WrappedUtxoState import org.ergoplatform.settings.{Algos, Constants, ErgoSettings} diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala index d17b3da442..7e1f17bfc5 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala @@ -9,12 +9,12 @@ import org.ergoplatform.mining.CandidateGenerator.{Candidate, GenerateCandidate} import org.ergoplatform.mining.ErgoMiner.StartMining import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction, UnsignedErgoTransaction} +import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.history.ErgoHistoryReader import org.ergoplatform.nodeView.state._ import org.ergoplatform.nodeView.wallet._ -import org.ergoplatform.nodeView.{ErgoNodeViewRef, ErgoReadersHolderRef} +import org.ergoplatform.nodeView.{ErgoNodeViewRef, ErgoReadersHolderRef, mempool} import org.ergoplatform.settings.ErgoSettings import org.ergoplatform.utils.ErgoTestHelpers import org.ergoplatform.utils.generators.ValidBlocksGenerators @@ -94,7 +94,7 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen 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(UnconfirmedTransaction(ErgoTransaction(tx), None)) + nodeViewHolderRef ! LocallyGeneratedTransaction(mempool.UnconfirmedTransaction(ErgoTransaction(tx), None)) expectNoMessage(1 seconds) testProbe.expectMsgClass(newBlockDelay, newBlockSignal) testProbe.expectMsgClass(newBlockDelay, newBlockSignal) @@ -121,7 +121,7 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen txCost shouldBe 439080 // send costly transaction to the mempool - nodeViewHolderRef ! LocallyGeneratedTransaction(UnconfirmedTransaction(ErgoTransaction(costlyTx), None)) + nodeViewHolderRef ! LocallyGeneratedTransaction(mempool.UnconfirmedTransaction(ErgoTransaction(costlyTx), None)) testProbe.expectMsgClass(newBlockDelay, newBlockSignal) testProbe.expectMsgClass(newBlockDelay, newBlockSignal) @@ -186,7 +186,7 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen ) } - txs.map(tx => UnconfirmedTransaction(tx, None)).foreach(nodeViewHolderRef ! LocallyGeneratedTransaction(_)) + txs.map(tx => mempool.UnconfirmedTransaction(tx, None)).foreach(nodeViewHolderRef ! LocallyGeneratedTransaction(_)) if (toSend > toSpend.size) { // wait for the next block @@ -254,7 +254,7 @@ class ErgoMinerSpec extends AnyFlatSpec with ErgoTestHelpers with ValidBlocksGen // As double-spending transactions are filtered out in the mempool, the only way to push them is to order to // include double-spending transaction directly via mandatoryTransactions argument of PrepareCandidate command - nodeViewHolderRef ! LocallyGeneratedTransaction(UnconfirmedTransaction(ErgoTransaction(tx1), None)) + nodeViewHolderRef ! LocallyGeneratedTransaction(mempool.UnconfirmedTransaction(ErgoTransaction(tx1), None)) testProbe.expectMsgClass(newBlockDelay, newBlockSignal) testProbe.expectNoMessage(200.millis) diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index 6c74462b33..0b48c03bd5 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -2,10 +2,11 @@ package org.ergoplatform.nodeView.mempool import org.ergoplatform.{ErgoBoxCandidate, Input} import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool import org.ergoplatform.nodeView.mempool.ErgoMemPool.ProcessingOutcome import org.ergoplatform.nodeView.state.wrapped.WrappedUtxoState -import org.ergoplatform.settings.ErgoSettings +import org.ergoplatform.settings.{ErgoSettings, MonetarySettings} import org.ergoplatform.utils.ErgoTestHelpers import org.ergoplatform.utils.generators.ErgoGenerators import org.scalatest.flatspec.AnyFlatSpec @@ -25,7 +26,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val txs = validTransactionsFromUtxoState(wus) val pool0 = ErgoMemPool.empty(settings) val poolAfter = txs.foldLeft(pool0) { case (pool, tx) => - val (p, outcome) = pool.process(UnconfirmedTransaction(tx, None), us) + val (p, outcome) = pool.process(mempool.UnconfirmedTransaction(tx, None), us) if (!outcome.isInstanceOf[ProcessingOutcome.Accepted]) { throw new Exception("Transaction not accepted") } @@ -36,12 +37,12 @@ class ErgoMemPoolSpec extends AnyFlatSpec // light mode val poolLight = ErgoMemPool.empty(lightModeSettings) txs.foreach { tx => - poolLight.process(UnconfirmedTransaction(tx, None), us)._2.isInstanceOf[ProcessingOutcome.Accepted] shouldBe true + poolLight.process(mempool.UnconfirmedTransaction(tx, None), us)._2.isInstanceOf[ProcessingOutcome.Accepted] shouldBe true } } it should "respect given sorting order" in { - implicit val ms = settings.chainSettings.monetary + implicit val ms: MonetarySettings = settings.chainSettings.monetary val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get @@ -52,6 +53,9 @@ class ErgoMemPoolSpec extends AnyFlatSpec IndexedSeq(feeOut) ) + implicit val fPb: SortingOption = SortingOption.FeePerByte + implicit val fPc: SortingOption = SortingOption.FeePerCycle + // Randomly initialized settings.nodeSettings.mempoolSorting should (be (SortingOption.FeePerByte) or be (SortingOption.FeePerCycle)) @@ -61,9 +65,9 @@ class ErgoMemPoolSpec extends AnyFlatSpec )) var poolSize = ErgoMemPool.empty(sortBySizeSettings) - poolSize = poolSize.process(UnconfirmedTransaction(tx, None), wus)._1 - val size = tx.size - poolSize.pool.orderedTransactions.firstKey.weight shouldBe OrderedTxPool.weighted(tx, size).weight + val uTx1 = mempool.UnconfirmedTransaction(tx, None) + poolSize = poolSize.process(uTx1, wus)._1 + poolSize.pool.orderedTransactions.firstKey.weight(ms,fPb) shouldBe uTx1.weight(ms,fPb) val sortByCostSettings: ErgoSettings = settings.copy( nodeSettings = settings.nodeSettings.copy( @@ -71,9 +75,9 @@ class ErgoMemPoolSpec extends AnyFlatSpec )) var poolCost = ErgoMemPool.empty(sortByCostSettings) - poolCost = poolCost.process(UnconfirmedTransaction(tx, None), wus)._1 - val cost = wus.validateWithCost(tx, Int.MaxValue).get - poolCost.pool.orderedTransactions.firstKey.weight shouldBe OrderedTxPool.weighted(tx, cost).weight + val uTx2 = mempool.UnconfirmedTransaction(tx, None) + poolCost = poolCost.process(uTx2, wus)._1 + poolCost.pool.orderedTransactions.firstKey.weight(ms,fPc) shouldBe uTx2.withCost(wus.validateWithCost(tx, Int.MaxValue).get).weight(ms,fPc) } it should "decline already contained transaction" in { @@ -83,10 +87,10 @@ class ErgoMemPoolSpec extends AnyFlatSpec val txs = validTransactionsFromUtxoState(wus) var pool = ErgoMemPool.empty(settings) txs.foreach { tx => - pool = pool.put(UnconfirmedTransaction(tx, None)) + pool = pool.put(mempool.UnconfirmedTransaction(tx, None)) } txs.foreach { tx => - pool.process(UnconfirmedTransaction(tx, None), us)._2.isInstanceOf[ProcessingOutcome.Declined] shouldBe true + pool.process(mempool.UnconfirmedTransaction(tx, None), us)._2.isInstanceOf[ProcessingOutcome.Declined] shouldBe true } } @@ -117,8 +121,8 @@ class ErgoMemPoolSpec extends AnyFlatSpec IndexedSeq(feeOut) ) - val tx1 = UnconfirmedTransaction(ErgoTransaction(tx1Like.inputs, tx1Like.outputCandidates), None) - val tx2 = UnconfirmedTransaction(ErgoTransaction(ErgoTransaction(tx2Like.inputs, tx2Like.outputCandidates)), None) + val tx1 = mempool.UnconfirmedTransaction(ErgoTransaction(tx1Like.inputs, tx1Like.outputCandidates), None) + val tx2 = mempool.UnconfirmedTransaction(ErgoTransaction(ErgoTransaction(tx2Like.inputs, tx2Like.outputCandidates)), None) val pool0 = ErgoMemPool.empty(settings) val (pool, tx1Outcome) = pool0.process(tx1, us) @@ -144,7 +148,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val us = createUtxoState(parameters)._1 var pool = ErgoMemPool.empty(settings) forAll(invalidBlockTransactionsGen) { blockTransactions => - val unconfirmedTxs = blockTransactions.txs.map(tx => UnconfirmedTransaction(tx, None)) + val unconfirmedTxs = blockTransactions.txs.map(tx => mempool.UnconfirmedTransaction(tx, None)) unconfirmedTxs.foreach(tx => pool = pool.process(tx, us)._1) unconfirmedTxs.foreach(tx => pool.process(tx, us)._2.isInstanceOf[ProcessingOutcome.Declined] shouldBe true) @@ -156,7 +160,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val genesis = validFullBlock(None, us, bh) val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get val txs = validTransactionsFromUtxoState(wus) - val unconfirmedTxs = txs.map(tx => UnconfirmedTransaction(tx, None)) + val unconfirmedTxs = txs.map(tx => mempool.UnconfirmedTransaction(tx, None)) val maxSettings = settings.copy(nodeSettings = settings.nodeSettings.copy(minimalFeeAmount = Long.MaxValue)) val pool = ErgoMemPool.empty(maxSettings) @@ -180,7 +184,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val pool = ErgoMemPool.empty(settings) forAll(invalidBlockTransactionsGen) { blockTransactions => blockTransactions.txs.forall{tx => - val valRes = pool.process(UnconfirmedTransaction(tx, None), us)._2 + val valRes = pool.process(mempool.UnconfirmedTransaction(tx, None), us)._2 valRes.isInstanceOf[ProcessingOutcome.Invalidated] || valRes.isInstanceOf[ProcessingOutcome.Declined]} shouldBe true } @@ -188,7 +192,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec it should "accept only unique transactions" in { val pool = ErgoMemPool.empty(settings) - val tx = UnconfirmedTransaction(invalidErgoTransactionGen.sample.get, None) + val tx = mempool.UnconfirmedTransaction(invalidErgoTransactionGen.sample.get, None) pool.put(Seq(tx, tx, tx)).size shouldBe 1 } @@ -202,8 +206,8 @@ class ErgoMemPoolSpec extends AnyFlatSpec acc :+ masterTx.copy(outputCandidates = IndexedSeq( new ErgoBoxCandidate(idx * 10000 + 1, proposition, c.creationHeight, c.additionalTokens, c.additionalRegisters))) } - val lessPrioritizedTxs = txsWithAscendingPriority.init.map(tx => UnconfirmedTransaction(tx, None)) - val mostPrioritizedTx = UnconfirmedTransaction(txsWithAscendingPriority.last, None) + val lessPrioritizedTxs = txsWithAscendingPriority.init.map(tx => mempool.UnconfirmedTransaction(tx, None)) + val mostPrioritizedTx = mempool.UnconfirmedTransaction(txsWithAscendingPriority.last, None) pool = pool.put(lessPrioritizedTxs) pool.size shouldBe 4 @@ -217,7 +221,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get - val txs = validTransactionsFromUtxoState(wus).map(tx => UnconfirmedTransaction(tx, None)) + val txs = validTransactionsFromUtxoState(wus).map(tx => mempool.UnconfirmedTransaction(tx, None)) var pool = ErgoMemPool.empty(settings) txs.foreach { tx => pool = pool.put(tx) @@ -237,7 +241,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get - var txs = validTransactionsFromUtxoState(wus).map(tx => UnconfirmedTransaction(tx, None)) + var txs = validTransactionsFromUtxoState(wus).map(tx => mempool.UnconfirmedTransaction(tx, None)) val family_depth = 10 val limitedPoolSettings = settings.copy(nodeSettings = settings.nodeSettings.copy(mempoolCapacity = (family_depth + 1) * txs.size)) var pool = ErgoMemPool.empty(limitedPoolSettings) @@ -272,7 +276,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get - var txs = validTransactionsFromUtxoState(wus).map(tx => UnconfirmedTransaction(tx, None)) + var txs = validTransactionsFromUtxoState(wus).map(tx => mempool.UnconfirmedTransaction(tx, None)) var allTxs = txs val family_depth = 10 val limitedPoolSettings = settings.copy(nodeSettings = settings.nodeSettings.copy(mempoolCapacity = (family_depth + 1) * txs.size)) @@ -329,12 +333,6 @@ class ErgoMemPoolSpec extends AnyFlatSpec }) } - val weights = pool.weightedTransactionIds(11) - val ids = weights.map(_.id) - - pool.take(11).toSeq.map(_.transaction.id) shouldBe ids - pool.getAll.map(_.transaction.id) shouldBe ids - pool.getAllPrioritized.map(_.transaction.id) shouldBe ids val conformingTxs = pool.take(3).toSeq val stateWithTxs = wus.withUnconfirmedTransactions(conformingTxs) @@ -347,7 +345,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get - var txs = validTransactionsFromUtxoState(wus).map(tx => UnconfirmedTransaction(tx, None)) + var txs = validTransactionsFromUtxoState(wus).map(tx => mempool.UnconfirmedTransaction(tx, None)) var allTxs = txs val family_depth = 10 val limitedPoolSettings = settings.copy(nodeSettings = settings.nodeSettings.copy(mempoolCapacity = (family_depth + 1) * txs.size)) @@ -387,7 +385,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec val utx1 = new UnconfirmedTransaction(tx, None, now, now, None, None) val utx2 = new UnconfirmedTransaction(tx, None, now, now, None, None) val utx3 = new UnconfirmedTransaction(tx, None, now + 1, now + 1, None, None) - val updPool = pool.put(utx1, 100).remove(utx1).put(utx2, 500).put(utx3, 5000) + val updPool = pool.put(utx1, Some(100)).remove(utx1).put(utx2, Some(500)).put(utx3, Some(5000)) updPool.size shouldBe 1 updPool.get(utx3.id).get.lastCheckedTime shouldBe (now + 1) } @@ -411,7 +409,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec ContextExtension(Map((1: Byte) -> ByteArrayConstant(Array.fill(1 + txCount - i)(0: Byte)))))) ), IndexedSeq(out) ) - txs(i) = UnconfirmedTransaction(ErgoTransaction(txLike.inputs, txLike.outputCandidates), None) + txs(i) = mempool.UnconfirmedTransaction(ErgoTransaction(txLike.inputs, txLike.outputCandidates), None) } val pool = ErgoMemPool.empty(settings) diff --git a/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala b/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala index f3b89434a5..5c2e1369b6 100644 --- a/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/viewholder/ErgoNodeViewHolderSpec.scala @@ -3,7 +3,6 @@ package org.ergoplatform.nodeView.viewholder import java.io.File import org.ergoplatform.ErgoBoxCandidate import org.ergoplatform.modifiers.ErgoFullBlock -import org.ergoplatform.modifiers.mempool.UnconfirmedTransaction import org.ergoplatform.nodeView.history.ErgoHistory import org.ergoplatform.nodeView.state.StateType.Utxo import org.ergoplatform.nodeView.state._ @@ -15,6 +14,7 @@ import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages._ import org.ergoplatform.nodeView.ErgoNodeViewHolder import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.ChainProgress import org.ergoplatform.nodeView.mempool.ErgoMemPool.ProcessingOutcome.Accepted +import org.ergoplatform.nodeView.mempool.UnconfirmedTransaction import scorex.crypto.authds.{ADKey, SerializedAdProof} import scorex.testkit.utils.NoShrink import scorex.util.{ModifierId, bytesToId} diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletServiceSpec.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletServiceSpec.scala index ba5a5015cb..7b88f45fe1 100644 --- a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletServiceSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletServiceSpec.scala @@ -3,8 +3,8 @@ package org.ergoplatform.nodeView.wallet import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, R1} import org.ergoplatform._ import org.ergoplatform.db.DBSpec -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import org.ergoplatform.nodeView.wallet.WalletScanLogic.ScanResults import org.ergoplatform.nodeView.wallet.persistence.{OffChainRegistry, WalletRegistry, WalletStorage} import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, PaymentRequest} diff --git a/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala b/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala index e4d9de0836..08c7465659 100644 --- a/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala +++ b/src/test/scala/org/ergoplatform/sanity/ErgoSanity.scala @@ -1,14 +1,14 @@ package org.ergoplatform.sanity import akka.actor.ActorRef -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoBox, nodeView} import org.ergoplatform.modifiers.history.header.Header import org.ergoplatform.modifiers.history.BlockTransactions -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.modifiers.{BlockSection, ErgoFullBlock} import org.ergoplatform.network.{ErgoNodeViewSynchronizer, ErgoSyncTracker} import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoSyncInfo, ErgoSyncInfoMessageSpec} -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.ergoplatform.nodeView.state.{DigestState, ErgoState, UtxoState} import org.ergoplatform.sanity.ErgoSanity._ import org.ergoplatform.settings.ErgoSettings @@ -42,7 +42,7 @@ trait ErgoSanity[ST <: ErgoState[ST]] extends HistoryTests //Generators override lazy val transactionGenerator: Gen[ErgoTransaction] = invalidErgoTransactionGen override lazy val unconfirmedTxGenerator: Gen[UnconfirmedTransaction] = - invalidErgoTransactionGen.map(tx => UnconfirmedTransaction(tx, None)) + invalidErgoTransactionGen.map(tx => nodeView.mempool.UnconfirmedTransaction(tx, None)) override lazy val memPoolGenerator: Gen[MPool] = emptyMemPoolGen override def syntacticallyValidModifier(history: HT): Header = { diff --git a/src/test/scala/org/ergoplatform/utils/MempoolTestHelpers.scala b/src/test/scala/org/ergoplatform/utils/MempoolTestHelpers.scala index 2473790652..8bdd94e31f 100644 --- a/src/test/scala/org/ergoplatform/utils/MempoolTestHelpers.scala +++ b/src/test/scala/org/ergoplatform/utils/MempoolTestHelpers.scala @@ -1,8 +1,8 @@ package org.ergoplatform.utils import org.ergoplatform.ErgoBox.BoxId -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, OrderedTxPool} +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.{ErgoMemPoolReader, UnconfirmedTransaction} import scorex.util.ModifierId trait MempoolTestHelpers { @@ -16,7 +16,7 @@ trait MempoolTestHelpers { override def size: Int = ??? - override def weightedTransactionIds(limit: Int): Seq[OrderedTxPool.WeightedTxId] = ??? + override def txTimesAndWeights: Seq[(Long,Long)] = ??? override def getAll: Seq[UnconfirmedTransaction] = ??? diff --git a/src/test/scala/org/ergoplatform/utils/Stubs.scala b/src/test/scala/org/ergoplatform/utils/Stubs.scala index 66ffffeea1..a248392db4 100644 --- a/src/test/scala/org/ergoplatform/utils/Stubs.scala +++ b/src/test/scala/org/ergoplatform/utils/Stubs.scala @@ -8,11 +8,11 @@ import org.ergoplatform.mining.CandidateGenerator.Candidate import org.ergoplatform.mining.{AutolykosSolution, CandidateGenerator, ErgoMiner, WorkMessage} import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedTransaction import org.ergoplatform.nodeView.ErgoReadersHolder.{GetDataFromHistory, GetReaders, Readers} import org.ergoplatform.nodeView.history.ErgoHistory -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.ergoplatform.nodeView.mempool.ErgoMemPool.{ProcessingOutcome, SortingOption} import org.ergoplatform.nodeView.state.wrapped.WrappedUtxoState import org.ergoplatform.nodeView.state.{DigestState, ErgoStateContext, StateType} diff --git a/src/test/scala/scorex/testkit/properties/NodeViewSynchronizerTests.scala b/src/test/scala/scorex/testkit/properties/NodeViewSynchronizerTests.scala index 5e9955f61e..da778e6243 100644 --- a/src/test/scala/scorex/testkit/properties/NodeViewSynchronizerTests.scala +++ b/src/test/scala/scorex/testkit/properties/NodeViewSynchronizerTests.scala @@ -4,9 +4,9 @@ import akka.actor._ import akka.testkit.TestProbe import org.ergoplatform.modifiers.BlockSection import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoSyncInfo, ErgoSyncInfoMessageSpec} -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.scalacheck.Gen import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec diff --git a/src/test/scala/scorex/testkit/properties/mempool/MemoryPoolTest.scala b/src/test/scala/scorex/testkit/properties/mempool/MemoryPoolTest.scala index 3ee1108614..0405b291b7 100644 --- a/src/test/scala/scorex/testkit/properties/mempool/MemoryPoolTest.scala +++ b/src/test/scala/scorex/testkit/properties/mempool/MemoryPoolTest.scala @@ -1,7 +1,7 @@ package scorex.testkit.properties.mempool -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.scalacheck.Gen diff --git a/src/test/scala/scorex/testkit/properties/mempool/MempoolFilterPerformanceTest.scala b/src/test/scala/scorex/testkit/properties/mempool/MempoolFilterPerformanceTest.scala index 0d482a790a..81f9b86e34 100644 --- a/src/test/scala/scorex/testkit/properties/mempool/MempoolFilterPerformanceTest.scala +++ b/src/test/scala/scorex/testkit/properties/mempool/MempoolFilterPerformanceTest.scala @@ -1,8 +1,8 @@ package scorex.testkit.properties.mempool import java.security.MessageDigest -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks diff --git a/src/test/scala/scorex/testkit/properties/mempool/MempoolRemovalTest.scala b/src/test/scala/scorex/testkit/properties/mempool/MempoolRemovalTest.scala index 5506e6ac35..534d2d4181 100644 --- a/src/test/scala/scorex/testkit/properties/mempool/MempoolRemovalTest.scala +++ b/src/test/scala/scorex/testkit/properties/mempool/MempoolRemovalTest.scala @@ -1,8 +1,8 @@ package scorex.testkit.properties.mempool -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.history.ErgoHistory -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.scalacheck.Gen import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec diff --git a/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala b/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala index 891c854c7e..91e3ced79a 100644 --- a/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala +++ b/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala @@ -1,7 +1,8 @@ package scorex.testkit.properties.mempool -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.modifiers.mempool.ErgoTransaction +import org.ergoplatform.nodeView.mempool +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.scalacheck.Gen import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec @@ -16,7 +17,7 @@ trait MempoolTransactionsTest val transactionSeqGenerator: Gen[Seq[ErgoTransaction]] = Gen.nonEmptyContainerOf[Seq, ErgoTransaction](transactionGenerator) val unconfirmedTxSeqGenerator: Gen[Seq[UnconfirmedTransaction]] = - transactionSeqGenerator.map(txs => txs.map(tx => UnconfirmedTransaction(tx, None))) + transactionSeqGenerator.map(txs => txs.map(tx => mempool.UnconfirmedTransaction(tx, None))) property("Size of mempool should increase when adding a non-present transaction") { forAll(memPoolGenerator, unconfirmedTxGenerator) { (mp: ErgoMemPool, unconfirmedTx: UnconfirmedTransaction) => From 1b2863c92fa9942d1a08669f9b4935ae1dc31497 Mon Sep 17 00:00:00 2001 From: jellymlg Date: Tue, 28 Feb 2023 01:20:59 +0100 Subject: [PATCH 4/8] fixed bugs --- .../nodeView/mempool/OrderedTxPool.scala | 25 ++++++++----------- .../mempool/UnconfirmedTransaction.scala | 4 +-- .../http/routes/TransactionApiRouteSpec.scala | 2 +- .../nodeView/mempool/ErgoMemPoolSpec.scala | 1 - 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala index 235013ea71..1ed54c8ca3 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala @@ -62,21 +62,16 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], unconfirmedTx._feeFactor = feeFactor.get val newPool = - get(tx.id) match { - case Some(uTx) => - new OrderedTxPool( - (orderedTransactions - uTx) + unconfirmedTx, - invalidatedTxIds, - outputs, - inputs - ) - case None => - new OrderedTxPool( - orderedTransactions + unconfirmedTx, - invalidatedTxIds, - outputs ++ tx.outputs.map(_.id -> tx.id), - inputs ++ tx.inputs.map(_.boxId -> tx.id) - ).updateFamily(tx, unconfirmedTx.weight, System.currentTimeMillis(), 0) + if(orderedTransactions.contains(unconfirmedTx) && + unconfirmedTx.lastCheckedTime == get(tx.id).get.lastCheckedTime) + this + else { + new OrderedTxPool( + (orderedTransactions - unconfirmedTx) + unconfirmedTx, + invalidatedTxIds, + outputs ++ tx.outputs.map(_.id -> tx.id), + inputs ++ tx.inputs.map(_.boxId -> tx.id) + ).updateFamily(tx, unconfirmedTx.weight, System.currentTimeMillis(), 0) } if (newPool.orderedTransactions.size > mempoolCapacity) { diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala index f04d765bbf..56c235756f 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala @@ -53,7 +53,7 @@ class UnconfirmedTransaction(val transaction: ErgoTransaction, fee * 1024 / feeFactor } - var _weight: Long = -1 + private[mempool] var _weight: Long = -1 def weight(implicit ms: MonetarySettings, sortingOption: SortingOption): Long = { if(_weight == -1) _weight = feePerFactor @@ -61,7 +61,7 @@ class UnconfirmedTransaction(val transaction: ErgoTransaction, } def addWeight(weight: Long): UnconfirmedTransaction = { - _weight = weight + _weight += weight this } diff --git a/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala b/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala index 3c844b3027..da6249bf88 100644 --- a/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/TransactionApiRouteSpec.scala @@ -128,7 +128,7 @@ class TransactionApiRouteSpec extends AnyFlatSpec it should "get unconfirmed txs from mempool" in { Get(prefix + "/unconfirmed") ~> route ~> check { status shouldBe StatusCodes.OK - memPool.take(50).map(_.transaction).toSeq shouldBe responseAs[Seq[ErgoTransaction]] + memPool.take(50).toSeq.map(_.transaction) shouldBe responseAs[Seq[ErgoTransaction]] } } diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index 0b48c03bd5..7ec687cce1 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -333,7 +333,6 @@ class ErgoMemPoolSpec extends AnyFlatSpec }) } - val conformingTxs = pool.take(3).toSeq val stateWithTxs = wus.withUnconfirmedTransactions(conformingTxs) From 88cce1e6ea0f28b2189463a9ed7ecf305668acfb Mon Sep 17 00:00:00 2001 From: jellymlg Date: Tue, 28 Feb 2023 14:52:46 +0100 Subject: [PATCH 5/8] small changes --- .../nodeView/mempool/ErgoMemPool.scala | 6 +++--- .../nodeView/mempool/OrderedTxPool.scala | 18 ++++++++++-------- .../mempool/UnconfirmedTransaction.scala | 2 +- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala index 733b427784..f67cc51255 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala @@ -43,8 +43,8 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, pool.get(modifierId).map(unconfirmedTx => unconfirmedTx.transaction) } - override def contains(modifierId: ModifierId): Boolean = { - pool.contains(modifierId) + def contains(uTx: UnconfirmedTransaction): Boolean = { + pool.contains(uTx) } override def take(limit: Int): Iterable[UnconfirmedTransaction] = { @@ -231,7 +231,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, acceptIfNoDoubleSpend(unconfirmedTx, validationStartTime) } } else { - val msg = if (this.contains(tx.id)) { + val msg = if (contains(unconfirmedTx)) { s"Pool can not accept transaction ${tx.id}, it is already in the mempool" } else if (pool.size == settings.nodeSettings.mempoolCapacity) { s"Pool can not accept transaction ${tx.id}, the mempool is full" diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala index 1ed54c8ca3..1bb1b79632 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala @@ -58,12 +58,13 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], def put(unconfirmedTx: UnconfirmedTransaction, feeFactor: Option[Int] = None): OrderedTxPool = { val tx = unconfirmedTx.transaction - if(feeFactor.isDefined) - unconfirmedTx._feeFactor = feeFactor.get + feeFactor match { + case Some(factor) => unconfirmedTx._feeFactor = factor + case _ => + } val newPool = - if(orderedTransactions.contains(unconfirmedTx) && - unconfirmedTx.lastCheckedTime == get(tx.id).get.lastCheckedTime) + if(contains(unconfirmedTx)) this else { new OrderedTxPool( @@ -132,16 +133,17 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], * */ def canAccept(unconfirmedTx: UnconfirmedTransaction): Boolean = { - !contains(unconfirmedTx.id) && size <= mempoolCapacity + !contains(unconfirmedTx) && size <= mempoolCapacity } /** * - * @param id - transaction id + * @param uTx - unconfirmed transaction * @return - true, if transaction is in the pool or invalidated earlier, false otherwise */ - def contains(id: ModifierId): Boolean = - get(id).isDefined + def contains(uTx: UnconfirmedTransaction): Boolean = + orderedTransactions.contains(uTx) && + uTx.lastCheckedTime == get(uTx.id).get.lastCheckedTime def isInvalidated(id: ModifierId): Boolean = invalidatedTxIds.mightContain(id) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala index 56c235756f..0c080f1b02 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/UnconfirmedTransaction.scala @@ -53,7 +53,7 @@ class UnconfirmedTransaction(val transaction: ErgoTransaction, fee * 1024 / feeFactor } - private[mempool] var _weight: Long = -1 + private var _weight: Long = -1 def weight(implicit ms: MonetarySettings, sortingOption: SortingOption): Long = { if(_weight == -1) _weight = feePerFactor From c08b9aeac5e437aab86ebd91a8bda68ae9e84cc4 Mon Sep 17 00:00:00 2001 From: jellymlg Date: Fri, 31 Mar 2023 03:08:43 +0200 Subject: [PATCH 6/8] Changed TreeSet to TreeMap for more efficient containment check --- .../nodeView/mempool/ErgoMemPool.scala | 24 ++++++--- .../nodeView/mempool/OrderedTxPool.scala | 52 +++++++++++++------ .../nodeView/mempool/ErgoMemPoolSpec.scala | 8 +-- .../mempool/MempoolTransactionsTest.scala | 4 +- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala index f67cc51255..a03ffcd552 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/ErgoMemPool.scala @@ -48,12 +48,12 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } override def take(limit: Int): Iterable[UnconfirmedTransaction] = { - pool.orderedTransactions.take(limit) + pool.orderedTransactions.values.take(limit) } def random(limit: Int): Iterable[UnconfirmedTransaction] = { val result = mutable.WrappedArray.newBuilder[UnconfirmedTransaction] - val txSeq = pool.orderedTransactions.to[Vector] + val txSeq = pool.orderedTransactions.values.to[Vector] val total = txSeq.size val start = if (total <= limit) { 0 @@ -71,14 +71,14 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, result.result() } - override def getAll: Seq[UnconfirmedTransaction] = pool.orderedTransactions.toSeq + override def getAll: Seq[UnconfirmedTransaction] = pool.orderedTransactions.values.toSeq override def getAll(ids: Seq[ModifierId]): Seq[UnconfirmedTransaction] = ids.flatMap(pool.get) /** * Returns all transactions resided in pool sorted by weight in descending order */ - override def getAllPrioritized: Seq[UnconfirmedTransaction] = pool.orderedTransactions.toSeq + override def getAllPrioritized: Seq[UnconfirmedTransaction] = pool.orderedTransactions.values.toSeq /** * Method to put a transaction into the memory pool. Validation of the transactions against @@ -101,8 +101,16 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } /** - * Remove transaction from the pool + * Remove unconfirmed transaction from the pool */ + def remove(utx: UnconfirmedTransaction): ErgoMemPool = { + log.debug(s"Removing unconfirmed transaction ${utx.id} from the mempool") + new ErgoMemPool(pool.remove(utx), updateStatsOnRemoval(utx.transaction), sortingOption) + } + + /** + * Remove transaction from the pool + */ def remove(tx: ErgoTransaction): ErgoMemPool = { log.debug(s"Removing transaction ${tx.id} from the mempool") new ErgoMemPool(pool.remove(tx), updateStatsOnRemoval(tx), sortingOption) @@ -168,7 +176,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } else { val poolSizeLimit = nodeSettings.mempoolCapacity if (pool.size == poolSizeLimit && - unconfirmedTransaction.weight <= pool.orderedTransactions.lastKey.weight) { + unconfirmedTransaction.weight <= pool.orderedTransactions.last._2.weight) { val exc = new Exception("Transaction pays less than any other in the pool being full") this -> new ProcessingOutcome.Declined(exc, validationStartTime) } else { @@ -250,7 +258,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, } } - def txTimesAndWeights: Seq[(Long,Long)] = pool.orderedTransactions.toSeq.map(uTx => uTx.createdTime -> uTx.weight(monetarySettings, sortingOption)) + def txTimesAndWeights: Seq[(Long,Long)] = getAll.map(uTx => uTx.createdTime -> uTx.weight(monetarySettings, sortingOption)) private def extractFee(tx: ErgoTransaction): Long = { tx.outputs @@ -290,7 +298,7 @@ class ErgoMemPool private[mempool](private[mempool] val pool: OrderedTxPool, val feePerKb = txFee * 1024 / txSize // Find position of entry in mempool - val posInPool = pool.orderedTransactions.dropWhile(_.weight(monetarySettings, sortingOption) > feePerKb).size + val posInPool = pool.orderedTransactions.dropWhile(_._2.weight(monetarySettings, sortingOption) > feePerKb).size // Time since statistics measurement interval (needed to calculate average tx rate) val elapsed = System.currentTimeMillis() - stats.startMeasurement diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala index 1bb1b79632..423de9b059 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala @@ -3,20 +3,21 @@ package org.ergoplatform.nodeView.mempool import org.ergoplatform.ErgoBox.BoxId import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption +import org.ergoplatform.nodeView.mempool.OrderedTxPool.WeightedKey import org.ergoplatform.settings.{Algos, ErgoSettings, MonetarySettings} import scorex.util.{ModifierId, ScorexLogging} -import scala.collection.immutable.{TreeMap, TreeSet} +import scala.collection.immutable.TreeMap /** * An immutable pool of transactions of limited size with priority management and blacklisting support. * - * @param orderedTransactions - collection containing transactions ordered by `tx.weight` + * @param orderedTransactions - collection mapping `WeightedKey(id,weight)` -> `UnconfirmedTransaction`, ordered by weight * @param invalidatedTxIds - invalidated transaction ids in bloom filters * @param outputs - mapping `box.id` -> `ModifierId` required for getting a transaction by its output box * @param inputs - mapping `box.id` -> `ModifierId` required for getting a transaction by its input box id */ -class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], +class OrderedTxPool(val orderedTransactions: TreeMap[WeightedKey, UnconfirmedTransaction], val invalidatedTxIds: ApproximateCacheLike[String], val outputs: TreeMap[BoxId, ModifierId], val inputs: TreeMap[BoxId, ModifierId]) @@ -40,7 +41,7 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], def size: Int = orderedTransactions.size def get(id: ModifierId): Option[UnconfirmedTransaction] = - orderedTransactions.find(_.id == id) + orderedTransactions.valuesIterator.find(_.id == id) /** @@ -67,8 +68,9 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], if(contains(unconfirmedTx)) this else { + val key = WeightedKey(unconfirmedTx) new OrderedTxPool( - (orderedTransactions - unconfirmedTx) + unconfirmedTx, + (orderedTransactions - key) + Tuple2(key, unconfirmedTx), invalidatedTxIds, outputs ++ tx.outputs.map(_.id -> tx.id), inputs ++ tx.inputs.map(_.boxId -> tx.id) @@ -76,8 +78,7 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], } if (newPool.orderedTransactions.size > mempoolCapacity) { - val victim = newPool.orderedTransactions.last - newPool.remove(victim) + newPool.remove(newPool.orderedTransactions.last._2) } else { newPool } @@ -96,7 +97,7 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], get(tx.id) match { case Some(wtx) => new OrderedTxPool( - orderedTransactions - wtx, + orderedTransactions - WeightedKey(wtx), invalidatedTxIds, outputs -- tx.outputs.map(_.id), inputs -- tx.inputs.map(_.boxId) @@ -105,7 +106,19 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], } } - def remove(utx: UnconfirmedTransaction): OrderedTxPool = remove(utx.transaction) + def remove(utx: UnconfirmedTransaction): OrderedTxPool = { + val key = WeightedKey(utx.id, utx.weight) + orderedTransactions.get(key) match { + case Some(wtx) => + new OrderedTxPool( + orderedTransactions - key, + invalidatedTxIds, + outputs -- utx.transaction.outputs.map(_.id), + inputs -- utx.transaction.inputs.map(_.boxId) + ).updateFamily(utx.transaction, -wtx.weight, System.currentTimeMillis(), depth = 0) + case None => this + } + } /** * Remove transaction from the pool and add it to invalidated transaction ids cache @@ -115,7 +128,7 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], get(tx.id) match { case Some(wtx) => new OrderedTxPool( - orderedTransactions - wtx, + orderedTransactions - WeightedKey(wtx), invalidatedTxIds.put(tx.id), outputs -- tx.outputs.map(_.id), inputs -- tx.inputs.map(_.boxId) @@ -142,8 +155,7 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], * @return - true, if transaction is in the pool or invalidated earlier, false otherwise */ def contains(uTx: UnconfirmedTransaction): Boolean = - orderedTransactions.contains(uTx) && - uTx.lastCheckedTime == get(uTx.id).get.lastCheckedTime + orderedTransactions.get(WeightedKey(uTx)).exists(_.lastCheckedTime == uTx.lastCheckedTime) def isInvalidated(id: ModifierId): Boolean = invalidatedTxIds.mightContain(id) @@ -176,7 +188,7 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], parentTxs.foldLeft(this) { case (pool, uTx) => val parent = uTx.transaction val newPool = new OrderedTxPool( - pool.orderedTransactions - uTx + uTx.addWeight(weight), + (pool.orderedTransactions - WeightedKey(uTx)) + Tuple2(WeightedKey(uTx.id, uTx.weight + weight), uTx.addWeight(weight)), invalidatedTxIds, parent.outputs.foldLeft(pool.outputs)((newOutputs, box) => newOutputs.updated(box.id, parent.id)), parent.inputs.foldLeft(pool.inputs)((newInputs, inp) => newInputs.updated(inp.boxId, parent.id)) @@ -189,17 +201,23 @@ class OrderedTxPool(val orderedTransactions: TreeSet[UnconfirmedTransaction], object OrderedTxPool { + case class WeightedKey(_1: ModifierId, _2: Long) + + case object WeightedKey { + def apply(utx: UnconfirmedTransaction)(implicit ms: MonetarySettings, sortingOption: SortingOption): WeightedKey = + WeightedKey(utx.id, utx.weight) + } + private implicit val ordBoxId: Ordering[BoxId] = Ordering[String].on(b => Algos.encode(b)) def empty(settings: ErgoSettings, sortingOption: SortingOption): OrderedTxPool = { val cacheSettings = settings.cacheSettings.mempool val frontCacheSize = cacheSettings.invalidModifiersCacheSize val frontCacheExpiration = cacheSettings.invalidModifiersCacheExpiration - implicit val ms: MonetarySettings = settings.chainSettings.monetary - implicit val ordWeight: Ordering[UnconfirmedTransaction] = - Ordering[(Long, ModifierId)].on(x => (-x.weight(ms, sortingOption), x.id)) + implicit val ordWeight: Ordering[WeightedKey] = + Ordering[(Long, ModifierId)].on(x => (-x._2, x._1)) new OrderedTxPool( - TreeSet.empty[UnconfirmedTransaction], + TreeMap.empty[WeightedKey, UnconfirmedTransaction], ExpiringApproximateCache.empty(frontCacheSize, frontCacheExpiration), TreeMap.empty[BoxId, ModifierId], TreeMap.empty[BoxId, ModifierId] diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index 7ec687cce1..1211867a74 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -67,7 +67,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec var poolSize = ErgoMemPool.empty(sortBySizeSettings) val uTx1 = mempool.UnconfirmedTransaction(tx, None) poolSize = poolSize.process(uTx1, wus)._1 - poolSize.pool.orderedTransactions.firstKey.weight(ms,fPb) shouldBe uTx1.weight(ms,fPb) + poolSize.pool.orderedTransactions.firstKey._2 shouldBe uTx1.weight(ms,fPb) val sortByCostSettings: ErgoSettings = settings.copy( nodeSettings = settings.nodeSettings.copy( @@ -77,7 +77,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec var poolCost = ErgoMemPool.empty(sortByCostSettings) val uTx2 = mempool.UnconfirmedTransaction(tx, None) poolCost = poolCost.process(uTx2, wus)._1 - poolCost.pool.orderedTransactions.firstKey.weight(ms,fPc) shouldBe uTx2.withCost(wus.validateWithCost(tx, Int.MaxValue).get).weight(ms,fPc) + poolCost.pool.orderedTransactions.firstKey._2 shouldBe uTx2.withCost(wus.validateWithCost(tx, Int.MaxValue).get).weight(ms,fPc) } it should "decline already contained transaction" in { @@ -298,7 +298,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec } pool.size shouldBe (family_depth + 1) * txs.size allTxs.foreach { tx => - pool = pool.remove(tx.transaction) + pool = pool.remove(tx) } pool.size shouldBe 0 } @@ -370,7 +370,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec pool.stats.snapTakenTxns shouldBe MemPoolStatistics(System.currentTimeMillis(),0,System.currentTimeMillis()).snapTakenTxns allTxs.foreach { tx => - pool = pool.remove(tx.transaction) + pool = pool.remove(tx) } pool.size shouldBe 0 pool.stats.takenTxns shouldBe (family_depth + 1) * txs.size diff --git a/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala b/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala index 91e3ced79a..286dbba8e9 100644 --- a/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala +++ b/src/test/scala/scorex/testkit/properties/mempool/MempoolTransactionsTest.scala @@ -85,7 +85,7 @@ trait MempoolTransactionsTest property("Size of mempool should decrease when removing a present transaction") { forAll(memPoolGenerator, unconfirmedTxSeqGenerator) { (mp: ErgoMemPool, unconfirmedTxs: Seq[UnconfirmedTransaction]) => val m: ErgoMemPool = mp.put(unconfirmedTxs) - val m2: ErgoMemPool = m.remove(unconfirmedTxs.headOption.get.transaction) + val m2: ErgoMemPool = m.remove(unconfirmedTxs.headOption.get) m2.size shouldBe unconfirmedTxs.size - 1 } } @@ -93,7 +93,7 @@ trait MempoolTransactionsTest property("Size of mempool should not decrease when removing a non-present transaction") { forAll(memPoolGenerator, unconfirmedTxSeqGenerator, unconfirmedTxGenerator) { (mp: ErgoMemPool, unconfirmedTxs: Seq[UnconfirmedTransaction], unconfirmedTx: UnconfirmedTransaction) => val m: ErgoMemPool = mp.put(unconfirmedTxs) - val m2: ErgoMemPool = m.remove(unconfirmedTx.transaction) + val m2: ErgoMemPool = m.remove(unconfirmedTx) m2.size shouldBe unconfirmedTxs.size } } From 966b735f1e3386950b9b798fb18ffa227d35b636 Mon Sep 17 00:00:00 2001 From: jellymlg Date: Tue, 11 Apr 2023 16:11:08 +0200 Subject: [PATCH 7/8] small changes --- .../scala/org/ergoplatform/http/api/WalletApiRoute.scala | 3 +-- .../org/ergoplatform/network/ErgoNodeViewSynchronizer.scala | 2 +- .../org/ergoplatform/nodeView/ErgoNodeViewHolder.scala | 2 +- .../org/ergoplatform/nodeView/mempool/OrderedTxPool.scala | 4 ++-- .../org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala | 6 +++--- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala index ce6e2d1d42..282d9fbf73 100644 --- a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala @@ -25,7 +25,6 @@ import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} import akka.http.scaladsl.server.MissingQueryParamRejection -import org.ergoplatform.nodeView.mempool import org.ergoplatform.nodeView.mempool.UnconfirmedTransaction case class WalletApiRoute(readersHolder: ActorRef, @@ -178,7 +177,7 @@ case class WalletApiRoute(readersHolder: ActorRef, requests, inputsRaw, dataInputsRaw, - tx => Future(Success(mempool.UnconfirmedTransaction(tx, source = None))), + tx => Future(Success(UnconfirmedTransaction(tx, source = None))), utx => ApiResponse(utx.transaction) ) } diff --git a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala index e0efc0faa9..5d6ff6d481 100644 --- a/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala +++ b/src/main/scala/org/ergoplatform/network/ErgoNodeViewSynchronizer.scala @@ -693,7 +693,7 @@ class ErgoNodeViewSynchronizer(networkControllerRef: ActorRef, } else { ErgoTransactionSerializer.parseBytesTry(bytes) match { case Success(tx) if id == tx.id => - val utx = mempool.UnconfirmedTransaction(tx, bytes, Some(remote)) + val utx = UnconfirmedTransaction(tx, bytes, Some(remote)) viewHolderRef ! TransactionFromRemote(utx) case _ => // Penalize peer and do nothing - it will be switched to correct state on CheckDelivery diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 4a7f90a0e8..59f3e2695f 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -374,7 +374,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti val rolledBackTxs = blocksRemoved .flatMap(extractTransactions) .filter(tx => !appliedTxs.exists(_.id == tx.id)) - .map(tx => mempool.UnconfirmedTransaction(tx, None)) + .map(tx => UnconfirmedTransaction(tx, None)) memPool.remove(appliedTxs).put(rolledBackTxs) } diff --git a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala index 423de9b059..7918ce5631 100644 --- a/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala +++ b/src/main/scala/org/ergoplatform/nodeView/mempool/OrderedTxPool.scala @@ -70,7 +70,7 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedKey, UnconfirmedTra else { val key = WeightedKey(unconfirmedTx) new OrderedTxPool( - (orderedTransactions - key) + Tuple2(key, unconfirmedTx), + orderedTransactions.updated(key, unconfirmedTx), invalidatedTxIds, outputs ++ tx.outputs.map(_.id -> tx.id), inputs ++ tx.inputs.map(_.boxId -> tx.id) @@ -125,7 +125,7 @@ class OrderedTxPool(val orderedTransactions: TreeMap[WeightedKey, UnconfirmedTra */ def invalidate(unconfirmedTx: UnconfirmedTransaction): OrderedTxPool = { val tx = unconfirmedTx.transaction - get(tx.id) match { + orderedTransactions.get(WeightedKey(unconfirmedTx)) match { case Some(wtx) => new OrderedTxPool( orderedTransactions - WeightedKey(wtx), diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index 1211867a74..975c98476c 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -84,13 +84,13 @@ class ErgoMemPoolSpec extends AnyFlatSpec val (us, bh) = createUtxoState(parameters) val genesis = validFullBlock(None, us, bh) val wus = WrappedUtxoState(us, bh, stateConstants, parameters).applyModifier(genesis)(_ => ()).get - val txs = validTransactionsFromUtxoState(wus) + val txs = validTransactionsFromUtxoState(wus).map(UnconfirmedTransaction(_, None)) var pool = ErgoMemPool.empty(settings) txs.foreach { tx => - pool = pool.put(mempool.UnconfirmedTransaction(tx, None)) + pool = pool.put(tx) } txs.foreach { tx => - pool.process(mempool.UnconfirmedTransaction(tx, None), us)._2.isInstanceOf[ProcessingOutcome.Declined] shouldBe true + pool.process(tx, us)._2.isInstanceOf[ProcessingOutcome.Declined] shouldBe true } } From 25cccf70920367ca955f7634e4c19c6d4eb610ce Mon Sep 17 00:00:00 2001 From: jellymlg Date: Sat, 1 Jul 2023 14:51:33 +0200 Subject: [PATCH 8/8] fixed imports in tests --- .../ergoplatform/mining/ErgoMinerSpec.scala | 3 ++- .../nodeView/NodeViewSynchronizerTests.scala | 4 ++-- .../nodeView/mempool/ErgoMemPoolSpec.scala | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala index 16e83f5d00..aefdb61cad 100644 --- a/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala +++ b/src/test/scala/org/ergoplatform/mining/ErgoMinerSpec.scala @@ -9,11 +9,12 @@ import org.ergoplatform.mining.CandidateGenerator.{Candidate, GenerateCandidate} import org.ergoplatform.mining.ErgoMiner.StartMining import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction, UnsignedErgoTransaction} +import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction} import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages.FullBlockApplied import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.LocallyGeneratedTransaction import org.ergoplatform.nodeView.ErgoReadersHolder.{GetReaders, Readers} import org.ergoplatform.nodeView.history.ErgoHistoryReader +import org.ergoplatform.nodeView.mempool.UnconfirmedTransaction import org.ergoplatform.nodeView.state._ import org.ergoplatform.nodeView.wallet._ import org.ergoplatform.nodeView.{ErgoNodeViewRef, ErgoReadersHolderRef} diff --git a/src/test/scala/org/ergoplatform/nodeView/NodeViewSynchronizerTests.scala b/src/test/scala/org/ergoplatform/nodeView/NodeViewSynchronizerTests.scala index c867070220..01a2c41e56 100644 --- a/src/test/scala/org/ergoplatform/nodeView/NodeViewSynchronizerTests.scala +++ b/src/test/scala/org/ergoplatform/nodeView/NodeViewSynchronizerTests.scala @@ -4,11 +4,11 @@ import akka.actor.{ActorRef, ActorSystem} import akka.testkit.TestProbe import org.ergoplatform.modifiers.BlockSection import org.ergoplatform.modifiers.history.header.Header -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.network.ErgoNodeViewSynchronizer.ReceivableMessages._ import org.ergoplatform.nodeView.ErgoNodeViewHolder.ReceivableMessages.{GetNodeViewChanges, ModifiersFromRemote} import org.ergoplatform.nodeView.history.{ErgoHistory, ErgoSyncInfo, ErgoSyncInfoMessageSpec} -import org.ergoplatform.nodeView.mempool.ErgoMemPool +import org.ergoplatform.nodeView.mempool.{ErgoMemPool, UnconfirmedTransaction} import org.ergoplatform.nodeView.state.UtxoState.ManifestId import org.ergoplatform.nodeView.state._ import org.ergoplatform.settings.Algos diff --git a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala index 4e7332ee64..51d26fdf10 100644 --- a/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/mempool/ErgoMemPoolSpec.scala @@ -2,7 +2,7 @@ package org.ergoplatform.nodeView.mempool import org.ergoplatform.{ErgoBoxCandidate, Input} import org.ergoplatform.nodeView.mempool.ErgoMemPool.SortingOption -import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction} +import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.mempool.ErgoMemPool.ProcessingOutcome import org.ergoplatform.nodeView.state.wrapped.WrappedUtxoState import org.ergoplatform.settings.{ErgoSettings, MonetarySettings} @@ -64,7 +64,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec )) var poolSize = ErgoMemPool.empty(sortBySizeSettings) - val uTx1 = mempool.UnconfirmedTransaction(tx, None) + val uTx1 = UnconfirmedTransaction(tx, None) poolSize = poolSize.process(uTx1, wus)._1 poolSize.pool.orderedTransactions.firstKey._2 shouldBe uTx1.weight(ms,fPb) @@ -74,7 +74,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec )) var poolCost = ErgoMemPool.empty(sortByCostSettings) - val uTx2 = mempool.UnconfirmedTransaction(tx, None) + val uTx2 = UnconfirmedTransaction(tx, None) poolCost = poolCost.process(uTx2, wus)._1 poolCost.pool.orderedTransactions.firstKey._2 shouldBe uTx2.withCost(wus.validateWithCost(tx, Int.MaxValue).get).weight(ms,fPc) } @@ -332,6 +332,13 @@ class ErgoMemPoolSpec extends AnyFlatSpec }) } + val weights = pool.pool.orderedTransactions.keysIterator.take(11).toSeq + val ids = weights.map(_._1) + + pool.take(11).toSeq.map(_.transaction.id) shouldBe ids + pool.getAll.map(_.transaction.id) shouldBe ids + pool.getAllPrioritized.map(_.transaction.id) shouldBe ids + val conformingTxs = pool.take(3).toSeq val stateWithTxs = wus.withUnconfirmedTransactions(conformingTxs) @@ -389,9 +396,9 @@ class ErgoMemPoolSpec extends AnyFlatSpec } it should "accept double-spending transaction if it is paying more than one already sitting in the pool" in { - val (us, bh) = createUtxoState(extendedParameters) + val (us, bh) = createUtxoState(settings) val genesis = validFullBlock(None, us, bh) - val wus = WrappedUtxoState(us, bh, stateConstants, extendedParameters).applyModifier(genesis)(_ => ()).get + val wus = WrappedUtxoState(us, bh, settings, extendedParameters).applyModifier(genesis)(_ => ()).get val input = wus.takeBoxes(100).collectFirst { case box if box.ergoTree == TrueLeaf.toSigmaProp.treeWithSegregation => box @@ -407,7 +414,7 @@ class ErgoMemPoolSpec extends AnyFlatSpec ContextExtension(Map((1: Byte) -> ByteArrayConstant(Array.fill(1 + txCount - i)(0: Byte)))))) ), IndexedSeq(out) ) - txs(i) = mempool.UnconfirmedTransaction(ErgoTransaction(txLike.inputs, txLike.outputCandidates), None) + txs(i) = UnconfirmedTransaction(ErgoTransaction(txLike.inputs, txLike.outputCandidates), None) } val pool = ErgoMemPool.empty(settings)