diff --git a/cmd/lncli/walletrpc_active.go b/cmd/lncli/walletrpc_active.go index 7bae32fbf..577121928 100644 --- a/cmd/lncli/walletrpc_active.go +++ b/cmd/lncli/walletrpc_active.go @@ -1447,6 +1447,9 @@ var importAccountCommand = cli.Command{ The address type can usually be inferred from the key's version, but may be required for certain keys to map them into the proper scope. + If an account with the same name already exists (even with a different + key scope), an error will be returned. + For BIP-0044 keys, an address type must be specified as we intend to not support importing BIP-0044 keys into the wallet using the legacy pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will diff --git a/itest/lnd_wallet_import_test.go b/itest/lnd_wallet_import_test.go index afd96a352..c0012213d 100644 --- a/itest/lnd_wallet_import_test.go +++ b/itest/lnd_wallet_import_test.go @@ -2,6 +2,7 @@ package itest import ( "bytes" + "fmt" "testing" "time" @@ -468,6 +469,10 @@ func testWalletImportAccount(ht *lntest.HarnessTest) { name string addrType walletrpc.AddressType }{ + { + name: "standard BIP-0044", + addrType: walletrpc.AddressType_WITNESS_PUBKEY_HASH, + }, { name: "standard BIP-0049", addrType: walletrpc. @@ -550,6 +555,34 @@ func runWalletImportAccountScenario(ht *lntest.HarnessTest, } dave.RPC.ImportAccount(importReq) + // Try to import an account with the same name but with a different + // key scope. It should return an error. + otherAddrType := walletrpc.AddressType_TAPROOT_PUBKEY + if addrType == walletrpc.AddressType_TAPROOT_PUBKEY { + otherAddrType-- + } + + listReq = &walletrpc.ListAccountsRequest{ + Name: "default", + AddressType: otherAddrType, + } + listResp = carol.RPC.ListAccounts(listReq) + require.Len(ht, listResp.Accounts, 1) + + carolAccountOtherAddrType := listResp.Accounts[0] + + errAccountExists := fmt.Sprintf( + "account '%s' already exists", importedAccount, + ) + + importReq = &walletrpc.ImportAccountRequest{ + Name: importedAccount, + ExtendedPublicKey: carolAccountOtherAddrType.ExtendedPublicKey, + AddressType: otherAddrType, + } + err := dave.RPC.ImportAccountAssertErr(importReq) + require.ErrorContains(ht, err, errAccountExists) + // We'll generate an address for Carol from Dave's node to receive some // funds. externalAddr := newExternalAddr( diff --git a/lntest/rpc/wallet_kit.go b/lntest/rpc/wallet_kit.go index 95af5f92d..f28eedf6a 100644 --- a/lntest/rpc/wallet_kit.go +++ b/lntest/rpc/wallet_kit.go @@ -235,6 +235,20 @@ func (h *HarnessRPC) ImportAccount( return resp } +// ImportAccountAssertErr makes the ImportAccount RPC call and asserts an error +// is returned. It then returns the error. +func (h *HarnessRPC) ImportAccountAssertErr( + req *walletrpc.ImportAccountRequest) error { + + ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout) + defer cancel() + + _, err := h.WalletKit.ImportAccount(ctxt, req) + require.Error(h, err) + + return err +} + // ImportPublicKey makes a RPC call to the node's WalletKitClient and asserts. // //nolint:lll diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 11f76a222..3050be03a 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -843,6 +843,10 @@ func (b *BtcWallet) ListAddresses(name string, // The address type can usually be inferred from the key's version, but may be // required for certain keys to map them into the proper scope. // +// For custom accounts, we will first check if there is no account with the same +// name (even with a different key scope). No custom account should have various +// key scopes as it will result in non-deterministic behaviour. +// // For BIP-0044 keys, an address type must be specified as we intend to not // support importing BIP-0044 keys into the wallet using the legacy // pay-to-pubkey-hash (P2PKH) scheme. A nested witness address type will force @@ -860,6 +864,22 @@ func (b *BtcWallet) ImportAccount(name string, accountPubKey *hdkeychain.Extende dryRun bool) (*waddrmgr.AccountProperties, []btcutil.Address, []btcutil.Address, error) { + // For custom accounts, we first check if there is no existing account + // with the same name. + if name != lnwallet.DefaultAccountName && + name != waddrmgr.ImportedAddrAccountName { + + _, err := b.ListAccounts(name, nil) + if err == nil { + return nil, nil, nil, + fmt.Errorf("account '%s' already exists", + name) + } + if !waddrmgr.IsError(err, waddrmgr.ErrAccountNotFound) { + return nil, nil, nil, err + } + } + if !dryRun { accountProps, err := b.wallet.ImportAccount( name, accountPubKey, masterKeyFingerprint, addrType,