rpcserver: add LookupHtlc call

This commit is contained in:
Joost Jager 2022-08-29 13:20:52 +02:00
parent 511fb00777
commit 3a89a84744
No known key found for this signature in database
GPG Key ID: B9A26449A5528325
11 changed files with 4637 additions and 4100 deletions

View File

@ -1473,6 +1473,12 @@ func TestFinalHtlcs(t *testing.T) {
TxPosition: 3,
}
// Test unknown htlc lookup.
const unknownHtlcID = 999
_, err = cdb.LookupFinalHtlc(chanID, unknownHtlcID)
require.ErrorIs(t, err, ErrHtlcUnknown)
// Test offchain final htlcs.
const offchainHtlcID = 1
@ -1489,9 +1495,23 @@ func TestFinalHtlcs(t *testing.T) {
}, func() {})
require.NoError(t, err)
info, err := cdb.LookupFinalHtlc(chanID, offchainHtlcID)
require.NoError(t, err)
require.True(t, info.Settled)
require.True(t, info.Offchain)
// Test onchain final htlcs.
const onchainHtlcID = 2
err = cdb.PutOnchainFinalHtlcOutcome(chanID, onchainHtlcID, true)
require.NoError(t, err)
info, err = cdb.LookupFinalHtlc(chanID, onchainHtlcID)
require.NoError(t, err)
require.True(t, info.Settled)
require.False(t, info.Offchain)
// Test unknown htlc lookup for existing channel.
_, err = cdb.LookupFinalHtlc(chanID, unknownHtlcID)
require.ErrorIs(t, err, ErrHtlcUnknown)
}

View File

