From 36ec40dfa39e5b2cdc9fcc859e092bb7c1bbaeb5 Mon Sep 17 00:00:00 2001 From: Chris Stewart Date: Tue, 4 Apr 2023 11:40:26 -0500 Subject: [PATCH] implement generating addresses when wallet is initialized if creationTime is > 1 hour ago (#5034) --- .../org/bitcoins/wallet/WalletUnitTest.scala | 42 +++++++++++++++++++ .../scala/org/bitcoins/wallet/Wallet.scala | 19 +++++++++ .../wallet/internal/RescanHandling.scala | 2 +- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala b/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala index 3aaa5e4026..3e11745720 100644 --- a/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala +++ b/wallet-test/src/test/scala/org/bitcoins/wallet/WalletUnitTest.scala @@ -9,6 +9,7 @@ import org.bitcoins.core.protocol.BitcoinAddress import org.bitcoins.core.protocol.script._ import org.bitcoins.core.util.FutureUtil import org.bitcoins.crypto.{CryptoUtil, ECPublicKey} +import org.bitcoins.keymanager.{DecryptedMnemonic, WalletStorage} import org.bitcoins.testkit.chain.MockChainQueryApi import org.bitcoins.testkit.wallet.BitcoinSWalletTest import org.bitcoins.testkitcore.util.TransactionTestUtil._ @@ -16,6 +17,7 @@ import org.scalatest.FutureOutcome import org.scalatest.compatible.Assertion import java.nio.file.Files +import java.time.Instant import scala.concurrent.Future class WalletUnitTest extends BitcoinSWalletTest { @@ -317,4 +319,44 @@ class WalletUnitTest extends BitcoinSWalletTest { toBroadcast <- wallet.getTransactionsToBroadcast } yield assert(toBroadcast.map(_.txIdBE) == Vector(dummyPrevTx1.txIdBE)) } + + it must "generate addresses for a wallet initialized with a old seed" in { + wallet: Wallet => + for { + isEmpty <- wallet.isEmpty() + _ = assert(isEmpty) + //manually override the seeds creation time + seedPath = wallet.walletConfig.kmConf.seedPath + mnemonic = WalletStorage + .decryptSeedFromDisk(seedPath = seedPath, passphraseOpt = None) + .getOrElse(sys.error(s"failed to decrypt seed for unit test")) + .asInstanceOf[DecryptedMnemonic] + _ = { + //delete old seed file because we do not allow overwriting a seed file + Files.delete(wallet.walletConfig.seedPath) + } + modifiedMnemonic = mnemonic.copy(creationTime = + Instant.now.minusSeconds(60 * 60 + 1)) //1 hour and 1 minute + + _ = WalletStorage.writeSeedToDisk(seedPath, modifiedMnemonic) + //delete old wallet database + _ = { + if (pgEnabled) { + //cannot delete database file if using postgres + () + } else { + val path = wallet.walletConfig.datadir + .resolve(wallet.walletConfig.walletName) + .resolve("walletdb.sqlite") + Files.delete(path) + } + + } + _ = wallet.walletConfig.migrate() + //initialize it + initOldWallet <- Wallet.initialize(wallet, None) + isOldWalletEmpty <- initOldWallet.isEmpty() + } yield assert(!isOldWalletEmpty) + + } } diff --git a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala index f35ddd1436..c3c611fca7 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala @@ -46,6 +46,7 @@ import scodec.bits.ByteVector import slick.dbio.{DBIOAction, Effect, NoStream} import java.time.Instant +import java.time.temporal.ChronoUnit import java.util.concurrent.TimeUnit import scala.concurrent.{ExecutionContext, Future} import scala.util.control.NonFatal @@ -1061,6 +1062,24 @@ object Wallet extends WalletLogger { _ = accounts.foreach { a => logger.info(s"Created account=${a} to DB") } + _ <- { + //check if creationTime is well in the past, if so generate a pool of addresses + //see: https://github.com/bitcoin-s/bitcoin-s/issues/5033 + val creationTime = wallet.keyManager.creationTime + val threshold = Instant.now().minus(1, ChronoUnit.HOURS) + val isOldCreationTime = creationTime.compareTo(threshold) <= 0 + if (isOldCreationTime) { + wallet + .generateScriptPubKeys(account = walletAppConfig.defaultAccount, + addressBatchSize = + walletAppConfig.discoveryBatchSize, + forceGenerateSpks = true) + .map(_ => ()) + } else { + //fresh seed, no need to generate addresses + Future.unit + } + } } yield { logger.debug(s"Created root level accounts for wallet") wallet diff --git a/wallet/src/main/scala/org/bitcoins/wallet/internal/RescanHandling.scala b/wallet/src/main/scala/org/bitcoins/wallet/internal/RescanHandling.scala index f2c3f654a9..65672950a7 100644 --- a/wallet/src/main/scala/org/bitcoins/wallet/internal/RescanHandling.scala +++ b/wallet/src/main/scala/org/bitcoins/wallet/internal/RescanHandling.scala @@ -500,7 +500,7 @@ private[wallet] trait RescanHandling extends WalletLogger { } } - private def generateScriptPubKeys( + def generateScriptPubKeys( account: HDAccount, addressBatchSize: Int, forceGenerateSpks: Boolean): Future[Vector[ScriptPubKey]] = {