multi: add a rpc endpoint to track the recovery process

This commit is contained in:
yyforyongyu 2020-06-09 14:47:17 +08:00
parent c1ef5bb908
commit 987edc9d81
17 changed files with 1182 additions and 613 deletions

View File

@ -1729,6 +1729,27 @@ func getInfo(ctx *cli.Context) error {
return nil
}
var getRecoveryInfoCommand = cli.Command{
Name: "getrecoveryinfo",
Usage: "Display information about an ongoing recovery attempt.",
Action: actionDecorator(getRecoveryInfo),
}
func getRecoveryInfo(ctx *cli.Context) error {
ctxb := context.Background()
client, cleanUp := getClient(ctx)
defer cleanUp()
req := &lnrpc.GetRecoveryInfoRequest{}
resp, err := client.GetRecoveryInfo(ctxb, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var pendingChannelsCommand = cli.Command{
Name: "pendingchannels",
Category: "Channels",

View File

@ -271,6 +271,7 @@ func main() {
walletBalanceCommand,
channelBalanceCommand,
getInfoCommand,
getRecoveryInfoCommand,
pendingChannelsCommand,
sendPaymentCommand,
payInvoiceCommand,

View File

@ -164,7 +164,15 @@ That final line indicates the rescan is complete! If not all funds have
appeared, then the user may need to _repeat_ the process with a higher recovery
window. Depending on how old the wallet is (the cipher seed stores the wallet's
birthday!) and how many addresses were used, the rescan may take anywhere from
a few minutes to a few hours.
a few minutes to a few hours. To track the recovery progress, one can use the
command `lncli getrecoveryinfo`. When finished, the following is returned,
```
{
"recovery_mode": true,
"recovery_finished": true,
"progress": 1
}
```
If the rescan wasn't able to complete fully (`lnd` was shutdown for example),
then from `lncli unlock`, it's possible to _restart_ the rescan from where it

View File

@ -55,6 +55,8 @@ description):
* Lists all available connected peers.
* GetInfo
* Returns basic data concerning the daemon.
* GetRecoveryInfo
* Returns information about recovery process.
* PendingChannels
* List the number of pending (not fully confirmed) channels.
* ListChannels

View File

@ -41,6 +41,8 @@ http:
get: "/v1/peers/subscribe"
- selector: lnrpc.Lightning.GetInfo
get: "/v1/getinfo"
- selector: lnrpc.Lightning.GetRecoveryInfo
get: "/v1/getrecoveryinfo"
- selector: lnrpc.Lightning.PendingChannels
get: "/v1/channels/pending"
- selector: lnrpc.Lightning.ListChannels

View File

@ -1876,8 +1876,8 @@ func (m *ForwardHtlcInterceptRequest) GetExpiry() uint32 {
}
//*
//ForwardHtlcInterceptResponse enables the caller to resolve a previously hold forward.
//The caller can choose either to:
//ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
//forward. The caller can choose either to:
//- `Resume`: Execute the default behavior (usually forward).
//- `Reject`: Fail the htlc backwards.
//- `Settle`: Settle this htlc with a given preimage.

View File

@ -98,7 +98,7 @@ service Router {
rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) {
option deprecated = true;
}
/**
HtlcInterceptor dispatches a bi-directional streaming RPC in which
Forwarded HTLC requests are sent to the client and the client responds with
@ -106,7 +106,8 @@ service Router {
In case of interception, the htlc can be either settled, cancelled or
resumed later by using the ResolveHoldForward endpoint.
*/
rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse) returns (stream ForwardHtlcInterceptRequest);
rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse)
returns (stream ForwardHtlcInterceptRequest);
}
message SendPaymentRequest {
@ -589,7 +590,7 @@ message PaymentStatus {
repeated lnrpc.HTLCAttempt htlcs = 4;
}
message CircuitKey {
message CircuitKey {
/// The id of the channel that the is part of this circuit.
uint64 chan_id = 1;
@ -618,14 +619,14 @@ message ForwardHtlcInterceptRequest {
}
/**
ForwardHtlcInterceptResponse enables the caller to resolve a previously hold forward.
The caller can choose either to:
ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
forward. The caller can choose either to:
- `Resume`: Execute the default behavior (usually forward).
- `Reject`: Fail the htlc backwards.
- `Settle`: Settle this htlc with a given preimage.
*/
message ForwardHtlcInterceptResponse {
/**
/**
The key of this forwarded htlc. It defines the incoming channel id and
the index in this channel.
*/
@ -638,9 +639,8 @@ message ForwardHtlcInterceptResponse {
bytes preimage = 3;
}
enum ResolveHoldForwardAction {
enum ResolveHoldForwardAction {
SETTLE = 0;
FAIL = 1;
RESUME = 2;
RESUME = 2;
}

File diff suppressed because it is too large Load Diff

View File

@ -519,6 +519,24 @@ func local_request_Lightning_GetInfo_0(ctx context.Context, marshaler runtime.Ma
}
func request_Lightning_GetRecoveryInfo_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetRecoveryInfoRequest
var metadata runtime.ServerMetadata
msg, err := client.GetRecoveryInfo(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_GetRecoveryInfo_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetRecoveryInfoRequest
var metadata runtime.ServerMetadata
msg, err := server.GetRecoveryInfo(ctx, &protoReq)
return msg, metadata, err
}
func request_Lightning_PendingChannels_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq PendingChannelsRequest
var metadata runtime.ServerMetadata
@ -2194,6 +2212,26 @@ func RegisterLightningHandlerServer(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("GET", pattern_Lightning_GetRecoveryInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_GetRecoveryInfo_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetRecoveryInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_PendingChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -3197,6 +3235,26 @@ func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("GET", pattern_Lightning_GetRecoveryInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_GetRecoveryInfo_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Lightning_GetRecoveryInfo_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Lightning_PendingChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -3953,6 +4011,8 @@ var (
pattern_Lightning_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getinfo"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Lightning_GetRecoveryInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "getrecoveryinfo"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Lightning_PendingChannels_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "channels", "pending"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Lightning_ListChannels_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "channels"}, "", runtime.AssumeColonVerbOpt(true)))
@ -4059,6 +4119,8 @@ var (
forward_Lightning_GetInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_GetRecoveryInfo_0 = runtime.ForwardResponseMessage
forward_Lightning_PendingChannels_0 = runtime.ForwardResponseMessage
forward_Lightning_ListChannels_0 = runtime.ForwardResponseMessage

View File

@ -140,6 +140,14 @@ service Lightning {
*/
rpc GetInfo (GetInfoRequest) returns (GetInfoResponse);
/** lncli: `getrecoveryinfo`
GetRecoveryInfo returns information concerning the recovery mode including
whether it's in a recovery mode, whether the recovery is finished, and the
progress made so far.
*/
rpc GetRecoveryInfo (GetRecoveryInfoRequest)
returns (GetRecoveryInfoResponse);
// TODO(roasbeef): merge with below with bool?
/* lncli: `pendingchannels`
PendingChannels returns a list of all the channels that are currently
@ -1381,6 +1389,19 @@ message GetInfoResponse {
map<uint32, Feature> features = 19;
}
message GetRecoveryInfoRequest {
}
message GetRecoveryInfoResponse {
// Whether the wallet is in recovery mode
bool recovery_mode = 1;
// Whether the wallet recovery progress is finished
bool recovery_finished = 2;
// The recovery progress, ranging from 0 to 1.
double progress = 3;
}
message Chain {
// The blockchain the node is on (eg bitcoin, litecoin)
string chain = 1;

View File

@ -829,6 +829,29 @@
]
}
},
"/v1/getrecoveryinfo": {
"get": {
"summary": "* lncli: `getrecoveryinfo`\nGetRecoveryInfo returns information concerning the recovery mode including\nwhether it's in a recovery mode, whether the recovery is finished, and the\nprogress made so far.",
"operationId": "GetRecoveryInfo",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/lnrpcGetRecoveryInfoResponse"
}
},
"default": {
"description": "An unexpected error response",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
},
"tags": [
"Lightning"
]
}
},
"/v1/graph": {
"get": {
"summary": "lncli: `describegraph`\nDescribeGraph returns a description of the latest graph state from the\npoint of view of the node. The graph information is partitioned into two\ncomponents: all the nodes/vertexes, and all the edges that connect the\nvertexes themselves. As this is a directed graph, the edges also contain\nthe node directional specific routing policy which includes: the time lock\ndelta, fee information, etc.",
@ -3530,6 +3553,26 @@
}
}
},
"lnrpcGetRecoveryInfoResponse": {
"type": "object",
"properties": {
"recovery_mode": {
"type": "boolean",
"format": "boolean",
"title": "Whether the wallet is in recovery mode"
},
"recovery_finished": {
"type": "boolean",
"format": "boolean",
"title": "Whether the wallet recovery progress is finished"
},
"progress": {
"type": "number",
"format": "double",
"description": "The recovery progress, ranging from 0 to 1."
}
}
},
"lnrpcGraphTopologyUpdate": {
"type": "object",
"properties": {

View File

@ -817,6 +817,105 @@ const (
AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH
)
// testGetRecoveryInfo checks whether lnd gives the right information about
// the wallet recovery process.
func testGetRecoveryInfo(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// First, create a new node with strong passphrase and grab the mnemonic
// used for key derivation. This will bring up Carol with an empty
// wallet, and such that she is synced up.
password := []byte("The Magic Words are Squeamish Ossifrage")
carol, mnemonic, err := net.NewNodeWithSeed("Carol", nil, password)
if err != nil {
t.Fatalf("unable to create node with seed; %v", err)
}
shutdownAndAssert(net, t, carol)
checkInfo := func(expectedRecoveryMode, expectedRecoveryFinished bool,
expectedProgress float64, recoveryWindow int32) {
// Restore Carol, passing in the password, mnemonic, and
// desired recovery window.
node, err := net.RestoreNodeWithSeed(
"Carol", nil, password, mnemonic, recoveryWindow, nil,
)
if err != nil {
t.Fatalf("unable to restore node: %v", err)
}
// Wait for Carol to sync to the chain.
_, minerHeight, err := net.Miner.Node.GetBestBlock()
if err != nil {
t.Fatalf("unable to get current blockheight %v", err)
}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
err = waitForNodeBlockHeight(ctxt, node, minerHeight)
if err != nil {
t.Fatalf("unable to sync to chain: %v", err)
}
// Query carol for her current wallet recovery progress.
var (
recoveryMode bool
recoveryFinished bool
progress float64
)
err = wait.Predicate(func() bool {
// Verify that recovery info gives the right response.
req := &lnrpc.GetRecoveryInfoRequest{}
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
resp, err := node.GetRecoveryInfo(ctxt, req)
if err != nil {
t.Fatalf("unable to query recovery info: %v", err)
}
recoveryMode = resp.RecoveryMode
recoveryFinished = resp.RecoveryFinished
progress = resp.Progress
if recoveryMode != expectedRecoveryMode ||
recoveryFinished != expectedRecoveryFinished ||
progress != expectedProgress {
return false
}
return true
}, 15*time.Second)
if err != nil {
t.Fatalf("expected recovery mode to be %v, got %v, "+
"expected recovery finished to be %v, got %v, "+
"expected progress %v, got %v",
expectedRecoveryMode, recoveryMode,
expectedRecoveryFinished, recoveryFinished,
expectedProgress, progress,
)
}
// Lastly, shutdown this Carol so we can move on to the next
// restoration.
shutdownAndAssert(net, t, node)
}
// Restore Carol with a recovery window of 0. Since it's not in recovery
// mode, the recovery info will give a response with recoveryMode=false,
// recoveryFinished=false, and progress=0
checkInfo(false, false, 0, 0)
// Change the recovery windown to be 1 to turn on recovery mode. Since the
// current chain height is the same as the birthday height, it should
// indicate the recovery process is finished.
checkInfo(true, true, 1, 1)
// We now go ahead 5 blocks. Because the wallet's syncing process is
// controlled by a goroutine in the background, it will catch up quickly.
// This makes the recovery progress back to 1.
mineBlocks(t, net, 5, 0)
checkInfo(true, true, 1, 1)
}
// testOnchainFundRecovery checks lnd's ability to rescan for onchain outputs
// when providing a valid aezeed that owns outputs on the chain. This test
// performs multiple restorations using the same seed and various recovery
@ -14430,6 +14529,10 @@ var testsCases = []*testCase{
name: "sweep coins",
test: testSweepAllCoins,
},
{
name: "recovery info",
test: testGetRecoveryInfo,
},
{
name: "onchain fund recovery",
test: testOnchainFundRecovery,

View File

@ -844,3 +844,83 @@ func (b *BtcWallet) IsSynced() (bool, int64, error) {
return true, bestTimestamp, nil
}
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
// in recovery mode. It also returns a float64, ranging from 0 to 1,
// representing the recovery progress made so far.
//
// This is a part of the WalletController interface.
func (b *BtcWallet) GetRecoveryInfo() (bool, float64, error) {
isRecoveryMode := true
progress := float64(0)
// A zero value in RecoveryWindow indicates there is no trigger of
// recovery mode.
if b.cfg.RecoveryWindow == 0 {
isRecoveryMode = false
return isRecoveryMode, progress, nil
}
// Query the wallet's birthday block height from db.
var birthdayBlock waddrmgr.BlockStamp
err := walletdb.View(b.db, func(tx walletdb.ReadTx) error {
var err error
addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey)
birthdayBlock, _, err = b.wallet.Manager.BirthdayBlock(addrmgrNs)
if err != nil {
return err
}
return nil
})
if err != nil {
// The wallet won't start until the backend is synced, thus the birthday
// block won't be set and this particular error will be returned. We'll
// catch this error and return a progress of 0 instead.
if waddrmgr.IsError(err, waddrmgr.ErrBirthdayBlockNotSet) {
return isRecoveryMode, progress, nil
}
return isRecoveryMode, progress, err
}
// Grab the best chain state the wallet is currently aware of.
syncState := b.wallet.Manager.SyncedTo()
// Next, query the chain backend to grab the info about the tip of the
// main chain.
//
// NOTE: The actual recovery process is handled by the btcsuite/btcwallet.
// The process purposefully doesn't update the best height. It might create
// a small difference between the height queried here and the height used
// in the recovery process, ie, the bestHeight used here might be greater,
// showing the recovery being unfinished while it's actually done. However,
// during a wallet rescan after the recovery, the wallet's synced height
// will catch up and this won't be an issue.
_, bestHeight, err := b.cfg.ChainSource.GetBestBlock()
if err != nil {
return isRecoveryMode, progress, err
}
// The birthday block height might be greater than the current synced height
// in a newly restored wallet, and might be greater than the chain tip if a
// rollback happens. In that case, we will return zero progress here.
if syncState.Height < birthdayBlock.Height ||
bestHeight < birthdayBlock.Height {
return isRecoveryMode, progress, nil
}
// progress is the ratio of the [number of blocks processed] over the [total
// number of blocks] needed in a recovery mode, ranging from 0 to 1, in
// which,
// - total number of blocks is the current chain's best height minus the
// wallet's birthday height plus 1.
// - number of blocks processed is the wallet's synced height minus its
// birthday height plus 1.
// - If the wallet is born very recently, the bestHeight can be equal to
// the birthdayBlock.Height, and it will recovery instantly.
progress = float64(syncState.Height-birthdayBlock.Height+1) /
float64(bestHeight-birthdayBlock.Height+1)
return isRecoveryMode, progress, nil
}

View File

@ -285,6 +285,11 @@ type WalletController interface {
// known to the wallet, expressed in Unix epoch time
IsSynced() (bool, int64, error)
// GetRecoveryInfo returns a boolean indicating whether the wallet is
// started in recovery mode. It also returns a float64 indicating the
// recovery progress made so far.
GetRecoveryInfo() (bool, float64, error)
// Start initializes the wallet, making any necessary connections,
// starting up required goroutines etc.
Start() error

View File

@ -45,6 +45,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
var (
@ -364,6 +365,51 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
return wallet, nil
}
func testGetRecoveryInfo(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
// alice's wallet is in recovery mode
expectedRecoveryMode := true
expectedProgress := float64(1)
isRecoveryMode, progress, err := alice.GetRecoveryInfo()
require.NoError(t, err, "unable to get alice's recovery info")
require.Equal(t,
expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
)
require.Equal(t, expectedProgress, progress, "progress incorrect")
// Generate 5 blocks and check the recovery process again.
const numBlocksMined = 5
_, err = miner.Node.Generate(numBlocksMined)
require.NoError(t, err, "unable to mine blocks")
// Check the recovery process. Once synced, the progress should be 1.
err = waitForWalletSync(miner, alice)
require.NoError(t, err, "Couldn't sync Alice's wallet")
isRecoveryMode, progress, err = alice.GetRecoveryInfo()
require.NoError(t, err, "unable to get alice's recovery info")
require.Equal(t,
expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
)
require.Equal(t, expectedProgress, progress, "progress incorrect")
// bob's wallet is not in recovery mode
expectedRecoveryMode = false
expectedProgress = float64(0)
isRecoveryMode, progress, err = bob.GetRecoveryInfo()
require.NoError(t, err, "unable to get bob's recovery info")
require.Equal(t,
expectedRecoveryMode, isRecoveryMode, "recovery mode incorrect",
)
require.Equal(t, expectedProgress, progress, "progress incorrect")
}
func testDualFundingReservationWorkflow(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
@ -2712,6 +2758,10 @@ var walletTests = []walletTestCase{
name: "test sign create account",
test: testSignOutputCreateAccount,
},
{
name: "test get recovery info",
test: testGetRecoveryInfo,
},
}
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
@ -3177,6 +3227,8 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
NetParams: netParams,
ChainSource: aliceClient,
CoinType: keychain.CoinTypeTestnet,
// wallet starts in recovery mode
RecoveryWindow: 2,
}
aliceWalletController, err = walletDriver.New(aliceWalletConfig)
if err != nil {
@ -3200,6 +3252,8 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
NetParams: netParams,
ChainSource: bobClient,
CoinType: keychain.CoinTypeTestnet,
// wallet starts without recovery mode
RecoveryWindow: 0,
}
bobWalletController, err = walletDriver.New(bobWalletConfig)
if err != nil {

View File

@ -349,6 +349,9 @@ func (*mockWalletController) SubscribeTransactions() (lnwallet.TransactionSubscr
func (*mockWalletController) IsSynced() (bool, int64, error) {
return true, int64(0), nil
}
func (*mockWalletController) GetRecoveryInfo() (bool, float64, error) {
return true, float64(1), nil
}
func (*mockWalletController) Start() error {
return nil
}

View File

@ -261,6 +261,10 @@ func mainRPCServerPermissions() map[string][]bakery.Op {
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/GetRecoveryInfo": {{
Entity: "info",
Action: "read",
}},
"/lnrpc.Lightning/ListPeers": {{
Entity: "peers",
Action: "read",
@ -2481,6 +2485,27 @@ func (r *rpcServer) GetInfo(ctx context.Context,
}, nil
}
// GetRecoveryInfo returns a boolean indicating whether the wallet is started
// in recovery mode, whether the recovery is finished, and the progress made
// so far.
func (r *rpcServer) GetRecoveryInfo(ctx context.Context,
in *lnrpc.GetRecoveryInfoRequest) (*lnrpc.GetRecoveryInfoResponse, error) {
isRecoveryMode, progress, err := r.server.cc.wallet.GetRecoveryInfo()
if err != nil {
return nil, fmt.Errorf("unable to get wallet recovery info: %v", err)
}
rpcsLog.Debugf("[getrecoveryinfo] is recovery mode=%v, progress=%v",
isRecoveryMode, progress)
return &lnrpc.GetRecoveryInfoResponse{
RecoveryMode: isRecoveryMode,
RecoveryFinished: progress == 1,
Progress: progress,
}, nil
}
// ListPeers returns a verbose listing of all currently active peers.
func (r *rpcServer) ListPeers(ctx context.Context,
in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) {