@ -39,6 +39,16 @@ var (
// ErrDryRunMigrationOK signals that a migration executed successful,
// but we intentionally did not commit the result.
ErrDryRunMigrationOK = errors.New("dry run migration successful")
// ErrFinalHtlcsBucketNotFound signals that the top-level final htlcs
// bucket does not exist.
ErrFinalHtlcsBucketNotFound = errors.New("final htlcs bucket not " +
"found")
// ErrFinalChannelBucketNotFound signals that the channel bucket for
// final htlc outcomes does not exist.
ErrFinalChannelBucketNotFound = errors.New("final htlcs channel " +
"bucket not found")
)
// migration is a function which takes a prior outdated version of the database
@ -1652,6 +1662,80 @@ func (c *ChannelStateDB) FetchHistoricalChannel(outPoint *wire.OutPoint) (
return channel, nil
}
func fetchFinalHtlcsBucket(tx kvdb.RTx,
chanID lnwire.ShortChannelID) (kvdb.RBucket, error) {
finalHtlcsBucket := tx.ReadBucket(finalHtlcsBucket)
if finalHtlcsBucket == nil {
return nil, ErrFinalHtlcsBucketNotFound
}
var chanIDBytes [8]byte
byteOrder.PutUint64(chanIDBytes[:], chanID.ToUint64())
chanBucket := finalHtlcsBucket.NestedReadBucket(chanIDBytes[:])
if chanBucket == nil {
return nil, ErrFinalChannelBucketNotFound
}
return chanBucket, nil
}
var ErrHtlcUnknown = errors.New("htlc unknown")
// LookupFinalHtlc retrieves a final htlc resolution from the database. If the
// htlc has no final resolution yet, ErrHtlcUnknown is returned.
func (c *ChannelStateDB) LookupFinalHtlc(chanID lnwire.ShortChannelID,
htlcIndex uint64) (*FinalHtlcInfo, error) {
var idBytes [8]byte
byteOrder.PutUint64(idBytes[:], htlcIndex)
var settledByte byte
err := kvdb.View(c.backend, func(tx kvdb.RTx) error {
finalHtlcsBucket, err := fetchFinalHtlcsBucket(
tx, chanID,
)
switch {
case errors.Is(err, ErrFinalHtlcsBucketNotFound):
fallthrough
case errors.Is(err, ErrFinalChannelBucketNotFound):
return ErrHtlcUnknown
case err != nil:
return fmt.Errorf("cannot fetch final htlcs bucket: %w",
err)
}
value := finalHtlcsBucket.Get(idBytes[:])
if value == nil {
return ErrHtlcUnknown
}
if len(value) != 1 {
return errors.New("unexpected final htlc value length")
}
settledByte = value[0]
return nil
}, func() {
settledByte = 0
})
if err != nil {
return nil, err
}
info := FinalHtlcInfo{
Settled: settledByte&byte(FinalHtlcSettledBit) != 0,
Offchain: settledByte&byte(FinalHtlcOffchainBit) != 0,
}
return &info, nil
}
// PutOnchainFinalHtlcOutcome stores the final on-chain outcome of an htlc in
// the database.
func (c *ChannelStateDB) PutOnchainFinalHtlcOutcome(

View File

@ -51,6 +51,16 @@
[expose tlv data](https://github.com/lightningnetwork/lnd/pull/7085) that is
broadcast over the gossip network.
* [Add new HTLC notifier event and lookup
RPC](https://github.com/lightningnetwork/lnd/pull/6517) for the final
settlement of incoming HTLCs. This allows applications to wait for the HTLC to
actually disappear from all valid commitment transactions, rather than assume
that it will. With the new extensions, situations can be avoided where the
application considers an HTLC settled, but in reality the HTLC has timed out.
Final resolution data will only be available for htlcs that are resolved
after upgrading lnd.
## Wallet
* [Allows Taproot public keys and tap scripts to be imported as watch-only

File diff suppressed because it is too large Load Diff

View File

@ -2460,6 +2460,78 @@ func local_request_Lightning_ListAliases_0(ctx context.Context, marshaler runtim
}
func request_Lightning_LookupHtlc_0(ctx context.Context, marshaler runtime.Marshaler, client LightningClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LookupHtlcRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_id")
}
protoReq.ChanId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_id", err)
}
val, ok = pathParams["htlc_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "htlc_index")
}
protoReq.HtlcIndex, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "htlc_index", err)
}
msg, err := client.LookupHtlc(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Lightning_LookupHtlc_0(ctx context.Context, marshaler runtime.Marshaler, server LightningServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq LookupHtlcRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["chan_id"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "chan_id")
}
protoReq.ChanId, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "chan_id", err)
}
val, ok = pathParams["htlc_index"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "htlc_index")
}
protoReq.HtlcIndex, err = runtime.Uint64(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "htlc_index", err)
}
msg, err := server.LookupHtlc(ctx, &protoReq)
return msg, metadata, err
}
// RegisterLightningHandlerServer registers the http handlers for service Lightning to "mux".
// UnaryRPC :call LightningServer directly.
// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906.
@ -3792,6 +3864,29 @@ func RegisterLightningHandlerServer(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("GET", pattern_Lightning_LookupHtlc_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, "/lnrpc.Lightning/LookupHtlc", runtime.WithHTTPPathPattern("/v1/htlc/{chan_id}/{htlc_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Lightning_LookupHtlc_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_Lightning_LookupHtlc_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -5153,6 +5248,26 @@ func RegisterLightningHandlerClient(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("GET", pattern_Lightning_LookupHtlc_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, "/lnrpc.Lightning/LookupHtlc", runtime.WithHTTPPathPattern("/v1/htlc/{chan_id}/{htlc_index}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Lightning_LookupHtlc_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_LookupHtlc_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
@ -5288,6 +5403,8 @@ var (
pattern_Lightning_SubscribeCustomMessages_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "custommessage", "subscribe"}, ""))
pattern_Lightning_ListAliases_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "aliases", "list"}, ""))
pattern_Lightning_LookupHtlc_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 1, 0, 4, 1, 5, 3}, []string{"v1", "htlc", "chan_id", "htlc_index"}, ""))
)
var (
@ -5422,4 +5539,6 @@ var (
forward_Lightning_SubscribeCustomMessages_0 = runtime.ForwardResponseStream
forward_Lightning_ListAliases_0 = runtime.ForwardResponseMessage
forward_Lightning_LookupHtlc_0 = runtime.ForwardResponseMessage
)

View File

@ -1723,4 +1723,29 @@ func RegisterLightningJSONCallbacks(registry map[string]func(ctx context.Context
}
callback(string(respBytes), nil)
}
registry["lnrpc.Lightning.LookupHtlc"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &LookupHtlcRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewLightningClient(conn)
resp, err := client.LookupHtlc(ctx, req)
if err != nil {
callback("", err)
return
}
respBytes, err := marshaler.Marshal(resp)
if err != nil {
callback("", err)
return
}
callback(string(respBytes), nil)
}
}

View File

