From 1541b2ef1b4c6a22da9364389a95f22a5a6ef3aa Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 14 Oct 2021 15:42:56 +0200 Subject: [PATCH] multi: create and list all default internal accounts --- keychain/derivation.go | 15 +++++++ keychain/interface_test.go | 65 ++++++----------------------- lnrpc/walletrpc/walletkit_server.go | 25 +++++++++-- lnwallet/btcwallet/btcwallet.go | 53 ++++++++++++++++++++++- rpcserver.go | 7 ++++ 5 files changed, 108 insertions(+), 57 deletions(-) diff --git a/keychain/derivation.go b/keychain/derivation.go index 9a0dc390..b10bb273 100644 --- a/keychain/derivation.go +++ b/keychain/derivation.go @@ -105,6 +105,21 @@ const ( KeyFamilyTowerID KeyFamily = 9 ) +// VersionZeroKeyFamilies is a slice of all the known key families for first +// version of the key derivation schema defined in this package. +var VersionZeroKeyFamilies = []KeyFamily{ + KeyFamilyMultiSig, + KeyFamilyRevocationBase, + KeyFamilyHtlcBase, + KeyFamilyPaymentBase, + KeyFamilyDelayBase, + KeyFamilyRevocationRoot, + KeyFamilyNodeKey, + KeyFamilyStaticBackup, + KeyFamilyTowerSession, + KeyFamilyTowerID, +} + // KeyLocator is a two-tuple that can be used to derive *any* key that has ever // been used under the key derivation mechanisms described in this file. // Version 0 of our key derivation schema uses the following BIP43-like diff --git a/keychain/interface_test.go b/keychain/interface_test.go index 654ad22c..3cd345e3 100644 --- a/keychain/interface_test.go +++ b/keychain/interface_test.go @@ -16,25 +16,11 @@ import ( "github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/walletdb" "github.com/davecgh/go-spew/spew" + "github.com/stretchr/testify/require" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // Required in order to create the default database. ) -// versionZeroKeyFamilies is a slice of all the known key families for first -// version of the key derivation schema defined in this package. -var versionZeroKeyFamilies = []KeyFamily{ - KeyFamilyMultiSig, - KeyFamilyRevocationBase, - KeyFamilyHtlcBase, - KeyFamilyPaymentBase, - KeyFamilyDelayBase, - KeyFamilyRevocationRoot, - KeyFamilyNodeKey, - KeyFamilyStaticBackup, - KeyFamilyTowerSession, - KeyFamilyTowerID, -} - var ( testHDSeed = chainhash.Hash{ 0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab, @@ -139,9 +125,7 @@ func TestKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeBitcoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeBitcoin) @@ -151,9 +135,7 @@ func TestKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeLitecoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeLitecoin) @@ -163,9 +145,7 @@ func TestKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeTestnet, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeTestnet) @@ -189,14 +169,11 @@ func TestKeyRingDerivation(t *testing.T) { success := t.Run(fmt.Sprintf("%v", keyRingName), func(t *testing.T) { // First, we'll ensure that we're able to derive keys // from each of the known key families. - for _, keyFam := range versionZeroKeyFamilies { + for _, keyFam := range VersionZeroKeyFamilies { // First, we'll ensure that we can derive the // *next* key in the keychain. keyDesc, err := keyRing.DeriveNextKey(keyFam) - if err != nil { - t.Fatalf("unable to derive next for "+ - "keyFam=%v: %v", keyFam, err) - } + require.NoError(t, err) assertEqualKeyLocator(t, KeyLocator{ Family: keyFam, @@ -212,10 +189,7 @@ func TestKeyRingDerivation(t *testing.T) { Index: 0, } firstKeyDesc, err := keyRing.DeriveKey(keyLoc) - if err != nil { - t.Fatalf("unable to derive first key for "+ - "keyFam=%v: %v", keyFam, err) - } + require.NoError(t, err) if !keyDesc.PubKey.IsEqual(firstKeyDesc.PubKey) { t.Fatalf("mismatched keys: expected %x, "+ "got %x", @@ -240,10 +214,7 @@ func TestKeyRingDerivation(t *testing.T) { Index: uint32(i), } keyDesc, err := keyRing.DeriveKey(keyLoc) - if err != nil { - t.Fatalf("unable to derive first key for "+ - "keyFam=%v: %v", keyFam, err) - } + require.NoError(t, err) // Ensure that the key locator matches // up as well. @@ -260,11 +231,7 @@ func TestKeyRingDerivation(t *testing.T) { Index: randKeyIndex, } keyDesc, err = keyRing.DeriveKey(keyLoc) - if err != nil { - t.Fatalf("unable to derive key_index=%v "+ - "for keyFam=%v: %v", - randKeyIndex, keyFam, err) - } + require.NoError(t, err) assertEqualKeyLocator( t, keyLoc, keyDesc.KeyLocator, ) @@ -293,9 +260,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeBitcoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeBitcoin) @@ -305,9 +270,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeLitecoin, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeLitecoin) @@ -317,9 +280,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { cleanUp, wallet, err := createTestBtcWallet( CoinTypeTestnet, ) - if err != nil { - t.Fatalf("unable to create wallet: %v", err) - } + require.NoError(t, err) keyRing := NewBtcWalletKeyRing(wallet, CoinTypeTestnet) @@ -342,7 +303,7 @@ func TestSecretKeyRingDerivation(t *testing.T) { // For, each key family, we'll ensure that we're able // to obtain the private key of a randomly select child // index within the key family. - for _, keyFam := range versionZeroKeyFamilies { + for _, keyFam := range VersionZeroKeyFamilies { randKeyIndex := uint32(rand.Int31()) keyLoc := KeyLocator{ Family: keyFam, diff --git a/lnrpc/walletrpc/walletkit_server.go b/lnrpc/walletrpc/walletkit_server.go index 364b373d..31796972 100644 --- a/lnrpc/walletrpc/walletkit_server.go +++ b/lnrpc/walletrpc/walletkit_server.go @@ -310,6 +310,14 @@ func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispat return subServer, macPermissions, nil } +// internalScope returns the internal key scope. +func (w *WalletKit) internalScope() waddrmgr.KeyScope { + return waddrmgr.KeyScope{ + Purpose: keychain.BIP0043Purpose, + Coin: w.cfg.ChainParams.HDCoinType, + } +} + // ListUnspent returns useful information about each unspent output owned by the // wallet, as reported by the underlying `ListUnspentWitness`; the information // returned is: outpoint, amount in satoshis, address, address type, @@ -1245,7 +1253,9 @@ func (w *WalletKit) FinalizePsbt(_ context.Context, // marshalWalletAccount converts the properties of an account into its RPC // representation. -func marshalWalletAccount(account *waddrmgr.AccountProperties) (*Account, error) { +func marshalWalletAccount(internalScope waddrmgr.KeyScope, + account *waddrmgr.AccountProperties) (*Account, error) { + var addrType AddressType switch account.KeyScope { case waddrmgr.KeyScopeBIP0049Plus: @@ -1259,6 +1269,10 @@ func marshalWalletAccount(account *waddrmgr.AccountProperties) (*Account, error) switch *account.AddrSchema { case waddrmgr.KeyScopeBIP0049AddrSchema: addrType = AddressType_NESTED_WITNESS_PUBKEY_HASH + + case waddrmgr.ScopeAddrMap[waddrmgr.KeyScopeBIP0049Plus]: + addrType = AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH + default: return nil, fmt.Errorf("unsupported address schema %v", *account.AddrSchema) @@ -1267,6 +1281,9 @@ func marshalWalletAccount(account *waddrmgr.AccountProperties) (*Account, error) case waddrmgr.KeyScopeBIP0084: addrType = AddressType_WITNESS_PUBKEY_HASH + case internalScope: + addrType = AddressType_WITNESS_PUBKEY_HASH + default: return nil, fmt.Errorf("account %v has unsupported "+ "key scope %v", account.AccountName, account.KeyScope) @@ -1340,7 +1357,9 @@ func (w *WalletKit) ListAccounts(ctx context.Context, continue } - rpcAccount, err := marshalWalletAccount(account) + rpcAccount, err := marshalWalletAccount( + w.internalScope(), account, + ) if err != nil { return nil, err } @@ -1431,7 +1450,7 @@ func (w *WalletKit) ImportAccount(ctx context.Context, return nil, err } - rpcAccount, err := marshalWalletAccount(accountProps) + rpcAccount, err := marshalWalletAccount(w.internalScope(), accountProps) if err != nil { return nil, err } diff --git a/lnwallet/btcwallet/btcwallet.go b/lnwallet/btcwallet/btcwallet.go index 3a2fb5f0..86793d18 100644 --- a/lnwallet/btcwallet/btcwallet.go +++ b/lnwallet/btcwallet/btcwallet.go @@ -299,7 +299,7 @@ func (b *BtcWallet) Start() error { } } - _, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope) + scope, err := b.wallet.Manager.FetchScopedKeyManager(b.chainKeyScope) if err != nil { // If the scope hasn't yet been created (it wouldn't been // loaded by default if it was), then we'll manually create the @@ -307,7 +307,7 @@ func (b *BtcWallet) Start() error { err := walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) - _, err := b.wallet.Manager.NewScopedKeyManager( + scope, err = b.wallet.Manager.NewScopedKeyManager( addrmgrNs, b.chainKeyScope, lightningAddrSchema, ) return err @@ -317,6 +317,43 @@ func (b *BtcWallet) Start() error { } } + // Now that the wallet is unlocked, we'll go ahead and make sure we + // create accounts for all the key families we're going to use. This + // will make it possible to list all the account/family xpubs in the + // wallet list RPC. + err = walletdb.Update(b.db, func(tx walletdb.ReadWriteTx) error { + addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) + + for _, keyFam := range keychain.VersionZeroKeyFamilies { + // If this is the multi-sig key family, then we can + // return early as this is the default account that's + // created. + if keyFam == keychain.KeyFamilyMultiSig { + continue + } + + // Otherwise, we'll check if the account already exists, + // if so, we can once again bail early. + _, err := scope.AccountName(addrmgrNs, uint32(keyFam)) + if err == nil { + continue + } + + // If we reach this point, then the account hasn't yet + // been created, so we'll need to create it before we + // can proceed. + err = scope.NewRawAccount(addrmgrNs, uint32(keyFam)) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return err + } + // Establish an RPC connection in addition to starting the goroutines // in the underlying wallet. if err := b.chain.Start(); err != nil { @@ -556,6 +593,18 @@ func (b *BtcWallet) ListAccounts(name string, account := account res = append(res, &account.AccountProperties) } + + accounts, err = b.wallet.Accounts(waddrmgr.KeyScope{ + Purpose: keychain.BIP0043Purpose, + Coin: b.cfg.CoinType, + }) + if err != nil { + return nil, err + } + for _, account := range accounts.Accounts { + account := account + res = append(res, &account.AccountProperties) + } } return res, nil diff --git a/rpcserver.go b/rpcserver.go index 85346b54..db1826f0 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -3019,6 +3019,13 @@ func (r *rpcServer) WalletBalance(ctx context.Context, default: } + // There now also are the accounts for the internal channel + // related keys. We skip those as they'll never have any direct + // balance. + if account.KeyScope.Purpose == keychain.BIP0043Purpose { + continue + } + // Get total balance, from txs that have >= 0 confirmations. totalBal, err := r.server.cc.Wallet.ConfirmedBalance( 0, account.AccountName,