Merge pull request #2661 from Roasbeef/last-unused-addr

lnwallet+lnrpc: add ability to fetch the last unused wallet addr
This commit is contained in:
Wilmer Paulino 2019-03-05 16:53:43 -08:00 committed by GitHub
commit 63273e195e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 713 additions and 579 deletions

File diff suppressed because it is too large Load Diff

View File

@ -900,6 +900,8 @@ message ListUnspentResponse {
enum AddressType {
WITNESS_PUBKEY_HASH = 0;
NESTED_PUBKEY_HASH = 1;
UNUSED_WITNESS_PUBKEY_HASH = 2;
UNUSED_NESTED_PUBKEY_HASH = 3;
}
message NewAddressRequest {

View File

@ -793,7 +793,9 @@
"type": "string",
"enum": [
"WITNESS_PUBKEY_HASH",
"NESTED_PUBKEY_HASH"
"NESTED_PUBKEY_HASH",
"UNUSED_WITNESS_PUBKEY_HASH",
"UNUSED_NESTED_PUBKEY_HASH"
],
"default": "WITNESS_PUBKEY_HASH"
}
@ -1289,7 +1291,9 @@
"type": "string",
"enum": [
"WITNESS_PUBKEY_HASH",
"NESTED_PUBKEY_HASH"
"NESTED_PUBKEY_HASH",
"UNUSED_WITNESS_PUBKEY_HASH",
"UNUSED_NESTED_PUBKEY_HASH"
],
"default": "WITNESS_PUBKEY_HASH",
"description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)",

View File

@ -254,6 +254,29 @@ func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool) (btcutil.Add
return b.wallet.NewAddress(defaultAccount, keyScope)
}
// LastUnusedAddress returns the last *unused* address known by the wallet. An
// address is unused if it hasn't received any payments. This can be useful in
// UIs in order to continually show the "freshest" address without having to
// worry about "address inflation" caused by continual refreshing. Similar to
// NewAddress it can derive a specified address type, and also optionally a
// change address.
func (b *BtcWallet) LastUnusedAddress(addrType lnwallet.AddressType) (
btcutil.Address, error) {
var keyScope waddrmgr.KeyScope
switch addrType {
case lnwallet.WitnessPubKey:
keyScope = waddrmgr.KeyScopeBIP0084
case lnwallet.NestedWitnessPubKey:
keyScope = waddrmgr.KeyScopeBIP0049Plus
default:
return nil, fmt.Errorf("unknown address type")
}
return b.wallet.CurrentAddress(defaultAccount, keyScope)
}
// IsOurAddress checks if the passed address belongs to this wallet
//
// This is a part of the WalletController interface.

View File

@ -150,6 +150,14 @@ type WalletController interface {
// p2wsh, etc.
NewAddress(addrType AddressType, change bool) (btcutil.Address, error)
// LastUnusedAddress returns the last *unused* address known by the
// wallet. An address is unused if it hasn't received any payments.
// This can be useful in UIs in order to continually show the
// "freshest" address without having to worry about "address inflation"
// caused by continual refreshing. Similar to NewAddress it can derive
// a specified address type. By default, this is a non-change address.
LastUnusedAddress(addrType AddressType) (btcutil.Address, error)
// IsOurAddress checks if the passed address belongs to this wallet
IsOurAddress(a btcutil.Address) bool

View File

@ -2165,6 +2165,62 @@ func testChangeOutputSpendConfirmation(r *rpctest.Harness,
}
}
// testLastUnusedAddr tests that the LastUnusedAddress returns the address if
// it isn't used, and also that once the address becomes used, then it's
// properly rotated.
func testLastUnusedAddr(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
if _, err := miner.Node.Generate(1); err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// We'll repeat this test for each address type to ensure they're all
// rotated properly.
addrTypes := []lnwallet.AddressType{
lnwallet.WitnessPubKey, lnwallet.NestedWitnessPubKey,
}
for _, addrType := range addrTypes {
addr1, err := alice.LastUnusedAddress(addrType)
if err != nil {
t.Fatalf("unable to get addr: %v", err)
}
addr2, err := alice.LastUnusedAddress(addrType)
if err != nil {
t.Fatalf("unable to get addr: %v", err)
}
// If we generate two addresses back to back, then we should
// get the same addr, as none of them have been used yet.
if addr1.String() != addr2.String() {
t.Fatalf("addresses changed w/o use: %v vs %v", addr1, addr2)
}
// Next, we'll have Bob pay to Alice's new address. This should
// trigger address rotation at the backend wallet.
addrScript, err := txscript.PayToAddrScript(addr1)
if err != nil {
t.Fatalf("unable to convert addr to script: %v", err)
}
feeRate := lnwallet.SatPerKWeight(2500)
output := &wire.TxOut{
Value: 1000000,
PkScript: addrScript,
}
sendCoins(t, miner, bob, alice, output, feeRate)
// If we make a new address, then it should be brand new, as
// the prior address has been used.
addr3, err := alice.LastUnusedAddress(addrType)
if err != nil {
t.Fatalf("unable to get addr: %v", err)
}
if addr1.String() == addr3.String() {
t.Fatalf("address should have changed but didn't")
}
}
}
type walletTestCase struct {
name string
test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
@ -2219,6 +2275,10 @@ var walletTests = []walletTestCase{
name: "test cancel non-existent reservation",
test: testCancelNonExistentReservation,
},
{
name: "last unused addr",
test: testLastUnusedAddr,
},
{
name: "reorg wallet balance",
test: testReorgWalletBalance,

View File

@ -226,6 +226,11 @@ func (m *mockWalletController) NewAddress(addrType lnwallet.AddressType,
m.rootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams)
return addr, nil
}
func (*mockWalletController) LastUnusedAddress(addrType lnwallet.AddressType) (
btcutil.Address, error) {
return nil, nil
}
func (*mockWalletController) IsOurAddress(a btcutil.Address) bool {
return false
}

View File

@ -932,17 +932,42 @@ func (r *rpcServer) NewAddress(ctx context.Context,
// Translate the gRPC proto address type to the wallet controller's
// available address types.
var addrType lnwallet.AddressType
var (
addr btcutil.Address
err error
)
switch in.Type {
case lnrpc.AddressType_WITNESS_PUBKEY_HASH:
addrType = lnwallet.WitnessPubKey
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
addrType = lnwallet.NestedWitnessPubKey
}
addr, err = r.server.cc.wallet.NewAddress(
lnwallet.WitnessPubKey, false,
)
if err != nil {
return nil, err
}
addr, err := r.server.cc.wallet.NewAddress(addrType, false)
if err != nil {
return nil, err
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
addr, err = r.server.cc.wallet.NewAddress(
lnwallet.NestedWitnessPubKey, false,
)
if err != nil {
return nil, err
}
case lnrpc.AddressType_UNUSED_WITNESS_PUBKEY_HASH:
addr, err = r.server.cc.wallet.LastUnusedAddress(
lnwallet.WitnessPubKey,
)
if err != nil {
return nil, err
}
case lnrpc.AddressType_UNUSED_NESTED_PUBKEY_HASH:
addr, err = r.server.cc.wallet.LastUnusedAddress(
lnwallet.NestedWitnessPubKey,
)
if err != nil {
return nil, err
}
}
rpcsLog.Infof("[newaddress] addr=%v", addr.String())