@ -577,6 +577,20 @@ service Lightning {
zero conf).
*/
rpc ListAliases (ListAliasesRequest) returns (ListAliasesResponse);
rpc LookupHtlc (LookupHtlcRequest) returns (LookupHtlcResponse);
}
message LookupHtlcRequest {
uint64 chan_id = 1;
uint64 htlc_index = 2;
}
message LookupHtlcResponse {
bool settled = 1;
bool offchain = 2;
}
message SubscribeCustomMessagesRequest {

View File

@ -1593,6 +1593,44 @@
]
}
},
"/v1/htlc/{chan_id}/{htlc_index}": {
"get": {
"operationId": "Lightning_LookupHtlc",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/lnrpcLookupHtlcResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "chan_id",
"in": "path",
"required": true,
"type": "string",
"format": "uint64"
},
{
"name": "htlc_index",
"in": "path",
"required": true,
"type": "string",
"format": "uint64"
}
],
"tags": [
"Lightning"
]
}
},
"/v1/invoice/{r_hash_str}": {
"get": {
"summary": "lncli: `lookupinvoice`\nLookupInvoice attempts to look up an invoice according to its payment hash.\nThe passed payment hash *must* be exactly 32 bytes, if not, an error is\nreturned.",
@ -5351,6 +5389,17 @@
}
}
},
"lnrpcLookupHtlcResponse": {
"type": "object",
"properties": {
"settled": {
"type": "boolean"
},
"offchain": {
"type": "boolean"
}
}
},
"lnrpcMPPRecord": {
"type": "object",
"properties": {

View File

@ -165,3 +165,5 @@ http:
get: "/v1/custommessage/subscribe"
- selector: lnrpc.Lightning.ListAliases
get: "/v1/aliases/list"
- selector: lnrpc.Lightning.LookupHtlc
get: "/v1/htlc/{chan_id}/{htlc_index}"

View File

@ -398,6 +398,7 @@ type LightningClient interface {
// their confirmed SCID (if it exists) and/or the base SCID (in the case of
// zero conf).
ListAliases(ctx context.Context, in *ListAliasesRequest, opts ...grpc.CallOption) (*ListAliasesResponse, error)
LookupHtlc(ctx context.Context, in *LookupHtlcRequest, opts ...grpc.CallOption) (*LookupHtlcResponse, error)
}
type lightningClient struct {
@ -1299,6 +1300,15 @@ func (c *lightningClient) ListAliases(ctx context.Context, in *ListAliasesReques
return out, nil
}
func (c *lightningClient) LookupHtlc(ctx context.Context, in *LookupHtlcRequest, opts ...grpc.CallOption) (*LookupHtlcResponse, error) {
out := new(LookupHtlcResponse)
err := c.cc.Invoke(ctx, "/lnrpc.Lightning/LookupHtlc", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// LightningServer is the server API for Lightning service.
// All implementations must embed UnimplementedLightningServer
// for forward compatibility
@ -1683,6 +1693,7 @@ type LightningServer interface {
// their confirmed SCID (if it exists) and/or the base SCID (in the case of
// zero conf).
ListAliases(context.Context, *ListAliasesRequest) (*ListAliasesResponse, error)
LookupHtlc(context.Context, *LookupHtlcRequest) (*LookupHtlcResponse, error)
mustEmbedUnimplementedLightningServer()
}
@ -1888,6 +1899,9 @@ func (UnimplementedLightningServer) SubscribeCustomMessages(*SubscribeCustomMess
func (UnimplementedLightningServer) ListAliases(context.Context, *ListAliasesRequest) (*ListAliasesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListAliases not implemented")
}
func (UnimplementedLightningServer) LookupHtlc(context.Context, *LookupHtlcRequest) (*LookupHtlcResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LookupHtlc not implemented")
}
func (UnimplementedLightningServer) mustEmbedUnimplementedLightningServer() {}
// UnsafeLightningServer may be embedded to opt out of forward compatibility for this service.
@ -3148,6 +3162,24 @@ func _Lightning_ListAliases_Handler(srv interface{}, ctx context.Context, dec fu
return interceptor(ctx, in, info, handler)
}
func _Lightning_LookupHtlc_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LookupHtlcRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(LightningServer).LookupHtlc(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/lnrpc.Lightning/LookupHtlc",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(LightningServer).LookupHtlc(ctx, req.(*LookupHtlcRequest))
}
return interceptor(ctx, in, info, handler)
}
// Lightning_ServiceDesc is the grpc.ServiceDesc for Lightning service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -3367,6 +3399,10 @@ var Lightning_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListAliases",
Handler: _Lightning_ListAliases_Handler,
},
{
MethodName: "LookupHtlc",
Handler: _Lightning_LookupHtlc_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@ -577,6 +577,10 @@ func MainRPCServerPermissions() map[string][]bakery.Op {
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/LookupHtlc": {{
Entity: "offchain",
Action: "read",
}},
"/lnrpc.Lightning/ListAliases": {{
Entity: "offchain",
Action: "read",
@ -3904,6 +3908,28 @@ func (r *rpcServer) ClosedChannels(ctx context.Context,
return resp, nil
}
// LookupHtlc retrieves a final htlc resolution from the database. If the htlc
// has no final resolution yet, a NotFound grpc status code is returned.
func (r *rpcServer) LookupHtlc(ctx context.Context,
in *lnrpc.LookupHtlcRequest) (*lnrpc.LookupHtlcResponse, error) {
chanID := lnwire.NewShortChanIDFromInt(in.ChanId)
info, err := r.server.chanStateDB.LookupFinalHtlc(chanID, in.HtlcIndex)
switch {
case errors.Is(err, channeldb.ErrHtlcUnknown):
return nil, status.Error(codes.NotFound, err.Error())
case err != nil:
return nil, err
}
return &lnrpc.LookupHtlcResponse{
Settled: info.Settled,
Offchain: info.Offchain,
}, nil
}
// ListChannels returns a description of all the open channels that this node
// is a participant in.
func (r *rpcServer) ListChannels(ctx context.Context,