Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add excludeMempoolSpent Query Parameter to /blockchain/box/unspent/byAddress #2131

Merged
merged 13 commits into from
Jul 26, 2024
9 changes: 8 additions & 1 deletion src/main/resources/api/openapi-ai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,13 @@ paths:
schema:
type: string
default: desc
- in: query
name: excludeMempoolSpent
required: false
description: if true exclude spent inputs from mempool
schema:
type: boolean
default: false
responses:
'200':
description: unspent boxes associated with wanted address
Expand Down Expand Up @@ -1479,4 +1486,4 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/ApiError'
$ref: '#/components/schemas/ApiError'
7 changes: 7 additions & 0 deletions src/main/resources/api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6413,6 +6413,13 @@ paths:
schema:
type: boolean
default: false
- in: query
name: excludeMempoolSpent
required: false
description: if true exclude spent inputs from mempool
schema:
type: boolean
default: false
responses:
'200':
description: unspent boxes associated with wanted address
Expand Down
58 changes: 46 additions & 12 deletions src/main/scala/org/ergoplatform/http/api/BlockchainApiRoute.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,39 +242,73 @@ case class BlockchainApiRoute(readersHolder: ActorRef, ergoSettings: ErgoSetting
validateAndGetBoxesByAddress(address, offset, limit)
}

private def getBoxesByAddressUnspent(addr: ErgoAddress, offset: Int, limit: Int, sortDir: Direction, unconfirmed: Boolean): Future[Seq[IndexedErgoBox]] =
getHistoryWithMempool.map { case (history, mempool) =>
getAddress(addr)(history)
private def getBoxesByAddressUnspent(
addr: ErgoAddress,
offset: Int,
limit: Int,
sortDir: Direction,
unconfirmed: Boolean,
excludeMempoolSpent: Boolean
): Future[Seq[IndexedErgoBox]] = {

def fetchAndFilter(limit: Int, accumulated: Seq[IndexedErgoBox] = Seq.empty): Future[Seq[IndexedErgoBox]] = {
getHistoryWithMempool.flatMap { case (history, mempool) =>
val addressUtxos = getAddress(addr)(history)
.getOrElse(IndexedErgoAddress(hashErgoTree(addr.script)))
.retrieveUtxos(history, mempool, offset, limit, sortDir, unconfirmed)
.retrieveUtxos(history, mempool, offset + accumulated.length, limit, sortDir, unconfirmed)

val spentBoxesIdsInMempool: Set[Array[Byte]] = mempool.spentInputs.map(idToBytes).toSet
val newUtxos = if (excludeMempoolSpent) {
addressUtxos.filterNot(box => spentBoxesIdsInMempool.contains(idToBytes(box.id)))
} else {
addressUtxos
}

val updatedAccumulated = accumulated ++ newUtxos
// If reached limit OR we have obtained the maximumm available UTXOS (returned amount < limit), return successful.
if (updatedAccumulated.length >= originalLimit || addressUtxos.length < limit) {
Future.successful(updatedAccumulated.take(originalLimit)) // Just to ensure that no more than limit is taken
} else {
val maxLimit = 200;
val newLimit = Math.min(limit * 2, maxLimit); // Prevents limit becoming too large
fetchAndFilter(newLimit, updatedAccumulated)
}
}
}
val originalLimit = limit
fetchAndFilter(originalLimit)
}

private def validateAndGetBoxesByAddressUnspent(address: ErgoAddress,
offset: Int,
limit: Int,
dir: Direction,
unconfirmed: Boolean): Route = {
unconfirmed: Boolean,
excludeMempoolSpent: Boolean): Route = {
if (limit > MaxItems) {
BadRequest(s"No more than $MaxItems boxes can be requested")
} else if (dir == SortDirection.INVALID) {
BadRequest("Invalid parameter for sort direction, valid values are \"ASC\" and \"DESC\"")
} else {
ApiResponse(getBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed))
ApiResponse(getBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed, excludeMempoolSpent))
}
}

private def getBoxesByAddressUnspentR: Route =
(post & pathPrefix("box" / "unspent" / "byAddress") & ergoAddress & paging & sortDir & unconfirmed) {
(address, offset, limit, dir, unconfirmed) =>
validateAndGetBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed)
(post & pathPrefix("box" / "unspent" / "byAddress") & ergoAddress & paging & sortDir & unconfirmed & parameter('excludeMempoolSpent.as[Boolean].?)) {
(address, offset, limit, dir, unconfirmed, excludeMempoolSpentOption) =>
val excludeMempoolSpent = excludeMempoolSpentOption.getOrElse(false)
validateAndGetBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed, excludeMempoolSpent)
}

private def getBoxesByAddressUnspentGetRoute: Route =
(pathPrefix("box" / "unspent" / "byAddress") & get & addressPass & paging & sortDir & unconfirmed) {
(address, offset, limit, dir, unconfirmed) =>
validateAndGetBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed)
(pathPrefix("box" / "unspent" / "byAddress") & get & addressPass & paging & sortDir & unconfirmed & parameter('excludeMempoolSpent.as[Boolean].?)) {
(address, offset, limit, dir, unconfirmed, excludeMempoolSpentOption) =>
val excludeMempoolSpent = excludeMempoolSpentOption.getOrElse(false)
validateAndGetBoxesByAddressUnspent(address, offset, limit, dir, unconfirmed, excludeMempoolSpent)
}


private def getBoxRange(offset: Int, limit: Int): Future[Seq[ModifierId]] =
getHistory.map { history =>
val base: Long = getIndex(GlobalBoxIndexKey, history).getLong - offset
Expand Down
Loading