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:
8 files changed
+713
-579
No files matched your search
+576
-569
File diff suppressed because it is too large.
Load diff
@@ -900,6 +900,8 @@ message ListUnspentResponse {
|
|||||||
enum AddressType {
|
enum AddressType {
|
||||||
WITNESS_PUBKEY_HASH = 0;
|
WITNESS_PUBKEY_HASH = 0;
|
||||||
NESTED_PUBKEY_HASH = 1;
|
NESTED_PUBKEY_HASH = 1;
|
||||||
|
UNUSED_WITNESS_PUBKEY_HASH = 2;
|
||||||
|
UNUSED_NESTED_PUBKEY_HASH = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message NewAddressRequest {
|
message NewAddressRequest {
|
||||||
|
|||||||
@@ -793,7 +793,9 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"WITNESS_PUBKEY_HASH",
|
"WITNESS_PUBKEY_HASH",
|
||||||
"NESTED_PUBKEY_HASH"
|
"NESTED_PUBKEY_HASH",
|
||||||
|
"UNUSED_WITNESS_PUBKEY_HASH",
|
||||||
|
"UNUSED_NESTED_PUBKEY_HASH"
|
||||||
],
|
],
|
||||||
"default": "WITNESS_PUBKEY_HASH"
|
"default": "WITNESS_PUBKEY_HASH"
|
||||||
}
|
}
|
||||||
@@ -1289,7 +1291,9 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"WITNESS_PUBKEY_HASH",
|
"WITNESS_PUBKEY_HASH",
|
||||||
"NESTED_PUBKEY_HASH"
|
"NESTED_PUBKEY_HASH",
|
||||||
|
"UNUSED_WITNESS_PUBKEY_HASH",
|
||||||
|
"UNUSED_NESTED_PUBKEY_HASH"
|
||||||
],
|
],
|
||||||
"default": "WITNESS_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)",
|
"description": "- `p2wkh`: Pay to witness key hash (`WITNESS_PUBKEY_HASH` = 0)\n- `np2wkh`: Pay to nested witness key hash (`NESTED_PUBKEY_HASH` = 1)",
|
||||||
|
|||||||
@@ -254,6 +254,29 @@ func (b *BtcWallet) NewAddress(t lnwallet.AddressType, change bool) (btcutil.Add
|
|||||||
return b.wallet.NewAddress(defaultAccount, keyScope)
|
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
|
// IsOurAddress checks if the passed address belongs to this wallet
|
||||||
//
|
//
|
||||||
// This is a part of the WalletController interface.
|
// This is a part of the WalletController interface.
|
||||||
|
|||||||
@@ -150,6 +150,14 @@ type WalletController interface {
|
|||||||
// p2wsh, etc.
|
// p2wsh, etc.
|
||||||
NewAddress(addrType AddressType, change bool) (btcutil.Address, error)
|
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 checks if the passed address belongs to this wallet
|
||||||
IsOurAddress(a btcutil.Address) bool
|
IsOurAddress(a btcutil.Address) bool
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
type walletTestCase struct {
|
||||||
name string
|
name string
|
||||||
test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
|
test func(miner *rpctest.Harness, alice, bob *lnwallet.LightningWallet,
|
||||||
@@ -2219,6 +2275,10 @@ var walletTests = []walletTestCase{
|
|||||||
name: "test cancel non-existent reservation",
|
name: "test cancel non-existent reservation",
|
||||||
test: testCancelNonExistentReservation,
|
test: testCancelNonExistentReservation,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "last unused addr",
|
||||||
|
test: testLastUnusedAddr,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "reorg wallet balance",
|
name: "reorg wallet balance",
|
||||||
test: testReorgWalletBalance,
|
test: testReorgWalletBalance,
|
||||||
|
|||||||
@@ -226,6 +226,11 @@ func (m *mockWalletController) NewAddress(addrType lnwallet.AddressType,
|
|||||||
m.rootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams)
|
m.rootKey.PubKey().SerializeCompressed(), &chaincfg.MainNetParams)
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
func (*mockWalletController) LastUnusedAddress(addrType lnwallet.AddressType) (
|
||||||
|
btcutil.Address, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (*mockWalletController) IsOurAddress(a btcutil.Address) bool {
|
func (*mockWalletController) IsOurAddress(a btcutil.Address) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-8
@@ -932,17 +932,42 @@ func (r *rpcServer) NewAddress(ctx context.Context,
|
|||||||
|
|
||||||
// Translate the gRPC proto address type to the wallet controller's
|
// Translate the gRPC proto address type to the wallet controller's
|
||||||
// available address types.
|
// available address types.
|
||||||
var addrType lnwallet.AddressType
|
var (
|
||||||
|
addr btcutil.Address
|
||||||
|
err error
|
||||||
|
)
|
||||||
switch in.Type {
|
switch in.Type {
|
||||||
case lnrpc.AddressType_WITNESS_PUBKEY_HASH:
|
case lnrpc.AddressType_WITNESS_PUBKEY_HASH:
|
||||||
addrType = lnwallet.WitnessPubKey
|
addr, err = r.server.cc.wallet.NewAddress(
|
||||||
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
|
lnwallet.WitnessPubKey, false,
|
||||||
addrType = lnwallet.NestedWitnessPubKey
|
)
|
||||||
}
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
addr, err := r.server.cc.wallet.NewAddress(addrType, false)
|
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
|
||||||
if err != nil {
|
addr, err = r.server.cc.wallet.NewAddress(
|
||||||
return nil, err
|
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())
|
rpcsLog.Infof("[newaddress] addr=%v", addr.String())
|
||||||
|
|||||||
Reference in new issue
Block a user