2020 06 14 wallet root accounts (#1556)

* Implement unit test cases for initializing a wallet twice, and failing with an exception when we initialize a wallet with a different key-manager

* Use bip39PasswordOpt cached in the WalletUnitTest suite

* Turn off logging

* Run scalafmt

* Turn log level back to WARN

* Run scalafmt
This commit is contained in:
Chris Stewart 2020-06-17 15:11:42 -05:00 committed by GitHub
parent 914c905bd7
commit ba0f38ccf6
4 changed files with 62 additions and 16 deletions

View file

@ -418,7 +418,6 @@ object BitcoinSWalletTest extends WalletLogger {
extraConfig: Option[Config] = None)(
implicit config: BitcoinSAppConfig,
ec: ExecutionContext): Future[Wallet] = {
val newWalletConf = extraConfig match {
case None =>
config.walletConf

View file

@ -20,11 +20,12 @@ import org.scalatest.compatible.Assertion
import scala.concurrent.Future
class WalletUnitTest extends BitcoinSWalletTest {
private val bip39PasswordOpt: Option[String] = getBIP39PasswordOpt()
override type FixtureParam = WalletApi
override type FixtureParam = Wallet
override def withFixture(test: OneArgAsyncTest): FutureOutcome =
withNewWallet(test, getBIP39PasswordOpt())
withNewWallet(test, bip39PasswordOpt)
behavior of "Wallet - unit test"
@ -166,4 +167,26 @@ class WalletUnitTest extends BitcoinSWalletTest {
blockHeight = 1)) == matched)
}
}
it must "be able to call initialize twice without throwing an exception if we have the same key manager" in {
wallet: Wallet =>
val twiceF = Wallet.initialize(wallet, bip39PasswordOpt).flatMap { _ =>
Wallet.initialize(wallet, bip39PasswordOpt)
}
twiceF.map(_ => succeed)
}
it must "be able to detect an incompatible key manager with a wallet" in {
wallet: Wallet =>
recoverToSucceededIf[RuntimeException] {
Wallet.initialize(wallet, bip39PasswordOpt).flatMap { _ =>
//use a BIP39 password to make the key-managers different
Wallet.initialize(
wallet,
Some("random-password-to-make-key-managers-different"))
}
}
}
}

View file

@ -440,7 +440,7 @@ object Wallet extends WalletLogger {
WalletImpl(keyManager, nodeApi, chainQueryApi, feeRateApi, creationTime)
}
/** Creates the level 0 account for the given HD purpose */
/** Creates the level 0 account for the given HD purpose, if the root account exists do nothing */
private def createRootAccount(wallet: Wallet, keyManager: BIP39KeyManager)(
implicit walletAppConfig: WalletAppConfig,
ec: ExecutionContext): Future[AccountDb] = {
@ -451,15 +451,35 @@ object Wallet extends WalletLogger {
// safe since we're deriving from a priv
val xpub = keyManager.deriveXPub(account).get
val accountDb = AccountDb(xpub, account)
logger.debug(
s"Creating account with constant prefix ${keyManager.kmParams.purpose}")
wallet.accountDAO
.create(accountDb)
.map { written =>
logger.debug(
s"Saved account with constant prefix ${keyManager.kmParams.purpose} to DB")
written
}
val accountDAO = wallet.accountDAO
//see if we already have this account in our database
//Three possible cases:
//1. We have nothing in our database, so we need to insert it
//2. We already have this account in our database, so we do nothing
//3. We have this account in our database, with a DIFFERENT xpub. This is bad. Fail with an exception
// this most likely means that we have a different key manager than we expected
val accountOptF = accountDAO.read(account.coin, account.index)
accountOptF.flatMap {
case Some(account) =>
if (account.xpub != xpub) {
val errorMsg = s"Divergent xpubs for account=${account}. Existing database xpub=${account.xpub}, new xpub=${xpub}. " +
s"It is possible we have a different key manager being used than expected, keymanager=${keyManager}"
Future.failed(new RuntimeException(errorMsg))
} else {
logger.debug(
s"Account already exists in database, no need to create it, account=${account}")
Future.successful(account)
}
case None =>
wallet.accountDAO
.create(accountDb)
.map { written =>
logger.info(s"Created account=${accountDb} to DB")
written
}
}
}
def initialize(wallet: Wallet, bip39PasswordOpt: Option[String])(
@ -485,8 +505,9 @@ object Wallet extends WalletLogger {
case Left(err) =>
//probably means you haven't initialized the key manager via the
//'CreateKeyManagerApi'
throw new RuntimeException(
s"Failed to create keymanager with params=$kmParams err=$err")
Future.failed(
new RuntimeException(
s"Failed to create keymanager with params=$kmParams err=$err"))
}
}

View file

@ -6,7 +6,10 @@ import org.bitcoins.keymanager.util.HDUtil
/** Represents the xpub at the account level, NOT the root xpub
* that in conjunction with the path specified in hdAccount
* can be used to generate the account level xpub */
* can be used to generate the account level xpub
* m / purpose' / coin_type' / account'
* @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#path-levels
* */
case class AccountDb(xpub: ExtPublicKey, hdAccount: HDAccount) {
def xpubVersion: ExtKeyPubVersion = xpub.version