Merge pull request #6592 from priyanshiiit/walletreserve

lnrpc+lncli: adds required wallet reserve rpc & cli
This commit is contained in:
Oliver Gugger authored and GitHub committed 2022-06-28 20:59:55 +02:00
commit 1ea8ea6c9e
21 files changed
+2642 -2112

No files matched your search

+48
View File
@@ -66,6 +66,7 @@ func walletCommands() []cli.Command {
listLeasesCommand, listLeasesCommand,
psbtCommand, psbtCommand,
accountsCommand, accountsCommand,
requiredReserveCommand,
}, },
}, },
} }
@@ -1083,6 +1084,53 @@ func listAccounts(ctx *cli.Context) error {
return nil return nil
} }
var requiredReserveCommand = cli.Command{
Name: "requiredreserve",
Usage: "Returns the wallet reserve.",
Description: `
Returns the minimum amount of satoshis that should be kept in the
wallet in order to fee bump anchor channels if necessary. The value
scales with the number of public anchor channels but is capped at
a maximum.
Use the flag --additional_channels to get the reserve value based
on the additional channels you would like to open.
`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "additional_channels",
Usage: "(optional) specify the additional public channels " +
"that you would like to open",
},
},
Action: actionDecorator(requiredReserve),
}
func requiredReserve(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() > 0 || ctx.NumFlags() > 1 {
return cli.ShowCommandHelp(ctx, "requiredreserve")
}
walletClient, cleanUp := getWalletClient(ctx)
defer cleanUp()
req := &walletrpc.RequiredReserveRequest{
AdditionalPublicChannels: uint32(ctx.Uint64("additional_channels")),
}
resp, err := walletClient.RequiredReserve(ctxc, req)
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var importAccountCommand = cli.Command{ var importAccountCommand = cli.Command{
Name: "import", Name: "import",
Usage: "Import an on-chain account into the wallet through its " + Usage: "Import an on-chain account into the wallet through its " +
+5 -2
View File
@@ -14,15 +14,18 @@
* [Add previous_outpoints to listchaintxns](https://github.com/lightningnetwork/lnd/pull/6321) * [Add previous_outpoints to listchaintxns](https://github.com/lightningnetwork/lnd/pull/6321)
## Bug Fixes ## Bug Fixes
* Fixed data race found in * Fixed data race found in
[`TestSerializeHTLCEntries`](https://github.com/lightningnetwork/lnd/pull/6673). [`TestSerializeHTLCEntries`](https://github.com/lightningnetwork/lnd/pull/6673).
## RPC Server
* [Add wallet reserve rpc & field in wallet balance](https://github.com/lightningnetwork/lnd/pull/6592)
# Contributors (Alphabetical Order) # Contributors (Alphabetical Order)
* Elle Mouton * Elle Mouton
* ErikEk * ErikEk
* Priyansh Rastogi * Priyansh Rastogi
* Yong Yu * Yong Yu
+1549 -1536
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -2567,6 +2567,9 @@ message WalletBalanceResponse {
// other usage. // other usage.
int64 locked_balance = 5; int64 locked_balance = 5;
// The amount of reserve required.
int64 reserved_balance_anchor_chan = 6;
// A mapping of each wallet account's name to its balance. // A mapping of each wallet account's name to its balance.
map<string, WalletAccountBalance> account_balance = 4; map<string, WalletAccountBalance> account_balance = 4;
} }
+5
View File
@@ -6646,6 +6646,11 @@
"format": "int64", "format": "int64",
"description": "The total amount of wallet UTXOs held in outputs that are locked for\nother usage." "description": "The total amount of wallet UTXOs held in outputs that are locked for\nother usage."
}, },
"reserved_balance_anchor_chan": {
"type": "string",
"format": "int64",
"description": "The amount of reserve required."
},
"account_balance": { "account_balance": {
"type": "object", "type": "object",
"additionalProperties": { "additionalProperties": {
+4
View File
@@ -67,4 +67,8 @@ type Config struct {
// ChainParams are the parameters of the wallet's backing chain. // ChainParams are the parameters of the wallet's backing chain.
ChainParams *chaincfg.Params ChainParams *chaincfg.Params
// CurrentNumAnchorChans returns the current number of non-private
// anchor channels the wallet should be ready to fee bump if needed.
CurrentNumAnchorChans func() (int, error)
} }
File diff suppressed because it is too large. Load diff
+83
View File
@@ -290,6 +290,42 @@ func local_request_WalletKit_ListAccounts_0(ctx context.Context, marshaler runti
} }
var (
filter_WalletKit_RequiredReserve_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_RequiredReserve_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RequiredReserveRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_RequiredReserve_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RequiredReserve(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_RequiredReserve_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RequiredReserveRequest
var metadata runtime.ServerMetadata
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_RequiredReserve_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RequiredReserve(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_ImportAccount_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_WalletKit_ImportAccount_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq ImportAccountRequest var protoReq ImportAccountRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
@@ -892,6 +928,29 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_WalletKit_RequiredReserve_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/walletrpc.WalletKit/RequiredReserve", runtime.WithHTTPPathPattern("/v2/wallet/reserve"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_RequiredReserve_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_RequiredReserve_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WalletKit_ImportAccount_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()
@@ -1369,6 +1428,26 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_WalletKit_RequiredReserve_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, "/walletrpc.WalletKit/RequiredReserve", runtime.WithHTTPPathPattern("/v2/wallet/reserve"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_RequiredReserve_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_WalletKit_RequiredReserve_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_ImportAccount_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("POST", pattern_WalletKit_ImportAccount_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()
@@ -1629,6 +1708,8 @@ var (
pattern_WalletKit_ListAccounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "accounts"}, "")) pattern_WalletKit_ListAccounts_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "accounts"}, ""))
pattern_WalletKit_RequiredReserve_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "reserve"}, ""))
pattern_WalletKit_ImportAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "accounts", "import"}, "")) pattern_WalletKit_ImportAccount_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "accounts", "import"}, ""))
pattern_WalletKit_ImportPublicKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "key", "import"}, "")) pattern_WalletKit_ImportPublicKey_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "key", "import"}, ""))
@@ -1671,6 +1752,8 @@ var (
forward_WalletKit_ListAccounts_0 = runtime.ForwardResponseMessage forward_WalletKit_ListAccounts_0 = runtime.ForwardResponseMessage
forward_WalletKit_RequiredReserve_0 = runtime.ForwardResponseMessage
forward_WalletKit_ImportAccount_0 = runtime.ForwardResponseMessage forward_WalletKit_ImportAccount_0 = runtime.ForwardResponseMessage
forward_WalletKit_ImportPublicKey_0 = runtime.ForwardResponseMessage forward_WalletKit_ImportPublicKey_0 = runtime.ForwardResponseMessage
+25
View File
@@ -224,6 +224,31 @@ func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context
callback(string(respBytes), nil) callback(string(respBytes), nil)
} }
registry["walletrpc.WalletKit.RequiredReserve"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &RequiredReserveRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.RequiredReserve(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.ImportAccount"] = func(ctx context.Context, registry["walletrpc.WalletKit.ImportAccount"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
+18
View File
@@ -64,6 +64,14 @@ service WalletKit {
*/ */
rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse); rpc ListAccounts (ListAccountsRequest) returns (ListAccountsResponse);
/*
RequiredReserve returns the minimum amount of satoshis that should be kept
in the wallet in order to fee bump anchor channels if necessary. The value
scales with the number of public anchor channels but is capped at a maximum.
*/
rpc RequiredReserve (RequiredReserveRequest)
returns (RequiredReserveResponse);
/* /*
ImportAccount imports an account backed by an account extended public key. ImportAccount imports an account backed by an account extended public key.
The master key fingerprint denotes the fingerprint of the root key The master key fingerprint denotes the fingerprint of the root key
@@ -400,6 +408,16 @@ message ListAccountsResponse {
repeated Account accounts = 1; repeated Account accounts = 1;
} }
message RequiredReserveRequest {
// The number of additional channels the user would like to open.
uint32 additional_public_channels = 1;
}
message RequiredReserveResponse {
// The amount of reserve required.
int64 required_reserve = 1;
}
message ImportAccountRequest { message ImportAccountRequest {
// A name to identify the account with. // A name to identify the account with.
string name = 1; string name = 1;
+43
View File
@@ -399,6 +399,39 @@
] ]
} }
}, },
"/v2/wallet/reserve": {
"get": {
"summary": "RequiredReserve returns the minimum amount of satoshis that should be kept\nin the wallet in order to fee bump anchor channels if necessary. The value\nscales with the number of public anchor channels but is capped at a maximum.",
"operationId": "WalletKit_RequiredReserve",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/walletrpcRequiredReserveResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "additional_public_channels",
"description": "The number of additional channels the user would like to open.",
"in": "query",
"required": false,
"type": "integer",
"format": "int64"
}
],
"tags": [
"WalletKit"
]
}
},
"/v2/wallet/send": { "/v2/wallet/send": {
"post": { "post": {
"summary": "SendOutputs is similar to the existing sendmany call in Bitcoind, and\nallows the caller to create a transaction that sends to several outputs at\nonce. This is ideal when wanting to batch create a set of transactions.", "summary": "SendOutputs is similar to the existing sendmany call in Bitcoind, and\nallows the caller to create a transaction that sends to several outputs at\nonce. This is ideal when wanting to batch create a set of transactions.",
@@ -1467,6 +1500,16 @@
"walletrpcReleaseOutputResponse": { "walletrpcReleaseOutputResponse": {
"type": "object" "type": "object"
}, },
"walletrpcRequiredReserveResponse": {
"type": "object",
"properties": {
"required_reserve": {
"type": "string",
"format": "int64",
"description": "The amount of reserve required."
}
}
},
"walletrpcSendOutputsRequest": { "walletrpcSendOutputsRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
+2
View File
@@ -55,6 +55,8 @@ http:
body: "*" body: "*"
- selector: walletrpc.WalletKit.ListAccounts - selector: walletrpc.WalletKit.ListAccounts
get: "/v2/wallet/accounts" get: "/v2/wallet/accounts"
- selector: walletrpc.WalletKit.RequiredReserve
get: "/v2/wallet/reserve"
- selector: walletrpc.WalletKit.ImportAccount - selector: walletrpc.WalletKit.ImportAccount
post: "/v2/wallet/accounts/import" post: "/v2/wallet/accounts/import"
body: "*" body: "*"
+44
View File
@@ -58,6 +58,11 @@ type WalletKitClient interface {
//wallet accounts and return only those matching. //wallet accounts and return only those matching.
ListAccounts(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error) ListAccounts(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error)
// //
//RequiredReserve returns the minimum amount of satoshis that should be kept
//in the wallet in order to fee bump anchor channels if necessary. The value
//scales with the number of public anchor channels but is capped at a maximum.
RequiredReserve(ctx context.Context, in *RequiredReserveRequest, opts ...grpc.CallOption) (*RequiredReserveResponse, error)
//
//ImportAccount imports an account backed by an account extended public key. //ImportAccount imports an account backed by an account extended public key.
//The master key fingerprint denotes the fingerprint of the root key //The master key fingerprint denotes the fingerprint of the root key
//corresponding to the account public key (also known as the key with //corresponding to the account public key (also known as the key with
@@ -281,6 +286,15 @@ func (c *walletKitClient) ListAccounts(ctx context.Context, in *ListAccountsRequ
return out, nil return out, nil
} }
func (c *walletKitClient) RequiredReserve(ctx context.Context, in *RequiredReserveRequest, opts ...grpc.CallOption) (*RequiredReserveResponse, error) {
out := new(RequiredReserveResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/RequiredReserve", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ImportAccount(ctx context.Context, in *ImportAccountRequest, opts ...grpc.CallOption) (*ImportAccountResponse, error) { func (c *walletKitClient) ImportAccount(ctx context.Context, in *ImportAccountRequest, opts ...grpc.CallOption) (*ImportAccountResponse, error) {
out := new(ImportAccountResponse) out := new(ImportAccountResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ImportAccount", in, out, opts...) err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ImportAccount", in, out, opts...)
@@ -432,6 +446,11 @@ type WalletKitServer interface {
//wallet accounts and return only those matching. //wallet accounts and return only those matching.
ListAccounts(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error) ListAccounts(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error)
// //
//RequiredReserve returns the minimum amount of satoshis that should be kept
//in the wallet in order to fee bump anchor channels if necessary. The value
//scales with the number of public anchor channels but is capped at a maximum.
RequiredReserve(context.Context, *RequiredReserveRequest) (*RequiredReserveResponse, error)
//
//ImportAccount imports an account backed by an account extended public key. //ImportAccount imports an account backed by an account extended public key.
//The master key fingerprint denotes the fingerprint of the root key //The master key fingerprint denotes the fingerprint of the root key
//corresponding to the account public key (also known as the key with //corresponding to the account public key (also known as the key with
@@ -604,6 +623,9 @@ func (UnimplementedWalletKitServer) NextAddr(context.Context, *AddrRequest) (*Ad
func (UnimplementedWalletKitServer) ListAccounts(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error) { func (UnimplementedWalletKitServer) ListAccounts(context.Context, *ListAccountsRequest) (*ListAccountsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAccounts not implemented") return nil, status.Errorf(codes.Unimplemented, "method ListAccounts not implemented")
} }
func (UnimplementedWalletKitServer) RequiredReserve(context.Context, *RequiredReserveRequest) (*RequiredReserveResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RequiredReserve not implemented")
}
func (UnimplementedWalletKitServer) ImportAccount(context.Context, *ImportAccountRequest) (*ImportAccountResponse, error) { func (UnimplementedWalletKitServer) ImportAccount(context.Context, *ImportAccountRequest) (*ImportAccountResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportAccount not implemented") return nil, status.Errorf(codes.Unimplemented, "method ImportAccount not implemented")
} }
@@ -797,6 +819,24 @@ func _WalletKit_ListAccounts_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletKit_RequiredReserve_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RequiredReserveRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).RequiredReserve(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/RequiredReserve",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).RequiredReserve(ctx, req.(*RequiredReserveRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ImportAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletKit_ImportAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ImportAccountRequest) in := new(ImportAccountRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -1052,6 +1092,10 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListAccounts", MethodName: "ListAccounts",
Handler: _WalletKit_ListAccounts_Handler, Handler: _WalletKit_ListAccounts_Handler,
}, },
{
MethodName: "RequiredReserve",
Handler: _WalletKit_RequiredReserve_Handler,
},
{ {
MethodName: "ImportAccount", MethodName: "ImportAccount",
Handler: _WalletKit_ImportAccount_Handler, Handler: _WalletKit_ImportAccount_Handler,
+25
View File
@@ -136,6 +136,10 @@ var (
Entity: "onchain", Entity: "onchain",
Action: "read", Action: "read",
}}, }},
"/walletrpc.WalletKit/RequiredReserve": {{
Entity: "onchain",
Action: "read",
}},
"/walletrpc.WalletKit/ImportAccount": {{ "/walletrpc.WalletKit/ImportAccount": {{
Entity: "onchain", Entity: "onchain",
Action: "write", Action: "write",
@@ -1468,6 +1472,27 @@ func (w *WalletKit) ListAccounts(ctx context.Context,
return &ListAccountsResponse{Accounts: rpcAccounts}, nil return &ListAccountsResponse{Accounts: rpcAccounts}, nil
} }
// RequiredReserve returns the minimum amount of satoshis that should be
// kept in the wallet in order to fee bump anchor channels if necessary.
// The value scales with the number of public anchor channels but is
// capped at a maximum.
func (w *WalletKit) RequiredReserve(ctx context.Context,
req *RequiredReserveRequest) (*RequiredReserveResponse, error) {
numAnchorChans, err := w.cfg.CurrentNumAnchorChans()
if err != nil {
return nil, err
}
additionalChans := req.AdditionalPublicChannels
totalChans := uint32(numAnchorChans) + additionalChans
reserved := w.cfg.Wallet.RequiredReserve(totalChans)
return &RequiredReserveResponse{
RequiredReserve: int64(reserved),
}, nil
}
// parseAddrType parses an address type from its RPC representation to a // parseAddrType parses an address type from its RPC representation to a
// *waddrmgr.AddressType. // *waddrmgr.AddressType.
func parseAddrType(addrType AddressType, func parseAddrType(addrType AddressType,
+33
View File
@@ -341,6 +341,39 @@ func testAnchorReservedValue(net *lntest.NetworkHarness, t *harnessTest) {
// The reserved value is now back in Alice's wallet. // The reserved value is now back in Alice's wallet.
aliceBalance := waitForConfirmedBalance() aliceBalance := waitForConfirmedBalance()
// The reserved value should be equal to the required reserve for anchor
// channels.
walletBalanceResp, err := alice.WalletBalance(
ctxb, &lnrpc.WalletBalanceRequest{},
)
require.NoError(t.t, err)
require.Equal(
t.t, aliceBalance, walletBalanceResp.ReservedBalanceAnchorChan,
)
additionalChannels := int64(1)
// Required reserve when additional channels are provided.
requiredReserveResp, err := alice.WalletKitClient.RequiredReserve(
ctxb, &walletrpc.RequiredReserveRequest{
AdditionalPublicChannels: uint32(additionalChannels),
},
)
require.NoError(t.t, err)
additionalReservedValue := btcutil.Amount(additionalChannels *
int64(lnwallet.AnchorChanReservedValue))
totalReserved := btcutil.Amount(aliceBalance) + additionalReservedValue
// The total reserved value should not exceed the maximum value reserved
// for anchor channels.
if totalReserved > lnwallet.MaxAnchorChanReservedValue {
totalReserved = lnwallet.MaxAnchorChanReservedValue
}
require.Equal(
t.t, int64(totalReserved), requiredReserveResp.RequiredReserve,
)
// Alice closes channel, should now be allowed to send everything to an // Alice closes channel, should now be allowed to send everything to an
// external address. // external address.
for _, chanPoint := range chanPoints { for _, chanPoint := range chanPoints {
+5
View File
@@ -105,6 +105,11 @@ func (w *WalletController) ListAccounts(string,
return nil, nil return nil, nil
} }
// RequiredReserve currently returns a dummy value.
func (w *WalletController) RequiredReserve(uint32) btcutil.Amount {
return 0
}
// ImportAccount currently returns a dummy value. // ImportAccount currently returns a dummy value.
func (w *WalletController) ImportAccount(string, *hdkeychain.ExtendedKey, func (w *WalletController) ImportAccount(string, *hdkeychain.ExtendedKey,
uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties, uint32, *waddrmgr.AddressType, bool) (*waddrmgr.AccountProperties,
+16
View File
@@ -711,6 +711,22 @@ func (b *BtcWallet) ListAccounts(name string,
return res, nil return res, nil
} }
// RequiredReserve returns the minimum amount of satoshis that should be
// kept in the wallet in order to fee bump anchor channels if necessary.
// The value scales with the number of public anchor channels but is
// capped at a maximum.
func (b *BtcWallet) RequiredReserve(
numAnchorChans uint32) btcutil.Amount {
anchorChanReservedValue := lnwallet.AnchorChanReservedValue
reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue
if reserved > lnwallet.MaxAnchorChanReservedValue {
reserved = lnwallet.MaxAnchorChanReservedValue
}
return reserved
}
// ImportAccount imports an account backed by an account extended public key. // ImportAccount imports an account backed by an account extended public key.
// The master key fingerprint denotes the fingerprint of the root key // The master key fingerprint denotes the fingerprint of the root key
// corresponding to the account public key (also known as the key with // corresponding to the account public key (also known as the key with
+6
View File
@@ -244,6 +244,12 @@ type WalletController interface {
ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties, ListAccounts(string, *waddrmgr.KeyScope) ([]*waddrmgr.AccountProperties,
error) error)
// RequiredReserve returns the minimum amount of satoshis that should be
// kept in the wallet in order to fee bump anchor channels if necessary.
// The value scales with the number of public anchor channels but is
// capped at a maximum.
RequiredReserve(uint32) btcutil.Amount
// ImportAccount imports an account backed by an account extended public // ImportAccount imports an account backed by an account extended public
// key. The master key fingerprint denotes the fingerprint of the root // key. The master key fingerprint denotes the fingerprint of the root
// key corresponding to the account public key (also known as the key // key corresponding to the account public key (also known as the key
+11 -11
View File
@@ -35,18 +35,18 @@ const (
// outside word. // outside word.
msgBufferSize = 100 msgBufferSize = 100
// anchorChanReservedValue is the amount we'll keep around in the // AnchorChanReservedValue is the amount we'll keep around in the
// wallet in case we have to fee bump anchor channels on force close. // wallet in case we have to fee bump anchor channels on force close.
// TODO(halseth): update constant to target a specific commit size at // TODO(halseth): update constant to target a specific commit size at
// set fee rate. // set fee rate.
anchorChanReservedValue = btcutil.Amount(10_000) AnchorChanReservedValue = btcutil.Amount(10_000)
// maxAnchorChanReservedValue is the maximum value we'll reserve for // MaxAnchorChanReservedValue is the maximum value we'll reserve for
// anchor channel fee bumping. We cap it at 10 times the per-channel // anchor channel fee bumping. We cap it at 10 times the per-channel
// amount such that nodes with a high number of channels don't have to // amount such that nodes with a high number of channels don't have to
// keep around a very large amount for the unlikely scenario that they // keep around a very large amount for the unlikely scenario that they
// all close at the same time. // all close at the same time.
maxAnchorChanReservedValue = 10 * anchorChanReservedValue MaxAnchorChanReservedValue = 10 * AnchorChanReservedValue
) )
var ( var (
@@ -900,7 +900,7 @@ func (l *LightningWallet) enforceNewReservedValue(fundingIntent chanfunding.Inte
return nil return nil
} }
numAnchors, err := l.currentNumAnchorChans() numAnchors, err := l.CurrentNumAnchorChans()
if err != nil { if err != nil {
return err return err
} }
@@ -919,9 +919,9 @@ func (l *LightningWallet) enforceNewReservedValue(fundingIntent chanfunding.Inte
}) })
} }
// currentNumAnchorChans returns the current number of non-private anchor // CurrentNumAnchorChans returns the current number of non-private anchor
// channels the wallet should be ready to fee bump if needed. // channels the wallet should be ready to fee bump if needed.
func (l *LightningWallet) currentNumAnchorChans() (int, error) { func (l *LightningWallet) CurrentNumAnchorChans() (int, error) {
// Count all anchor channels that are open or pending // Count all anchor channels that are open or pending
// open, or waiting close. // open, or waiting close.
chans, err := l.Cfg.Database.FetchAllChannels() chans, err := l.Cfg.Database.FetchAllChannels()
@@ -1046,9 +1046,9 @@ func (l *LightningWallet) CheckReservedValue(in []wire.OutPoint,
} }
// We reserve a given amount for each anchor channel. // We reserve a given amount for each anchor channel.
reserved := btcutil.Amount(numAnchorChans) * anchorChanReservedValue reserved := btcutil.Amount(numAnchorChans) * AnchorChanReservedValue
if reserved > maxAnchorChanReservedValue { if reserved > MaxAnchorChanReservedValue {
reserved = maxAnchorChanReservedValue reserved = MaxAnchorChanReservedValue
} }
if walletBalance < reserved { if walletBalance < reserved {
@@ -1069,7 +1069,7 @@ func (l *LightningWallet) CheckReservedValue(in []wire.OutPoint,
func (l *LightningWallet) CheckReservedValueTx(req CheckReservedValueTxReq) ( func (l *LightningWallet) CheckReservedValueTx(req CheckReservedValueTxReq) (
btcutil.Amount, error) { btcutil.Amount, error) {
numAnchors, err := l.currentNumAnchorChans() numAnchors, err := l.CurrentNumAnchorChans()
if err != nil { if err != nil {
return 0, err return 0, err
} }
+17 -5
View File
@@ -3136,16 +3136,28 @@ func (r *rpcServer) WalletBalance(ctx context.Context,
lockedBalance += utxoInfo.Value lockedBalance += utxoInfo.Value
} }
// Get the current number of non-private anchor channels.
currentNumAnchorChans, err := r.server.cc.Wallet.CurrentNumAnchorChans()
if err != nil {
return nil, err
}
// Get the required reserve for the wallet.
requiredReserve := r.server.cc.Wallet.RequiredReserve(
uint32(currentNumAnchorChans),
)
rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+ rpcsLog.Debugf("[walletbalance] Total balance=%v (confirmed=%v, "+
"unconfirmed=%v)", totalBalance, confirmedBalance, "unconfirmed=%v)", totalBalance, confirmedBalance,
unconfirmedBalance) unconfirmedBalance)
return &lnrpc.WalletBalanceResponse{ return &lnrpc.WalletBalanceResponse{
TotalBalance: int64(totalBalance), TotalBalance: int64(totalBalance),
ConfirmedBalance: int64(confirmedBalance), ConfirmedBalance: int64(confirmedBalance),
UnconfirmedBalance: int64(unconfirmedBalance), UnconfirmedBalance: int64(unconfirmedBalance),
LockedBalance: int64(lockedBalance), LockedBalance: int64(lockedBalance),
AccountBalance: rpcAccountBalances, ReservedBalanceAnchorChan: int64(requiredReserve),
AccountBalance: rpcAccountBalances,
}, nil }, nil
} }
+3
View File
@@ -193,6 +193,9 @@ func (s *subRPCServerConfigs) PopulateDependencies(cfg *Config,
subCfgValue.FieldByName("ChainParams").Set( subCfgValue.FieldByName("ChainParams").Set(
reflect.ValueOf(activeNetParams), reflect.ValueOf(activeNetParams),
) )
subCfgValue.FieldByName("CurrentNumAnchorChans").Set(
reflect.ValueOf(cc.Wallet.CurrentNumAnchorChans),
)
case *autopilotrpc.Config: case *autopilotrpc.Config:
subCfgValue := extractReflectValue(subCfg) subCfgValue := extractReflectValue(subCfg)