mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-29 03:01:52 +01:00
multi: add a rpc endpoint to track the recovery process
This commit is contained in:
parent
c1ef5bb908
commit
987edc9d81
@ -1729,6 +1729,27 @@ func getInfo(ctx *cli.Context) error {
|
|||||||
return nil
|
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{
|
var pendingChannelsCommand = cli.Command{
|
||||||
Name: "pendingchannels",
|
Name: "pendingchannels",
|
||||||
Category: "Channels",
|
Category: "Channels",
|
||||||
|
@ -271,6 +271,7 @@ func main() {
|
|||||||
walletBalanceCommand,
|
walletBalanceCommand,
|
||||||
channelBalanceCommand,
|
channelBalanceCommand,
|
||||||
getInfoCommand,
|
getInfoCommand,
|
||||||
|
getRecoveryInfoCommand,
|
||||||
pendingChannelsCommand,
|
pendingChannelsCommand,
|
||||||
sendPaymentCommand,
|
sendPaymentCommand,
|
||||||
payInvoiceCommand,
|
payInvoiceCommand,
|
||||||
|
@ -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
|
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
|
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
|
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),
|
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
|
then from `lncli unlock`, it's possible to _restart_ the rescan from where it
|
||||||
|
@ -55,6 +55,8 @@ description):
|
|||||||
* Lists all available connected peers.
|
* Lists all available connected peers.
|
||||||
* GetInfo
|
* GetInfo
|
||||||
* Returns basic data concerning the daemon.
|
* Returns basic data concerning the daemon.
|
||||||
|
* GetRecoveryInfo
|
||||||
|
* Returns information about recovery process.
|
||||||
* PendingChannels
|
* PendingChannels
|
||||||
* List the number of pending (not fully confirmed) channels.
|
* List the number of pending (not fully confirmed) channels.
|
||||||
* ListChannels
|
* ListChannels
|
||||||
|
@ -41,6 +41,8 @@ http:
|
|||||||
get: "/v1/peers/subscribe"
|
get: "/v1/peers/subscribe"
|
||||||
- selector: lnrpc.Lightning.GetInfo
|
- selector: lnrpc.Lightning.GetInfo
|
||||||
get: "/v1/getinfo"
|
get: "/v1/getinfo"
|
||||||
|
- selector: lnrpc.Lightning.GetRecoveryInfo
|
||||||
|
get: "/v1/getrecoveryinfo"
|
||||||
- selector: lnrpc.Lightning.PendingChannels
|
- selector: lnrpc.Lightning.PendingChannels
|
||||||
get: "/v1/channels/pending"
|
get: "/v1/channels/pending"
|
||||||
- selector: lnrpc.Lightning.ListChannels
|
- selector: lnrpc.Lightning.ListChannels
|
||||||
|
@ -1876,8 +1876,8 @@ func (m *ForwardHtlcInterceptRequest) GetExpiry() uint32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//*
|
//*
|
||||||
//ForwardHtlcInterceptResponse enables the caller to resolve a previously hold forward.
|
//ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
|
||||||
//The caller can choose either to:
|
//forward. The caller can choose either to:
|
||||||
//- `Resume`: Execute the default behavior (usually forward).
|
//- `Resume`: Execute the default behavior (usually forward).
|
||||||
//- `Reject`: Fail the htlc backwards.
|
//- `Reject`: Fail the htlc backwards.
|
||||||
//- `Settle`: Settle this htlc with a given preimage.
|
//- `Settle`: Settle this htlc with a given preimage.
|
||||||
|
@ -98,7 +98,7 @@ service Router {
|
|||||||
rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) {
|
rpc TrackPayment (TrackPaymentRequest) returns (stream PaymentStatus) {
|
||||||
option deprecated = true;
|
option deprecated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
HtlcInterceptor dispatches a bi-directional streaming RPC in which
|
||||||
Forwarded HTLC requests are sent to the client and the client responds with
|
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
|
In case of interception, the htlc can be either settled, cancelled or
|
||||||
resumed later by using the ResolveHoldForward endpoint.
|
resumed later by using the ResolveHoldForward endpoint.
|
||||||
*/
|
*/
|
||||||
rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse) returns (stream ForwardHtlcInterceptRequest);
|
rpc HtlcInterceptor (stream ForwardHtlcInterceptResponse)
|
||||||
|
returns (stream ForwardHtlcInterceptRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
message SendPaymentRequest {
|
message SendPaymentRequest {
|
||||||
@ -589,7 +590,7 @@ message PaymentStatus {
|
|||||||
repeated lnrpc.HTLCAttempt htlcs = 4;
|
repeated lnrpc.HTLCAttempt htlcs = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CircuitKey {
|
message CircuitKey {
|
||||||
/// The id of the channel that the is part of this circuit.
|
/// The id of the channel that the is part of this circuit.
|
||||||
uint64 chan_id = 1;
|
uint64 chan_id = 1;
|
||||||
|
|
||||||
@ -618,14 +619,14 @@ message ForwardHtlcInterceptRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
ForwardHtlcInterceptResponse enables the caller to resolve a previously hold forward.
|
ForwardHtlcInterceptResponse enables the caller to resolve a previously hold
|
||||||
The caller can choose either to:
|
forward. The caller can choose either to:
|
||||||
- `Resume`: Execute the default behavior (usually forward).
|
- `Resume`: Execute the default behavior (usually forward).
|
||||||
- `Reject`: Fail the htlc backwards.
|
- `Reject`: Fail the htlc backwards.
|
||||||
- `Settle`: Settle this htlc with a given preimage.
|
- `Settle`: Settle this htlc with a given preimage.
|
||||||
*/
|
*/
|
||||||
message ForwardHtlcInterceptResponse {
|
message ForwardHtlcInterceptResponse {
|
||||||
/**
|
/**
|
||||||
The key of this forwarded htlc. It defines the incoming channel id and
|
The key of this forwarded htlc. It defines the incoming channel id and
|
||||||
the index in this channel.
|
the index in this channel.
|
||||||
*/
|
*/
|
||||||
@ -638,9 +639,8 @@ message ForwardHtlcInterceptResponse {
|
|||||||
bytes preimage = 3;
|
bytes preimage = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResolveHoldForwardAction {
|
enum ResolveHoldForwardAction {
|
||||||
SETTLE = 0;
|
SETTLE = 0;
|
||||||
FAIL = 1;
|
FAIL = 1;
|
||||||
RESUME = 2;
|
RESUME = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
1341
lnrpc/rpc.pb.go
1341
lnrpc/rpc.pb.go
File diff suppressed because it is too large
Load Diff
@ -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) {
|
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 protoReq PendingChannelsRequest
|
||||||
var metadata runtime.ServerMetadata
|
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) {
|
mux.Handle("GET", pattern_Lightning_PendingChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
defer cancel()
|
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) {
|
mux.Handle("GET", pattern_Lightning_PendingChannels_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
||||||
ctx, cancel := context.WithCancel(req.Context())
|
ctx, cancel := context.WithCancel(req.Context())
|
||||||
defer cancel()
|
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_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_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)))
|
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_GetInfo_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_Lightning_GetRecoveryInfo_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_Lightning_PendingChannels_0 = runtime.ForwardResponseMessage
|
forward_Lightning_PendingChannels_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_Lightning_ListChannels_0 = runtime.ForwardResponseMessage
|
forward_Lightning_ListChannels_0 = runtime.ForwardResponseMessage
|
||||||
|
@ -140,6 +140,14 @@ service Lightning {
|
|||||||
*/
|
*/
|
||||||
rpc GetInfo (GetInfoRequest) returns (GetInfoResponse);
|
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?
|
// TODO(roasbeef): merge with below with bool?
|
||||||
/* lncli: `pendingchannels`
|
/* lncli: `pendingchannels`
|
||||||
PendingChannels returns a list of all the channels that are currently
|
PendingChannels returns a list of all the channels that are currently
|
||||||
@ -1381,6 +1389,19 @@ message GetInfoResponse {
|
|||||||
map<uint32, Feature> features = 19;
|
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 {
|
message Chain {
|
||||||
// The blockchain the node is on (eg bitcoin, litecoin)
|
// The blockchain the node is on (eg bitcoin, litecoin)
|
||||||
string chain = 1;
|
string chain = 1;
|
||||||
|
@ -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": {
|
"/v1/graph": {
|
||||||
"get": {
|
"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.",
|
"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": {
|
"lnrpcGraphTopologyUpdate": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -817,6 +817,105 @@ const (
|
|||||||
AddrTypeNestedPubkeyHash = lnrpc.AddressType_NESTED_PUBKEY_HASH
|
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
|
// testOnchainFundRecovery checks lnd's ability to rescan for onchain outputs
|
||||||
// when providing a valid aezeed that owns outputs on the chain. This test
|
// when providing a valid aezeed that owns outputs on the chain. This test
|
||||||
// performs multiple restorations using the same seed and various recovery
|
// performs multiple restorations using the same seed and various recovery
|
||||||
@ -14430,6 +14529,10 @@ var testsCases = []*testCase{
|
|||||||
name: "sweep coins",
|
name: "sweep coins",
|
||||||
test: testSweepAllCoins,
|
test: testSweepAllCoins,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "recovery info",
|
||||||
|
test: testGetRecoveryInfo,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "onchain fund recovery",
|
name: "onchain fund recovery",
|
||||||
test: testOnchainFundRecovery,
|
test: testOnchainFundRecovery,
|
||||||
|
@ -844,3 +844,83 @@ func (b *BtcWallet) IsSynced() (bool, int64, error) {
|
|||||||
|
|
||||||
return true, bestTimestamp, nil
|
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
|
||||||
|
}
|
||||||
|
@ -285,6 +285,11 @@ type WalletController interface {
|
|||||||
// known to the wallet, expressed in Unix epoch time
|
// known to the wallet, expressed in Unix epoch time
|
||||||
IsSynced() (bool, int64, error)
|
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,
|
// Start initializes the wallet, making any necessary connections,
|
||||||
// starting up required goroutines etc.
|
// starting up required goroutines etc.
|
||||||
Start() error
|
Start() error
|
||||||
|
@ -45,6 +45,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
|
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -364,6 +365,51 @@ func createTestWallet(tempTestDir string, miningNode *rpctest.Harness,
|
|||||||
return wallet, nil
|
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,
|
func testDualFundingReservationWorkflow(miner *rpctest.Harness,
|
||||||
alice, bob *lnwallet.LightningWallet, t *testing.T) {
|
alice, bob *lnwallet.LightningWallet, t *testing.T) {
|
||||||
|
|
||||||
@ -2712,6 +2758,10 @@ var walletTests = []walletTestCase{
|
|||||||
name: "test sign create account",
|
name: "test sign create account",
|
||||||
test: testSignOutputCreateAccount,
|
test: testSignOutputCreateAccount,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "test get recovery info",
|
||||||
|
test: testGetRecoveryInfo,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
|
func clearWalletStates(a, b *lnwallet.LightningWallet) error {
|
||||||
@ -3177,6 +3227,8 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
|
|||||||
NetParams: netParams,
|
NetParams: netParams,
|
||||||
ChainSource: aliceClient,
|
ChainSource: aliceClient,
|
||||||
CoinType: keychain.CoinTypeTestnet,
|
CoinType: keychain.CoinTypeTestnet,
|
||||||
|
// wallet starts in recovery mode
|
||||||
|
RecoveryWindow: 2,
|
||||||
}
|
}
|
||||||
aliceWalletController, err = walletDriver.New(aliceWalletConfig)
|
aliceWalletController, err = walletDriver.New(aliceWalletConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -3200,6 +3252,8 @@ func runTests(t *testing.T, walletDriver *lnwallet.WalletDriver,
|
|||||||
NetParams: netParams,
|
NetParams: netParams,
|
||||||
ChainSource: bobClient,
|
ChainSource: bobClient,
|
||||||
CoinType: keychain.CoinTypeTestnet,
|
CoinType: keychain.CoinTypeTestnet,
|
||||||
|
// wallet starts without recovery mode
|
||||||
|
RecoveryWindow: 0,
|
||||||
}
|
}
|
||||||
bobWalletController, err = walletDriver.New(bobWalletConfig)
|
bobWalletController, err = walletDriver.New(bobWalletConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
3
mock.go
3
mock.go
@ -349,6 +349,9 @@ func (*mockWalletController) SubscribeTransactions() (lnwallet.TransactionSubscr
|
|||||||
func (*mockWalletController) IsSynced() (bool, int64, error) {
|
func (*mockWalletController) IsSynced() (bool, int64, error) {
|
||||||
return true, int64(0), nil
|
return true, int64(0), nil
|
||||||
}
|
}
|
||||||
|
func (*mockWalletController) GetRecoveryInfo() (bool, float64, error) {
|
||||||
|
return true, float64(1), nil
|
||||||
|
}
|
||||||
func (*mockWalletController) Start() error {
|
func (*mockWalletController) Start() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
25
rpcserver.go
25
rpcserver.go
@ -261,6 +261,10 @@ func mainRPCServerPermissions() map[string][]bakery.Op {
|
|||||||
Entity: "info",
|
Entity: "info",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
}},
|
}},
|
||||||
|
"/lnrpc.Lightning/GetRecoveryInfo": {{
|
||||||
|
Entity: "info",
|
||||||
|
Action: "read",
|
||||||
|
}},
|
||||||
"/lnrpc.Lightning/ListPeers": {{
|
"/lnrpc.Lightning/ListPeers": {{
|
||||||
Entity: "peers",
|
Entity: "peers",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
@ -2481,6 +2485,27 @@ func (r *rpcServer) GetInfo(ctx context.Context,
|
|||||||
}, nil
|
}, 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.
|
// ListPeers returns a verbose listing of all currently active peers.
|
||||||
func (r *rpcServer) ListPeers(ctx context.Context,
|
func (r *rpcServer) ListPeers(ctx context.Context,
|
||||||
in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) {
|
in *lnrpc.ListPeersRequest) (*lnrpc.ListPeersResponse, error) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user