Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -403,15 +403,15 @@ class ErgoWalletServiceImpl(override val ergoSettings: ErgoSettings) extends Erg
val confirmed = 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)
(confirmed ++ state.offChainRegistry.walletOffChainBoxes).filter(state.walletFilter)
} else {
confirmed
}
} else {
val confirmed = state.registry.walletConfirmedBoxes()
if (considerUnconfirmed) {
// Just adding boxes created off-chain
confirmed ++ state.offChainRegistry.offChainBoxes
confirmed ++ state.offChainRegistry.walletOffChainBoxes
} else {
confirmed
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ case class ErgoWalletState(
*/
def getBoxesToSpend: Seq[TrackedBox] = {
require(walletVars.publicKeyAddresses.nonEmpty, "No public keys in the prover to extract change address from")
(registry.walletUnspentBoxes(maxInputsToUse * BoxSelector.ScanDepthFactor) ++ offChainRegistry.offChainBoxes).distinct
(registry.walletUnspentBoxes(maxInputsToUse * BoxSelector.ScanDepthFactor) ++ offChainRegistry.walletOffChainBoxes).distinct
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ case class OffChainRegistry(height: Int,
WalletDigest(height, balance, tokensBalance.toSeq)
}

/**
* Off-chain boxes belonging to the wallet itself (i.e. tracked by the payments scan),
* excluding boxes tracked by external application scans only, which must not be
* spent by wallet transactions (see #1905).
*/
def walletOffChainBoxes: Seq[TrackedBox] = offChainBoxes.filter(_.scans.contains(PaymentsScanId))

/**
* Update on receiving new off-chain transaction.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -433,4 +433,36 @@ class ErgoWalletServiceSpec
}
}


property("wallet-related APIs should not use boxes belonging to external scans only") {
withVersionedStore(2) { versionedStore =>
withStore { store =>
val customScanId = ScanId @@ 42.shortValue()
val emptyTx = ErgoLikeTransaction(IndexedSeq(), IndexedSeq())
val walletBox =
TrackedBox(emptyTx, creationOutIndex = 0, None, testBox(1000000000L, TrueTree, 0), Set(PaymentsScanId))
val customScanBox =
TrackedBox(emptyTx, creationOutIndex = 1, None, testBox(2000000000L, TrueTree, 0, boxIndex = 1), Set(customScanId))
val sharedBox =
TrackedBox(emptyTx, creationOutIndex = 2, None, testBox(3000000000L, TrueTree, 0, boxIndex = 2), Set(PaymentsScanId, customScanId))

val offChainRegistry =
OffChainRegistry.empty.copy(offChainBoxes = Seq(walletBox, customScanBox, sharedBox))
val walletState = initialState(store, versionedStore).copy(offChainRegistry = offChainRegistry)
val walletService = new ErgoWalletServiceImpl(settings)

// boxes the wallet can spend must not include boxes tracked by external scans only
walletState.getBoxesToSpend.map(_.boxId) should contain theSameElementsAs Seq(walletBox.boxId, sharedBox.boxId)

// /wallet/boxes/unspent with considerUnconfirmed must not return them either
val unspent = walletService.getWalletBoxes(walletState, unspentOnly = true, considerUnconfirmed = true)
unspent.map(_.trackedBox.boxId) should contain theSameElementsAs Seq(walletBox.boxId, sharedBox.boxId)

// while scan-related API still sees the box of the external scan
val scanBoxes = walletService.getScanUnspentBoxes(walletState, customScanId, considerUnconfirmed = true, 0, Int.MaxValue)
scanBoxes.map(_.trackedBox.boxId) should contain theSameElementsAs Seq(customScanBox.boxId, sharedBox.boxId)
}
}
}

}
Loading