Refactor codebase to have has-a relationship with RescanHandling rather than is-a (#5675)

* Refactor codebase to have has-a relationship with RescanHandling rather than is-a

get everything compiling

Get all tests passing

* Revert and clean up files

* Fix docs
This commit is contained in:
Chris Stewart 2024-09-19 09:46:56 -05:00 committed by GitHub
parent a8e9dcd443
commit 8c5d685953
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 367 additions and 296 deletions

View File

@ -1,28 +1,33 @@
package org.bitcoins.server
import org.apache.pekko.http.scaladsl.model.ContentTypes._
import org.apache.pekko.http.scaladsl.model.ContentTypes.*
import org.apache.pekko.http.scaladsl.model.StatusCodes
import org.apache.pekko.http.scaladsl.testkit.{
RouteTestTimeout,
ScalatestRouteTest
}
import org.bitcoins.core.api.chain.ChainApi
import org.bitcoins.core.api.chain.db._
import org.bitcoins.core.api.wallet.db._
import org.bitcoins.core.api.wallet.{AddressInfo, CoinSelectionAlgo}
import org.bitcoins.core.api.chain.db.*
import org.bitcoins.core.api.wallet.db.*
import org.bitcoins.core.api.wallet.{
AccountHandlingApi,
AddressInfo,
CoinSelectionAlgo,
RescanHandlingApi
}
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto.ExtPublicKey
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit, Satoshis}
import org.bitcoins.core.dlc.accounting.DLCWalletAccounting
import org.bitcoins.core.hd._
import org.bitcoins.core.hd.*
import org.bitcoins.core.number.{UInt32, UInt64}
import org.bitcoins.core.protocol.BlockStamp.{BlockHash, BlockHeight, BlockTime}
import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
import org.bitcoins.core.protocol.dlc.models._
import org.bitcoins.core.protocol.dlc.models.DLCMessage.*
import org.bitcoins.core.protocol.dlc.models.*
import org.bitcoins.core.protocol.script.P2WPKHWitnessV0
import org.bitcoins.core.protocol.tlv._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.protocol.tlv.*
import org.bitcoins.core.protocol.transaction.*
import org.bitcoins.core.protocol.{
Bech32Address,
BitcoinAddress,
@ -35,8 +40,8 @@ import org.bitcoins.core.util.FutureUtil
import org.bitcoins.core.util.sorted.OrderedSchnorrSignatures
import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.rescan.RescanState
import org.bitcoins.core.wallet.utxo._
import org.bitcoins.crypto._
import org.bitcoins.core.wallet.utxo.*
import org.bitcoins.crypto.*
import org.bitcoins.feeprovider.ConstantFeeRateProvider
import org.bitcoins.node.Node
import org.bitcoins.server.routes.{CommonRoutes, ServerCommand}
@ -47,7 +52,7 @@ import org.bitcoins.wallet.{MockWalletApi, WalletHolder}
import org.scalamock.scalatest.MockFactory
import org.scalatest.wordspec.AnyWordSpec
import scodec.bits.ByteVector
import ujson._
import ujson.*
import java.net.InetSocketAddress
import java.time.{ZoneId, ZonedDateTime}
@ -77,6 +82,10 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
val mockWalletApi: MockWalletApi = mock[MockWalletApi]
val mockRescanHandlingApi: RescanHandlingApi = mock[RescanHandlingApi]
val mockAccountHandlingApi: AccountHandlingApi = mock[AccountHandlingApi]
val walletHolder = new WalletHolder(Some(mockWalletApi))
val feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one)
@ -652,9 +661,10 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
(() => mockWalletApi.accountHandling)
.expects()
.returning(mockWalletApi)
.returning(mockAccountHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.listAccounts())
(() => mockWalletApi.accountHandling.listAccounts())
.expects()
.returning(Future.successful(Vector(accountDb)))
@ -2021,19 +2031,25 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
"run wallet rescan" in {
// positive cases
(() => mockWalletApi.discoveryBatchSize())
(() => mockWalletApi.rescanHandling)
.expects()
.returning(mockRescanHandlingApi)
.anyNumberOfTimes()
(() => mockWalletApi.rescanHandling.discoveryBatchSize())
.expects()
.returning(100)
.atLeastOnce()
(mockWalletApi
(mockWalletApi.rescanHandling
.rescanNeutrinoWallet(
_: Option[BlockStamp],
_: Option[BlockStamp],
_: Int,
_: Boolean,
_: Boolean
)(_: ExecutionContext))
.expects(None, None, 100, false, false, executor)
))
.expects(None, None, 100, false, false)
.returning(
Future.successful(
RescanState
@ -2058,14 +2074,14 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
)
}
(mockWalletApi
(mockWalletApi.rescanHandling
.rescanNeutrinoWallet(
_: Option[BlockStamp],
_: Option[BlockStamp],
_: Int,
_: Boolean,
_: Boolean
)(_: ExecutionContext))
))
.expects(
Some(
BlockTime(
@ -2075,8 +2091,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
None,
100,
false,
false,
executor
false
)
.returning(
Future.successful(
@ -2106,21 +2121,20 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
}
(mockWalletApi
(mockWalletApi.rescanHandling
.rescanNeutrinoWallet(
_: Option[BlockStamp],
_: Option[BlockStamp],
_: Int,
_: Boolean,
_: Boolean
)(_: ExecutionContext))
))
.expects(
None,
Some(BlockHash(DoubleSha256DigestBE.empty)),
100,
false,
false,
executor
false
)
.returning(
Future.successful(
@ -2149,21 +2163,20 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
)
}
(mockWalletApi
(mockWalletApi.rescanHandling
.rescanNeutrinoWallet(
_: Option[BlockStamp],
_: Option[BlockStamp],
_: Int,
_: Boolean,
_: Boolean
)(_: ExecutionContext))
))
.expects(
Some(BlockHeight(12345)),
Some(BlockHeight(67890)),
100,
false,
false,
executor
false
)
.returning(Future.successful(RescanState.RescanDone))
@ -2234,15 +2247,15 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
)
}
(mockWalletApi
(mockWalletApi.rescanHandling
.rescanNeutrinoWallet(
_: Option[BlockStamp],
_: Option[BlockStamp],
_: Int,
_: Boolean,
_: Boolean
)(_: ExecutionContext))
.expects(None, None, 55, false, false, executor)
))
.expects(None, None, 55, false, false)
.returning(Future.successful(RescanState.RescanDone))
walletLoader.clearRescanState()

