diff --git a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala index 1232e882fd..5aea87d2f3 100644 --- a/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala +++ b/src/main/scala/org/ergoplatform/http/api/WalletApiRoute.scala @@ -307,8 +307,16 @@ case class WalletApiRoute(readersHolder: ActorRef, def unspentBoxesR: Route = (path("boxes" / "unspent") & get & boxParams) { (minConfNum, maxConfNum, minHeight, maxHeight, limit, offset) => val considerUnconfirmed = minConfNum == -1 + // Determine actual height range to query from DB + val (actualMinHeight, actualMaxHeight) = if (minHeight > 0 || maxHeight < Int.MaxValue) { + // Height-based filtering: use height range for DB query + (minHeight, if (maxHeight == -1) Int.MaxValue else maxHeight) + } else { + // No height filtering or only confirmation-based filtering + (0, Int.MaxValue) + } withWallet { wallet => - wallet.walletBoxes(unspentOnly = true, considerUnconfirmed) + wallet.walletBoxes(unspentOnly = true, considerUnconfirmed, actualMinHeight, actualMaxHeight) .map { boxes => boxes .filter(boxConfirmationHeightFilter(_, minConfNum, maxConfNum, minHeight, maxHeight)) @@ -321,7 +329,7 @@ case class WalletApiRoute(readersHolder: ActorRef, (minConfNum, maxConfNum, minHeight, maxHeight, limit, offset) => val considerUnconfirmed = minConfNum == -1 withWallet { - _.walletBoxes(unspentOnly = false, considerUnconfirmed = considerUnconfirmed) + _.walletBoxes(unspentOnly = false, considerUnconfirmed = considerUnconfirmed, 0, Int.MaxValue) .map { _.filter(boxConfirmationHeightFilter(_, minConfNum, maxConfNum, minHeight, maxHeight)) .slice(offset, offset + limit) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala index 78d7621a25..dc0aea4be2 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActor.scala @@ -174,8 +174,8 @@ class ErgoWalletActor(settings: ErgoSettings, * Read wallet boxes, unspent only (if corresponding flag is set), or all (both spent and unspent). * If considerUnconfirmed flag is set, mempool contents is considered as well. */ - case GetWalletBoxes(unspent, considerUnconfirmed) => - val boxes = ergoWalletService.getWalletBoxes(state, unspent, considerUnconfirmed) + case GetWalletBoxes(unspent, considerUnconfirmed, minHeight, maxHeight) => + val boxes = ergoWalletService.getWalletBoxes(state, unspent, considerUnconfirmed, minHeight, maxHeight) sender() ! boxes case GetScanUnspentBoxes(scanId, considerUnconfirmed, minHeight, maxHeight) => diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActorMessages.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActorMessages.scala index a5b3d470fe..ed095f6f64 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActorMessages.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletActorMessages.scala @@ -172,8 +172,10 @@ object ErgoWalletActorMessages { * @param unspentOnly - return only unspent boxes * @param considerUnconfirmed - consider mempool (filter our unspent boxes spent in the pool if unspent = true, add * boxes created in the pool for both values of unspentOnly). + * @param minHeight - min inclusion height of unspent boxes + * @param maxHeight - max inclusion height of unspent boxes */ - final case class GetWalletBoxes(unspentOnly: Boolean, considerUnconfirmed: Boolean) + final case class GetWalletBoxes(unspentOnly: Boolean, considerUnconfirmed: Boolean, minHeight: Int, maxHeight: Int) /** * Get boxes by requested params diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala index c566a6fd15..5d9d108f24 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletReader.scala @@ -79,8 +79,8 @@ trait ErgoWalletReader extends NodeViewComponent { def getPrivateKeyFromPath(path: DerivationPath): Future[Try[DLogProverInput]] = (walletActor ? GetPrivateKeyFromPath(path)).mapTo[Try[DLogProverInput]] - def walletBoxes(unspentOnly: Boolean, considerUnconfirmed: Boolean): Future[Seq[WalletBox]] = - (walletActor ? GetWalletBoxes(unspentOnly, considerUnconfirmed)).mapTo[Seq[WalletBox]] + def walletBoxes(unspentOnly: Boolean, considerUnconfirmed: Boolean, minHeight: Int = 0, maxHeight: Int = Int.MaxValue): Future[Seq[WalletBox]] = + (walletActor ? GetWalletBoxes(unspentOnly, considerUnconfirmed, minHeight, maxHeight)).mapTo[Seq[WalletBox]] def scanUnspentBoxes(scanId: ScanId, considerUnconfirmed: Boolean, minHeight: Int, maxHeight: Int): Future[Seq[WalletBox]] = (walletActor ? GetScanUnspentBoxes(scanId, considerUnconfirmed, minHeight, maxHeight)).mapTo[Seq[WalletBox]] diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala index 3ddcdc255b..4a07b69a13 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletService.scala @@ -111,7 +111,7 @@ trait ErgoWalletService { */ def recreateStorage(state: ErgoWalletState, settings: ErgoSettings): Try[ErgoWalletState] - def getWalletBoxes(state: ErgoWalletState, unspentOnly: Boolean, considerUnconfirmed: Boolean): Seq[WalletBox] + def getWalletBoxes(state: ErgoWalletState, unspentOnly: Boolean, considerUnconfirmed: Boolean, minHeight: Int, maxHeight: Int): Seq[WalletBox] /** * @param state current wallet state @@ -396,10 +396,16 @@ class ErgoWalletServiceImpl(override val ergoSettings: ErgoSettings) extends Erg state.copy(storage = WalletStorage.readOrCreate(settings)) } - override def getWalletBoxes(state: ErgoWalletState, unspentOnly: Boolean, considerUnconfirmed: Boolean): Seq[WalletBox] = { + override def getWalletBoxes(state: ErgoWalletState, unspentOnly: Boolean, considerUnconfirmed: Boolean, minHeight: Int, maxHeight: Int): Seq[WalletBox] = { val currentHeight = state.fullHeight val boxes = if (unspentOnly) { - val confirmed = state.registry.walletUnspentBoxes(state.maxInputsToUse * BoxSelector.ScanDepthFactor) + val confirmed = if (minHeight > 0 || maxHeight < Int.MaxValue) { + // Use height-based query to extract only boxes in given range from DB, like done for /wallet/transactions + state.registry.walletUnspentBoxesByInclusionHeight(minHeight, maxHeight) + } else { + // Use the original query when no height filtering is needed + state.registry.walletUnspentBoxes(state.maxInputsToUse * BoxSelector.ScanDepthFactor) + } if (considerUnconfirmed) { // We filter out spent boxes in the same way as wallet does when assembling a transaction (confirmed ++ state.offChainRegistry.offChainBoxes).filter(state.walletFilter) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala index f3e7deba48..5359ffd761 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/persistence/WalletRegistry.scala @@ -158,7 +158,15 @@ class WalletRegistry(private val store: LDBVersionedStore)(ws: WalletSettings) e * Unspent boxes belong to the wallet (payments scan) */ def walletUnspentBoxes(limit: Int = Int.MaxValue): Seq[TrackedBox] = unspentBoxes(Constants.PaymentsScanId, limit) - + /** + * Read unspent wallet boxes within height range (payments scan) + * + * @param heightFrom - min inclusion height of unspent boxes + * @param heightTo - max inclusion height of unspent boxes + * @return sequences of wallet-related unspent boxes found in the database + */ +def walletUnspentBoxesByInclusionHeight(heightFrom: Height, heightTo: Height): Seq[TrackedBox] = + unspentBoxesByInclusionHeight(Constants.PaymentsScanId, heightFrom, heightTo) /** * Spent boxes belong to the wallet (payments scan) */