multi: create and list all default internal accounts

This commit is contained in:
Oliver Gugger 2021-10-14 15:42:56 +02:00
parent ceb31f9034
commit 1541b2ef1b
No known key found for this signature in database
GPG key ID: 8E4256593F177720
5 changed files with 108 additions and 57 deletions

View file

@ -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

View file

@ -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,

View file

@ -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
}

View file

@ -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

View file

@ -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,