View File

@ -7,6 +7,7 @@ import org.bitcoins.core.api.commons.ArgumentSource
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
import org.bitcoins.core.api.feeprovider.FeeRateApi
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.RescanHandlingApi
import org.bitcoins.core.util.StartStopAsync
import org.bitcoins.core.wallet.rescan.RescanState
import org.bitcoins.crypto.AesPassword
@ -114,7 +115,7 @@ sealed trait DLCWalletLoaderApi
}
protected def restartRescanIfNeeded(
wallet: DLCNeutrinoHDWalletApi
wallet: RescanHandlingApi
)(implicit ec: ExecutionContext): Future[RescanState] = {
for {
isRescanning <- wallet.isRescanning()
@ -311,7 +312,7 @@ case class DLCWalletNeutrinoBackendLoader(
CallbackUtil.createNeutrinoNodeCallbacksForWallet(walletHolder)
_ = nodeConf.replaceCallbacks(nodeCallbacks)
_ <- updateWalletName(walletNameOpt)
rescanState <- restartRescanIfNeeded(walletHolder)
rescanState <- restartRescanIfNeeded(walletHolder.rescanHandling)
_ = setRescanState(rescanState)
} yield {
logger.info(s"Done loading wallet=$walletNameOpt")
@ -363,7 +364,7 @@ case class DLCWalletBitcoindBackendLoader(
_ = nodeConf.replaceCallbacks(nodeCallbacks)
_ <- walletHolder.replaceWallet(dlcWallet)
// do something with possible rescan?
rescanState <- restartRescanIfNeeded(walletHolder)
rescanState <- restartRescanIfNeeded(walletHolder.rescanHandling)
_ = setRescanState(rescanState)
} yield {
logger.info(s"Done loading wallet=$walletNameOpt")

View File

@ -1186,12 +1186,13 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
/** Only call this if we know we are in a state */
private def startRescan(rescan: Rescan): Future[RescanState] = {
val stateF = wallet
val rescanHandling = wallet.rescanHandling
val stateF = rescanHandling
.rescanNeutrinoWallet(
startOpt = rescan.startBlock,
endOpt = rescan.endBlock,
addressBatchSize =
rescan.batchSize.getOrElse(wallet.discoveryBatchSize()),
rescan.batchSize.getOrElse(rescanHandling.discoveryBatchSize()),
useCreationTime = !rescan.ignoreCreationTime,
force = false
)

View File

@ -1,7 +1,8 @@
package org.bitcoins.core.api.wallet
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.hd.AddressType
import org.bitcoins.core.hd.{AddressType, HDAccount}
import org.bitcoins.core.protocol.script.ScriptPubKey
import scala.concurrent.Future
@ -9,4 +10,10 @@ trait AccountHandlingApi {
def getDefaultAccount(): Future[AccountDb]
def listAccounts(): Future[Vector[AccountDb]]
def getDefaultAccountForType(addressType: AddressType): Future[AccountDb]
def clearUtxos(account: HDAccount): Future[Unit]
def generateScriptPubKeys(
account: HDAccount,
addressBatchSize: Int,
forceGenerateSpks: Boolean
): Future[Vector[ScriptPubKey]]
}

View File

@ -27,11 +27,12 @@ import scala.concurrent.{ExecutionContext, Future}
* @see
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]]
*/
trait HDWalletApi extends WalletApi with AccountHandlingApi {
trait HDWalletApi extends WalletApi {
override def keyManager: BIP39KeyManagerApi
def accountHandling: AccountHandlingApi
def fundTxHandling: FundTransactionHandlingApi
def rescanHandling: RescanHandlingApi
/** Gets the balance of the given account */
def getBalance(account: HDAccount)(implicit
@ -457,8 +458,6 @@ trait HDWalletApi extends WalletApi with AccountHandlingApi {
override def clearAllUtxos(): Future[HDWalletApi]
def clearUtxos(account: HDAccount): Future[HDWalletApi]
/** Gets the address associated with the pubkey at the resulting `BIP32Path`
* determined by the default account and the given chainType and addressIndex
*/

View File

@ -1,11 +1,9 @@
package org.bitcoins.core.api.wallet
import org.bitcoins.core.gcs.GolombFilter
import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.wallet.rescan.RescanState
import org.bitcoins.crypto.DoubleSha256DigestBE
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.Future
trait NeutrinoWalletApi { self: WalletApi =>
@ -18,56 +16,6 @@ trait NeutrinoWalletApi { self: WalletApi =>
blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)])
: Future[NeutrinoHDWalletApi]
/** Recreates the account using BIP-157 approach
*
* DANGER! This method removes all records from the wallet database and
* creates new ones while the account discovery process.
*
* The Wallet UI should check if the database is empty before calling this
* method and let the end users to decide whether they want to proceed or
* not.
*
* This method generates [[addressBatchSize]] of addresses, then matches them
* against the BIP-158 compact filters, and downloads and processes the
* matched blocks. This method keeps doing the steps until there are
* [[WalletConfig.addressGapLimit]] or more unused addresses in a row. In
* this case it considers the discovery process completed.
*
* [[addressBatchSize]] - the number of addresses we should generate from a
* keychain to attempt to match in in a rescan
* [[WalletConfig.addressGapLimit]] - the number of addresses required to go
* without a match before we determine that our wallet is "discovered". For
* instance, if addressBatchSize=100, and AddressGapLimit=20 we do a rescan
* and the last address we find containing funds is at index 75, we would not
* generate more addresses to try and rescan. However if the last index
* containing funds was 81, we would generate another 100 addresses from the
* keychain and attempt to rescan those.
*
* @param startOpt
* start block (if None it starts from the genesis block)
* @param endOpt
* end block (if None it ends at the current tip)
* @param addressBatchSize
* how many addresses to match in a single pass
*/
def rescanNeutrinoWallet(
startOpt: Option[BlockStamp],
endOpt: Option[BlockStamp],
addressBatchSize: Int,
useCreationTime: Boolean,
force: Boolean)(implicit ec: ExecutionContext): Future[RescanState]
/** Helper method to rescan the ENTIRE blockchain. */
def fullRescanNeutrinoWallet(addressBatchSize: Int, force: Boolean = false)(
implicit ec: ExecutionContext): Future[RescanState] =
rescanNeutrinoWallet(startOpt = None,
endOpt = None,
addressBatchSize = addressBatchSize,
useCreationTime = false,
force = force)
def discoveryBatchSize(): Int
}
object NeutrinoWalletApi {

View File

@ -1,7 +1,70 @@
package org.bitcoins.core.api.wallet
import org.bitcoins.core.api.chain.ChainQueryApi.FilterResponse
import org.bitcoins.core.api.wallet.NeutrinoWalletApi.BlockMatchingResponse
import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.wallet.rescan.RescanState
import scala.concurrent.Future
trait RescanHandlingApi {
def isRescanning(): Future[Boolean]
/** Recreates the account using BIP-157 approach
*
* DANGER! This method removes all records from the wallet database and
* creates new ones while the account discovery process.
*
* The Wallet UI should check if the database is empty before calling this
* method and let the end users to decide whether they want to proceed or
* not.
*
* This method generates [[addressBatchSize]] of addresses, then matches them
* against the BIP-158 compact filters, and downloads and processes the
* matched blocks. This method keeps doing the steps until there are
* [[WalletConfig.addressGapLimit]] or more unused addresses in a row. In
* this case it considers the discovery process completed.
*
* [[addressBatchSize]] - the number of addresses we should generate from a
* keychain to attempt to match in in a rescan
* [[WalletConfig.addressGapLimit]] - the number of addresses required to go
* without a match before we determine that our wallet is "discovered". For
* instance, if addressBatchSize=100, and AddressGapLimit=20 we do a rescan
* and the last address we find containing funds is at index 75, we would not
* generate more addresses to try and rescan. However if the last index
* containing funds was 81, we would generate another 100 addresses from the
* keychain and attempt to rescan those.
*
* @param startOpt
* start block (if None it starts from the genesis block)
* @param endOpt
* end block (if None it ends at the current tip)
* @param addressBatchSize
* how many addresses to match in a single pass
*/
def rescanNeutrinoWallet(
startOpt: Option[BlockStamp],
endOpt: Option[BlockStamp],
addressBatchSize: Int,
useCreationTime: Boolean,
force: Boolean): Future[RescanState]
/** Helper method to rescan the ENTIRE blockchain. */
def fullRescanNeutrinoWallet(
addressBatchSize: Int,
force: Boolean = false): Future[RescanState] =
rescanNeutrinoWallet(startOpt = None,
endOpt = None,
addressBatchSize = addressBatchSize,
useCreationTime = false,
force = force)
def findMatches(
filters: Vector[FilterResponse],
scripts: Vector[ScriptPubKey],
parallelismLevel: Int
): Future[Vector[BlockMatchingResponse]]
def discoveryBatchSize(): Int
}

View File

@ -55,7 +55,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
Vector(hash) <- bitcoind.generate(1)
_ <- wallet.rescanNeutrinoWallet(
_ <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = Some(BlockHash(hash)),
addressBatchSize = 20,
@ -100,7 +100,7 @@ class RescanDLCTest extends DualWalletTestCachedBitcoind {
Vector(hash) <- bitcoind.generate(1)
_ <- wallet.rescanNeutrinoWallet(
_ <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = Some(BlockHash(hash)),
addressBatchSize = 20,

View File

@ -349,6 +349,7 @@ object DLCAppConfig
Wallet
.initialize(
wallet = unInitializedWallet,
accountHandling = unInitializedWallet.accountHandling,
bip39PasswordOpt = bip39PasswordOpt
)
.map(_.asInstanceOf[DLCWallet])

View File

@ -113,7 +113,8 @@ abstract class DLCWallet
DLCActionBuilder(dlcWalletDAOs)
}
override lazy val transactionProcessing: DLCTransactionProcessing = {
override protected lazy val transactionProcessing
: DLCTransactionProcessing = {
val txProcessing = TransactionProcessing(
walletApi = this,
chainQueryApi = chainQueryApi,
@ -123,10 +124,10 @@ abstract class DLCWallet
DLCTransactionProcessing(
txProcessing = txProcessing,
dlcWalletDAOs = dlcWalletDAOs,
walletDAOs = walletDAOs,
dlcDataManagement = dlcDataManagement,
keyManager = keyManager,
transactionDAO = transactionDAO,
rescanHandling = this,
utxoHandling = utxoHandling,
dlcWalletApi = this
)

View File

@ -5,7 +5,6 @@ import org.bitcoins.core.api.dlc.wallet.DLCWalletApi
import org.bitcoins.core.api.dlc.wallet.db.*
import org.bitcoins.core.api.wallet.{
ProcessTxResult,
RescanHandlingApi,
TransactionProcessingApi,
UtxoHandlingApi
}
@ -39,7 +38,7 @@ import org.bitcoins.db.SafeDatabase
import org.bitcoins.dlc.wallet.DLCAppConfig
import org.bitcoins.dlc.wallet.models.*
import org.bitcoins.keymanager.bip39.BIP39KeyManager
import org.bitcoins.wallet.models.TransactionDAO
import org.bitcoins.wallet.models.{TransactionDAO, WalletDAOs}
import scala.concurrent.*
@ -49,10 +48,10 @@ import scala.concurrent.*
case class DLCTransactionProcessing(
txProcessing: TransactionProcessingApi,
dlcWalletDAOs: DLCWalletDAOs,
walletDAOs: WalletDAOs,
dlcDataManagement: DLCDataManagement,
keyManager: BIP39KeyManager,
transactionDAO: TransactionDAO,
rescanHandling: RescanHandlingApi,
utxoHandling: UtxoHandlingApi,
dlcWalletApi: DLCWalletApi)(implicit
dlcConfig: DLCAppConfig,
@ -318,7 +317,7 @@ case class DLCTransactionProcessing(
_ <- dlcDAO.updateAll(updated)
dlcIds = updated.map(_.dlcId).distinct
isRescanning <- rescanHandling.isRescanning()
isRescanning <- walletDAOs.stateDescriptorDAO.isRescanning
_ <- sendWsDLCStateChange(dlcIds, isRescanning)
} yield {
updated

View File

@ -107,7 +107,7 @@ val addrBatchSize = 100
val rescannedBalanceF = for {
_ <- clearedWalletF
w <- walletF
_ <- w.fullRescanNeutrinoWallet(addrBatchSize)
_ <- w.rescanHandling.fullRescanNeutrinoWallet(addrBatchSize)
balanceAfterRescan <- w.getBalance()
} yield {
println(s"Wallet balance after rescan: ${balanceAfterRescan}")

View File

@ -152,7 +152,7 @@ val wallet = Wallet(new NodeApi {
override def getConnectionCount: Future[Int] = Future.successful(0)
}, chainApi, ConstantFeeRateProvider(SatoshisPerVirtualByte.one))
val walletF: Future[WalletApi] = configF.flatMap { _ =>
Wallet.initialize(wallet, None)
Wallet.initialize(wallet, wallet.accountHandling, None)
}
// when this future completes, ww have sent a transaction

View File

@ -214,7 +214,8 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
rescan <- wallet.isRescanning()
_ = assert(!rescan)
rescanState <- wallet.fullRescanNeutrinoWallet(addressBatchSize = 7)
rescanState <- wallet.rescanHandling
.fullRescanNeutrinoWallet(addressBatchSize = 7)
_ <- AsyncUtil.awaitConditionF(
() => condition(),

View File

@ -299,7 +299,9 @@ object BitcoinSWalletTest extends WalletLogger {
walletConfig.start().flatMap { _ =>
val wallet =
Wallet(nodeApi, chainQueryApi, new RandomFeeProvider)(walletConfig)
Wallet.initialize(wallet, walletConfig.bip39PasswordOpt)
Wallet.initialize(wallet,
wallet.accountHandling,
walletConfig.bip39PasswordOpt)
}
}
}
@ -327,7 +329,9 @@ object BitcoinSWalletTest extends WalletLogger {
)
Wallet
.initialize(wallet, config.walletConf.bip39PasswordOpt)
.initialize(wallet,
wallet.accountHandling,
config.walletConf.bip39PasswordOpt)
.map(_.asInstanceOf[DLCWallet])
}
}

View File

@ -32,7 +32,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
addresses <- wallet.listAddresses(account)
_ = assert(addresses.nonEmpty)
_ <- wallet.clearUtxos(account)
_ <- wallet.accountHandling.clearUtxos(account)
clearedUtxos <- wallet.listUtxos(account)
clearedAddresses <- wallet.listAddresses(account)
@ -79,7 +79,8 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
initBalance > CurrencyUnits.zero,
s"Cannot run rescan test if our init wallet balance is zero!"
)
rescanState <- wallet.fullRescanNeutrinoWallet(DEFAULT_ADDR_BATCH_SIZE)
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(
DEFAULT_ADDR_BATCH_SIZE)
_ = assert(rescanState.isInstanceOf[RescanState.RescanStarted])
_ <- RescanState.awaitRescanDone(rescanState)
balanceAfterRescan <- wallet.getBalance()
@ -133,7 +134,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
_ <- wallet.clearAllUtxos()
zeroBalance <- wallet.getBalance()
_ = assert(zeroBalance == Satoshis.zero)
rescanState <- wallet.rescanNeutrinoWallet(
rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = txInBlockHeightOpt,
endOpt = None,
addressBatchSize = DEFAULT_ADDR_BATCH_SIZE,
@ -200,8 +201,9 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
_ <- wallet.clearAllUtxos()
_ <- wallet.clearAllAddresses()
balanceAfterClear <- wallet.getBalance()
rescanState <- wallet.fullRescanNeutrinoWallet(addressBatchSize = 1,
force = true)
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(
addressBatchSize = 1,
force = true)
_ <- RescanState.awaitRescanDone(rescanState)
_ <- AsyncUtil.awaitConditionF(
@ -249,7 +251,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
for {
_ <- newTxWalletF
rescanState <- wallet.rescanNeutrinoWallet(
rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = DEFAULT_ADDR_BATCH_SIZE,
@ -293,7 +295,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
)
oldestUtxoHeight <- oldestHeightF
end = Some(BlockStamp.BlockHeight(oldestUtxoHeight - 1))
rescanState <- wallet.rescanNeutrinoWallet(
rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = BlockStamp.height0Opt,
endOpt = end,
addressBatchSize = DEFAULT_ADDR_BATCH_SIZE,
@ -313,7 +315,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
(fixture: WalletWithBitcoindRpc) =>
val wallet = fixture.wallet
// do these in parallel on purpose to simulate multiple threads calling rescan
val startF = wallet.rescanNeutrinoWallet(
val startF = wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = DEFAULT_ADDR_BATCH_SIZE,
@ -324,7 +326,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
// slight delay to make sure other rescan is started
val alreadyStartedF =
AsyncUtil.nonBlockingSleep(10.millis).flatMap { _ =>
wallet.rescanNeutrinoWallet(
wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = DEFAULT_ADDR_BATCH_SIZE,
@ -352,7 +354,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
// start a rescan without sending payment to that address
for {
address <- addressNoFundsF
_ <- wallet.rescanNeutrinoWallet(
_ <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = 10,
@ -392,7 +394,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
val bitcoind = fixture.bitcoind
val amt = Bitcoins.one
for {
_ <- wallet.rescanNeutrinoWallet(
_ <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = 10,
@ -401,7 +403,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
)
addressNoFunds <- wallet.getNewChangeAddress()
// rescan again
_ <- wallet.rescanNeutrinoWallet(
_ <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = 10,
@ -428,7 +430,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
"Cannot be rescanning before we started the test"
)
// start the rescan
state <- wallet.rescanNeutrinoWallet(
state <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = 10,
@ -461,7 +463,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
"Cannot be rescanning before we started the test"
)
// start the rescan
state <- wallet.rescanNeutrinoWallet(
state <- wallet.rescanHandling.rescanNeutrinoWallet(
startOpt = None,
endOpt = None,
addressBatchSize = 10,
@ -551,7 +553,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
_ <- wallet.clearAllUtxos()
_ <- wallet.clearAllAddresses()
balanceAfterClear <- wallet.getBalance()
rescanState <- wallet.fullRescanNeutrinoWallet(1, true)
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(1, true)
_ <- RescanState.awaitRescanDone(rescanState)
_ <- AsyncUtil.awaitConditionF(
() => wallet.getBalance().map(_ == balanceAfterPayment1),
@ -574,7 +576,8 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
// a fresh pool of addresses
_ <- wallet.clearAllUtxos()
_ <- wallet.clearAllAddresses()
rescanState <- wallet.fullRescanNeutrinoWallet(DEFAULT_ADDR_BATCH_SIZE)
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(
DEFAULT_ADDR_BATCH_SIZE)
_ = assert(rescanState.isInstanceOf[RescanState.RescanStarted])
_ <- RescanState.awaitRescanDone(rescanState)
addresses <- wallet.listAddresses()

View File

@ -158,6 +158,7 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
)(config)
init <- Wallet.initialize(
wallet = wallet,
accountHandling = wallet.accountHandling,
bip39PasswordOpt = bip39PasswordOpt
)
} yield init

View File

@ -138,7 +138,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
startHeight = 0,
endHeight = height
)
matched <- wallet.findMatches(
matched <- wallet.rescanHandling.findMatches(
filters = filtersResponse,
scripts = Vector(
// this is a random address which is included into the test block
@ -162,9 +162,13 @@ class WalletUnitTest extends BitcoinSWalletTest {
(wallet: Wallet) =>
val bip39PasswordOpt = wallet.walletConfig.bip39PasswordOpt
val twiceF = Wallet
.initialize(wallet, bip39PasswordOpt)
.initialize(wallet = wallet,
accountHandling = wallet.accountHandling,
bip39PasswordOpt = bip39PasswordOpt)
.flatMap { _ =>
Wallet.initialize(wallet, bip39PasswordOpt)
Wallet.initialize(wallet = wallet,
accountHandling = wallet.accountHandling,
bip39PasswordOpt = bip39PasswordOpt)
}
twiceF.map(_ => succeed)
@ -176,11 +180,12 @@ class WalletUnitTest extends BitcoinSWalletTest {
val bip39PasswordOpt = wallet.walletConfig.bip39PasswordOpt
recoverToSucceededIf[RuntimeException] {
Wallet
.initialize(wallet, bip39PasswordOpt)
.initialize(wallet, wallet.accountHandling, bip39PasswordOpt)
.flatMap { _ =>
// use a BIP39 password to make the key-managers different
Wallet.initialize(
wallet,
wallet.accountHandling,
Some("random-password-to-make-key-managers-different")
)
}
@ -206,7 +211,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
recoverToSucceededIf[IllegalArgumentException] {
walletDiffKeyManagerF.flatMap { walletDiffKeyManager =>
Wallet.initialize(walletDiffKeyManager, None)
Wallet.initialize(walletDiffKeyManager, wallet.accountHandling, None)
}
}
}
@ -373,6 +378,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
// initialize it
initOldWallet <- Wallet.initialize(
wallet = wallet,
accountHandling = wallet.accountHandling,
bip39PasswordOpt = wallet.walletConfig.bip39PasswordOpt
)
isOldWalletEmpty <- initOldWallet.isEmpty()

View File

@ -49,7 +49,6 @@ import scala.util.{Failure, Random, Success}
abstract class Wallet
extends NeutrinoHDWalletApi
with AddressHandling
with RescanHandling
with WalletLogger {
override def keyManager: BIP39KeyManager = {
@ -67,8 +66,6 @@ abstract class Wallet
val networkParameters: BitcoinNetwork = walletConfig.network
override val discoveryBatchSize: Int = walletConfig.discoveryBatchSize
private[bitcoins] val addressDAO: AddressDAO = AddressDAO()
private[bitcoins] val accountDAO: AccountDAO = AccountDAO()
private[bitcoins] val spendingInfoDAO: SpendingInfoDAO = SpendingInfoDAO()
@ -105,16 +102,17 @@ abstract class Wallet
UtxoHandling(spendingInfoDAO, transactionDAO, chainQueryApi)
def fundTxHandling: FundTransactionHandling = FundTransactionHandling(
accountHandling = this,
accountHandling = accountHandling,
utxoHandling = utxoHandling,
addressHandling = this,
spendingInfoDAO = spendingInfoDAO,
transactionDAO = transactionDAO,
keyManager = keyManager
)
def accountHandling: AccountHandlingApi = AccountHandling(accountDAO)
def accountHandling: AccountHandlingApi =
AccountHandling(this, walletDAOs)
override lazy val transactionProcessing: TransactionProcessingApi = {
protected lazy val transactionProcessing: TransactionProcessingApi = {
TransactionProcessing(
walletApi = this,
chainQueryApi = chainQueryApi,
@ -122,6 +120,18 @@ abstract class Wallet
walletDAOs = walletDAOs
)
}
override lazy val rescanHandling: RescanHandlingApi = {
RescanHandling(
transactionProcessing = transactionProcessing,
accountHandling = accountHandling,
addressHandling = this,
chainQueryApi = chainQueryApi,
nodeApi = nodeApi,
walletDAOs = walletDAOs
)
}
override def isRescanning(): Future[Boolean] = rescanHandling.isRescanning()
def walletCallbacks: WalletCallbacks = walletConfig.callBacks
@ -303,18 +313,6 @@ abstract class Wallet
spendingInfoCount <- spendingInfoDAO.count()
} yield addressCount == 0 && spendingInfoCount == 0
override def clearUtxos(account: HDAccount): Future[Wallet] = {
val aggregatedActions
: DBIOAction[Wallet, NoStream, Effect.Read with Effect.Write] = {
for {
accountUtxos <- spendingInfoDAO.findAllForAccountAction(account)
_ <- spendingInfoDAO.deleteSpendingInfoDbAllAction(accountUtxos)
} yield this
}
safeDatabase.run(aggregatedActions)
}
override def clearAllUtxos(): Future[Wallet] = {
val aggregatedActions
: DBIOAction[Unit, NoStream, Effect.Write with Effect.Transactional] =
@ -1008,7 +1006,7 @@ abstract class Wallet
for {
accountDb <- getDefaultAccount()
walletState <- getSyncState()
rescan <- isRescanning()
rescan <- rescanHandling.isRescanning()
} yield {
WalletInfo(
walletName = walletConfig.walletName,
@ -1204,9 +1202,10 @@ object Wallet extends WalletLogger {
def initialize(
wallet: Wallet,
accountHandling: AccountHandlingApi,
bip39PasswordOpt: Option[String]
): Future[Wallet] = {
implicit val walletAppConfig = wallet.walletConfig
implicit val walletAppConfig: WalletAppConfig = wallet.walletConfig
import walletAppConfig.ec
val passwordOpt = walletAppConfig.aesPasswordOpt
@ -1260,7 +1259,7 @@ object Wallet extends WalletLogger {
val threshold = Instant.now().minus(1, ChronoUnit.HOURS)
val isOldCreationTime = creationTime.compareTo(threshold) <= 0
if (isOldCreationTime) {
wallet
accountHandling
.generateScriptPubKeys(
account = walletAppConfig.defaultAccount,
addressBatchSize = walletAppConfig.discoveryBatchSize,

View File

@ -28,14 +28,13 @@ import org.bitcoins.core.protocol.transaction.{
TransactionOutPoint,
TransactionOutput
}
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.psbt.PSBT
import org.bitcoins.core.wallet.builder.{
FundRawTxHelper,
ShufflingNonInteractiveFinalizer
}
import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte}
import org.bitcoins.core.wallet.rescan.RescanState
import org.bitcoins.core.wallet.utxo.{
AddressTag,
AddressTagName,
@ -69,6 +68,8 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
override def accountHandling: AccountHandlingApi = wallet.accountHandling
override def rescanHandling: RescanHandlingApi = wallet.rescanHandling
override def fundTxHandling: FundTransactionHandlingApi =
wallet.fundTxHandling
def isInitialized: Boolean = synchronized {
@ -141,24 +142,7 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
delegate(_.processCompactFilters(blockFilters))
}
override def rescanNeutrinoWallet(
startOpt: Option[BlockStamp],
endOpt: Option[BlockStamp],
addressBatchSize: Int,
useCreationTime: Boolean,
force: Boolean
)(implicit ec: ExecutionContext): Future[RescanState] =
delegate(
_.rescanNeutrinoWallet(
startOpt,
endOpt,
addressBatchSize,
useCreationTime,
force
)
)
override def discoveryBatchSize(): Int = wallet.discoveryBatchSize()
override def isRescanning(): Future[Boolean] = delegate(_.isRescanning())
override lazy val nodeApi: NodeApi = wallet.nodeApi
override lazy val chainQueryApi: ChainQueryApi = wallet.chainQueryApi
@ -371,8 +355,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
_.getSyncState()
)
override def isRescanning(): Future[Boolean] = delegate(_.isRescanning())
override def createDLCOffer(
contractInfo: ContractInfo,
collateral: Satoshis,
@ -649,10 +631,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
_.clearAllUtxos()
)
override def clearUtxos(account: HDAccount): Future[HDWalletApi] = delegate(
_.clearUtxos(account)
)
override def clearAllAddresses(): Future[WalletApi] = {
delegate(_.clearAllAddresses())
}
@ -782,12 +760,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
): Future[NeutrinoHDWalletApi] =
delegate(_.processCompactFilter(blockHash, blockFilter))
override def fullRescanNeutrinoWallet(addressBatchSize: Int, force: Boolean)(
implicit ec: ExecutionContext
): Future[RescanState] = delegate(
_.fullRescanNeutrinoWallet(addressBatchSize, force)
)
override def getNewChangeAddress()(implicit
ec: ExecutionContext
): Future[BitcoinAddress] =

View File

@ -426,6 +426,7 @@ object WalletAppConfig
Wallet.initialize(
wallet = unInitializedWallet,
accountHandling = unInitializedWallet.accountHandling,
bip39PasswordOpt = bip39PasswordOpt
)
}

View File

@ -1,9 +1,11 @@
package org.bitcoins.wallet.internal
import org.bitcoins.commons.util.BitcoinSLogger
import org.bitcoins.core.api.wallet.AccountHandlingApi
import org.bitcoins.core.api.wallet.db.AccountDb
import org.bitcoins.core.hd.AddressType.*
import org.bitcoins.core.hd.*
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.blockchain.{
ChainParams,
MainNetChainParams,
@ -11,19 +13,35 @@ import org.bitcoins.core.protocol.blockchain.{
SigNetChainParams,
TestNetChainParams
}
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.db.SafeDatabase
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.AccountDAO
import org.bitcoins.wallet.models.{
AccountDAO,
AddressDAO,
ScriptPubKeyDAO,
SpendingInfoDAO,
WalletDAOs
}
import slick.dbio.{DBIOAction, Effect, NoStream}
import scala.concurrent.{ExecutionContext, Future}
/** Provides functionality related enumerating accounts. Account creation does
* not happen here, as that requires an unlocked wallet.
*/
case class AccountHandling(accountDAO: AccountDAO)(implicit
case class AccountHandling(
addressHandling: AddressHandling,
walletDAOs: WalletDAOs)(implicit
walletConfig: WalletAppConfig,
ec: ExecutionContext)
extends AccountHandlingApi {
extends AccountHandlingApi
with BitcoinSLogger {
private val accountDAO: AccountDAO = walletDAOs.accountDAO
private val spendingInfoDAO: SpendingInfoDAO = walletDAOs.utxoDAO
private val addressDAO: AddressDAO = walletDAOs.addressDAO
private val scriptPubKeyDAO: ScriptPubKeyDAO = walletDAOs.scriptPubKeyDAO
private val safeDatabase: SafeDatabase = spendingInfoDAO.safeDatabase
private val chainParams: ChainParams = walletConfig.chain
/** @inheritdoc */
@ -71,6 +89,107 @@ case class AccountHandling(accountDAO: AccountDAO)(implicit
} yield getOrThrowAccount(account)
}
override def clearUtxos(account: HDAccount): Future[Unit] = {
val aggregatedActions
: DBIOAction[Unit, NoStream, Effect.Read with Effect.Write] = {
for {
accountUtxos <- spendingInfoDAO.findAllForAccountAction(account)
_ <- spendingInfoDAO.deleteSpendingInfoDbAllAction(accountUtxos)
} yield ()
}
safeDatabase.run(aggregatedActions)
}
override def generateScriptPubKeys(
account: HDAccount,
addressBatchSize: Int,
forceGenerateSpks: Boolean
): Future[Vector[ScriptPubKey]] = {
val action = generateScriptPubKeysAction(
account = account,
addressBatchSize = addressBatchSize,
forceGenerateSpks = forceGenerateSpks
)
safeDatabase.run(action)
}
/** If forceGeneratSpks is true or addressCount == 0 we generate a new pool of
* scriptpubkeys
*/
private def generateScriptPubKeysAction(
account: HDAccount,
addressBatchSize: Int,
forceGenerateSpks: Boolean
): DBIOAction[Vector[
ScriptPubKey
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
val addressCountA = addressDAO.countAction
for {
addressCount <- addressCountA
addresses <- {
if (forceGenerateSpks || addressCount == 0) {
logger.info(
s"Generating $addressBatchSize fresh addresses for the rescan"
)
generateAddressesForRescanAction(account, addressBatchSize)
} else {
// we don't want to continously generate addresses
// if our wallet already has them, so just use what is in the
// database already
addressDAO.findAllAddressesAction().map(_.map(_.address))
}
}
spksDb <- scriptPubKeyDAO.findAllAction()
} yield {
val addrSpks =
addresses.map(_.scriptPubKey)
val otherSpks = spksDb.map(_.scriptPubKey)
(addrSpks ++ otherSpks).distinct
}
}
private def generateAddressesForRescanAction(
account: HDAccount,
addressBatchSize: Int
): DBIOAction[Vector[
BitcoinAddress
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
val receiveAddressesA: DBIOAction[
Vector[
BitcoinAddress
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
DBIOAction.sequence {
1.to(addressBatchSize)
.map(_ => addressHandling.getNewAddressAction(account))
}
}.map(_.toVector)
val changeAddressesA: DBIOAction[
Vector[
BitcoinAddress
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
DBIOAction.sequence {
1.to(addressBatchSize)
.map(_ => addressHandling.getNewChangeAddressAction(account))
}
}.map(_.toVector)
for {
receiveAddresses <- receiveAddressesA
changeAddresses <- changeAddressesA
} yield receiveAddresses ++ changeAddresses
}
/** The default HD coin for this wallet, read from config */
protected[wallet] lazy val DEFAULT_HD_COIN: HDCoin = {
val coinType = DEFAULT_HD_COIN_TYPE

View File

@ -1,14 +1,17 @@
package org.bitcoins.wallet.internal
import org.apache.pekko.NotUsed
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.stream.scaladsl.{Flow, Keep, Merge, Sink, Source}
import org.bitcoins.core.api.chain.ChainQueryApi
import org.bitcoins.core.api.chain.ChainQueryApi.{
FilterResponse,
InvalidBlockRange
}
import org.bitcoins.core.api.node.NodeApi
import org.bitcoins.core.api.wallet.NeutrinoWalletApi.BlockMatchingResponse
import org.bitcoins.core.api.wallet.{
AccountHandlingApi,
RescanHandlingApi,
TransactionProcessingApi
}
@ -16,26 +19,44 @@ import org.bitcoins.core.gcs.SimpleFilterMatcher
import org.bitcoins.core.hd.{HDAccount, HDChainType}
import org.bitcoins.core.protocol.BlockStamp.BlockHeight
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.core.wallet.rescan.RescanState
import org.bitcoins.core.wallet.rescan.RescanState.RescanTerminatedEarly
import org.bitcoins.crypto.DoubleSha256DigestBE
import org.bitcoins.wallet.{Wallet, WalletLogger}
import slick.dbio.{DBIOAction, Effect, NoStream}
import org.bitcoins.wallet.callback.WalletCallbacks
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.wallet.models.{
AddressDAO,
SpendingInfoDAO,
WalletDAOs,
WalletStateDescriptorDAO
}
import org.bitcoins.wallet.WalletLogger
import java.time.Instant
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.{Failure, Success}
private[wallet] trait RescanHandling
case class RescanHandling(
transactionProcessing: TransactionProcessingApi,
accountHandling: AccountHandlingApi,
addressHandling: AddressHandling,
chainQueryApi: ChainQueryApi,
nodeApi: NodeApi,
walletDAOs: WalletDAOs)(implicit
walletConfig: WalletAppConfig,
system: ActorSystem)
extends RescanHandlingApi
with WalletLogger {
self: Wallet =>
/////////////////////
// Public facing API
def transactionProcessing: TransactionProcessingApi
private implicit val rescanEC: ExecutionContext =
ExecutionContext.fromExecutor(walletConfig.rescanThreadPool)
private def walletCallbacks: WalletCallbacks = walletConfig.callBacks
private val stateDescriptorDAO: WalletStateDescriptorDAO =
walletDAOs.stateDescriptorDAO
private val addressDAO: AddressDAO = walletDAOs.addressDAO
private val spendingInfoDAO: SpendingInfoDAO = walletDAOs.utxoDAO
private val creationTime: Instant = walletConfig.creationTime
override def isRescanning(): Future[Boolean] = stateDescriptorDAO.isRescanning
@ -46,9 +67,9 @@ private[wallet] trait RescanHandling
addressBatchSize: Int,
useCreationTime: Boolean,
force: Boolean
)(implicit ec: ExecutionContext): Future[RescanState] = {
): Future[RescanState] = {
for {
account <- getDefaultAccount()
account <- accountHandling.getDefaultAccount()
state <- rescanNeutrinoWallet(
account.hdAccount,
startOpt,
@ -98,7 +119,7 @@ private[wallet] trait RescanHandling
case (None, false) =>
Future.successful(None)
}
_ <- clearUtxos(account)
_ <- accountHandling.clearUtxos(account)
state <- doNeutrinoRescan(
account = account,
startOpt = start,
@ -150,6 +171,8 @@ private[wallet] trait RescanHandling
}
}
override def discoveryBatchSize(): Int = walletConfig.discoveryBatchSize
/** Register callbacks to reset rescan flag in the database if there is a
* rescan failure
*/
@ -197,7 +220,7 @@ private[wallet] trait RescanHandling
filterBatchSize: Int,
forceGenerateSpks: Boolean
): RescanState.RescanStarted = {
val scriptsF = generateScriptPubKeys(
val scriptsF = accountHandling.generateScriptPubKeys(
account = account,
addressBatchSize = addressBatchSize,
forceGenerateSpks = forceGenerateSpks
@ -236,9 +259,7 @@ private[wallet] trait RescanHandling
val heightRange = filterResponse.map(_.blockHeight)
val f =
scriptsF.flatMap { scripts =>
searchFiltersForMatches(scripts, filterResponse, parallelism)(
ExecutionContext.fromExecutor(walletConfig.rescanThreadPool)
)
searchFiltersForMatches(scripts, filterResponse, parallelism)
}
f.onComplete {
@ -486,82 +507,6 @@ private[wallet] trait RescanHandling
rescanStateF
}
private def generateAddressesForRescanAction(
account: HDAccount,
addressBatchSize: Int
): DBIOAction[Vector[
BitcoinAddress
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
val receiveAddressesA: DBIOAction[
Vector[
BitcoinAddress
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
DBIOAction.sequence {
1.to(addressBatchSize)
.map(_ => getNewAddressAction(account))
}
}.map(_.toVector)
val changeAddressesA: DBIOAction[
Vector[
BitcoinAddress
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
DBIOAction.sequence {
1.to(addressBatchSize)
.map(_ => getNewChangeAddressAction(account))
}
}.map(_.toVector)
for {
receiveAddresses <- receiveAddressesA
changeAddresses <- changeAddressesA
} yield receiveAddresses ++ changeAddresses
}
/** If forceGeneratSpks is true or addressCount == 0 we generate a new pool of
* scriptpubkeys
*/
private def generateScriptPubKeysAction(
account: HDAccount,
addressBatchSize: Int,
forceGenerateSpks: Boolean
): DBIOAction[Vector[
ScriptPubKey
],
NoStream,
Effect.Read with Effect.Write with Effect.Transactional] = {
val addressCountA = addressDAO.countAction
for {
addressCount <- addressCountA
addresses <- {
if (forceGenerateSpks || addressCount == 0) {
logger.info(
s"Generating $addressBatchSize fresh addresses for the rescan"
)
generateAddressesForRescanAction(account, addressBatchSize)
} else {
// we don't want to continously generate addresses
// if our wallet already has them, so just use what is in the
// database already
addressDAO.findAllAddressesAction().map(_.map(_.address))
}
}
spksDb <- scriptPubKeyDAO.findAllAction()
} yield {
val addrSpks =
addresses.map(_.scriptPubKey)
val otherSpks = spksDb.map(_.scriptPubKey)
(addrSpks ++ otherSpks).distinct
}
}
/** Given a range of filter heights, we fetch the filters associated with
* those heights and emit them downstream
*/
@ -583,19 +528,6 @@ private[wallet] trait RescanHandling
}
}
def generateScriptPubKeys(
account: HDAccount,
addressBatchSize: Int,
forceGenerateSpks: Boolean
): Future[Vector[ScriptPubKey]] = {
val action = generateScriptPubKeysAction(
account = account,
addressBatchSize = addressBatchSize,
forceGenerateSpks = forceGenerateSpks
)
safeDatabase.run(action)
}
/** Searches the given block filters against the given scriptPubKeys for
* matches. If there is a match, request the full block to search
*/
@ -603,11 +535,11 @@ private[wallet] trait RescanHandling
scripts: Vector[ScriptPubKey],
filtersResponse: Vector[ChainQueryApi.FilterResponse],
parallelismLevel: Int
)(implicit ec: ExecutionContext): Future[Vector[BlockMatchingResponse]] = {
): Future[Vector[BlockMatchingResponse]] = {
val startHeightOpt = filtersResponse.headOption.map(_.blockHeight)
val endHeightOpt = filtersResponse.lastOption.map(_.blockHeight)
for {
filtered <- findMatches(filtersResponse, scripts, parallelismLevel)(ec)
filtered <- findMatches(filtersResponse, scripts, parallelismLevel)
_ <- downloadAndProcessBlocks(filtered.map(_.blockHash))
} yield {
logger.debug(
@ -617,11 +549,11 @@ private[wallet] trait RescanHandling
}
}
private[wallet] def findMatches(
override def findMatches(
filters: Vector[FilterResponse],
scripts: Vector[ScriptPubKey],
parallelismLevel: Int
)(implicit ec: ExecutionContext): Future[Vector[BlockMatchingResponse]] = {
): Future[Vector[BlockMatchingResponse]] = {
if (filters.isEmpty) {
logger.info("No Filters to check against")
Future.successful(Vector.empty)