1
0
Fork 0
mirror of https://github.com/lightningnetwork/lnd.git synced 2025-03-13 11:09:23 +01:00

walletrpc: add new RemoveTransaction endpoint.

The RemoveTransaction endpoint removes the transaction with the
provided txid including all its descendants from the internal wallet.

We still keep watching for the address of the transation in case
the transcation is confirmed nonetheless. This command is particular
useful for neutrino backends because new bitcoind versions do not
reply with an invalid transaction error code when the tx published
fails to be included into the mempool (fullnodes do).
This commit is contained in:
ziggie 2023-11-07 13:00:59 +01:00
parent 2bc6b22a43
commit 227ac770af
No known key found for this signature in database
GPG key ID: 1AFF9C4DCED6D666
8 changed files with 910 additions and 561 deletions

File diff suppressed because it is too large Load diff

View file

@ -602,6 +602,40 @@ func local_request_WalletKit_PublishTransaction_0(ctx context.Context, marshaler
}
func request_WalletKit_RemoveTransaction_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.RemoveTransaction(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_RemoveTransaction_0(ctx context.Context, marshaler runtime.Marshaler, server WalletKitServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetTransactionRequest
var metadata runtime.ServerMetadata
newReader, berr := utilities.IOReaderFactory(req.Body)
if berr != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr)
}
if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.RemoveTransaction(ctx, &protoReq)
return msg, metadata, err
}
func request_WalletKit_SendOutputs_0(ctx context.Context, marshaler runtime.Marshaler, client WalletKitClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq SendOutputsRequest
var metadata runtime.ServerMetadata
@ -1309,6 +1343,29 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_WalletKit_RemoveTransaction_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/RemoveTransaction", runtime.WithHTTPPathPattern("/v2/wallet/removetx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_RemoveTransaction_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_RemoveTransaction_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SendOutputs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -1897,6 +1954,26 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux,
})
mux.Handle("POST", pattern_WalletKit_RemoveTransaction_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/RemoveTransaction", runtime.WithHTTPPathPattern("/v2/wallet/removetx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_RemoveTransaction_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_RemoveTransaction_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("POST", pattern_WalletKit_SendOutputs_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -2115,6 +2192,8 @@ var (
pattern_WalletKit_PublishTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, ""))
pattern_WalletKit_RemoveTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "removetx"}, ""))
pattern_WalletKit_SendOutputs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "send"}, ""))
pattern_WalletKit_EstimateFee_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"v2", "wallet", "estimatefee", "conf_target"}, ""))
@ -2169,6 +2248,8 @@ var (
forward_WalletKit_PublishTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_RemoveTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_SendOutputs_0 = runtime.ForwardResponseMessage
forward_WalletKit_EstimateFee_0 = runtime.ForwardResponseMessage

View file

@ -447,6 +447,31 @@ func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context
callback(string(respBytes), nil)
}
registry["walletrpc.WalletKit.RemoveTransaction"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
req := &GetTransactionRequest{}
err := marshaler.Unmarshal([]byte(reqJSON), req)
if err != nil {
callback("", err)
return
}
client := NewWalletKitClient(conn)
resp, err := client.RemoveTransaction(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.SendOutputs"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {

View file

@ -208,6 +208,13 @@ service WalletKit {
*/
rpc PublishTransaction (Transaction) returns (PublishResponse);
/* lncli: `wallet removetx`
RemoveTransaction attempts to remove the provided transaction from the
internal transaction store of the wallet.
*/
rpc RemoveTransaction (GetTransactionRequest)
returns (RemoveTransactionResponse);
/*
SendOutputs is similar to the existing sendmany call in Bitcoind, and
allows the caller to create a transaction that sends to several outputs at
@ -751,6 +758,7 @@ message Transaction {
*/
string label = 2;
}
message PublishResponse {
/*
If blank, then no error occurred and the transaction was successfully
@ -762,6 +770,11 @@ message PublishResponse {
string publish_error = 1;
}
message RemoveTransactionResponse {
// The status of the remove transaction operation.
string status = 1;
}
message SendOutputsRequest {
/*
The number of satoshis per kilo weight that should be used when crafting

View file

@ -506,6 +506,39 @@
]
}
},
"/v2/wallet/removetx": {
"post": {
"summary": "lncli: `wallet removetx`\nRemoveTransaction attempts to remove the provided transaction from the\ninternal transaction store of the wallet.",
"operationId": "WalletKit_RemoveTransaction",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/walletrpcRemoveTransactionResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/walletrpcGetTransactionRequest"
}
}
],
"tags": [
"WalletKit"
]
}
},
"/v2/wallet/reserve": {
"get": {
"summary": "lncli: `wallet requiredreserve`\nRequiredReserve 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.",
@ -1432,6 +1465,15 @@
}
}
},
"walletrpcGetTransactionRequest": {
"type": "object",
"properties": {
"txid": {
"type": "string",
"description": "The txid of the transaction."
}
}
},
"walletrpcImportAccountRequest": {
"type": "object",
"properties": {
@ -1775,6 +1817,15 @@
"walletrpcReleaseOutputResponse": {
"type": "object"
},
"walletrpcRemoveTransactionResponse": {
"type": "object",
"properties": {
"status": {
"type": "string",
"description": "The status of the remove transaction operation."
}
}
},
"walletrpcRequiredReserveResponse": {
"type": "object",
"properties": {

View file

@ -73,3 +73,6 @@ http:
- selector: walletrpc.WalletKit.VerifyMessageWithAddr
post: "/v2/wallet/address/verifymessage"
body: "*"
- selector: walletrpc.WalletKit.RemoveTransaction
post: "/v2/wallet/removetx"
body: "*"

View file

@ -156,6 +156,10 @@ type WalletKitClient interface {
// attempt to re-broadcast the transaction on start up, until it enters the
// chain.
PublishTransaction(ctx context.Context, in *Transaction, opts ...grpc.CallOption) (*PublishResponse, error)
// lncli: `wallet removetx`
// RemoveTransaction attempts to remove the provided transaction from the
// internal transaction store of the wallet.
RemoveTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*RemoveTransactionResponse, error)
// SendOutputs is similar to the existing sendmany call in Bitcoind, and
// allows the caller to create a transaction that sends to several outputs at
// once. This is ideal when wanting to batch create a set of transactions.
@ -420,6 +424,15 @@ func (c *walletKitClient) PublishTransaction(ctx context.Context, in *Transactio
return out, nil
}
func (c *walletKitClient) RemoveTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*RemoveTransactionResponse, error) {
out := new(RemoveTransactionResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/RemoveTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) SendOutputs(ctx context.Context, in *SendOutputsRequest, opts ...grpc.CallOption) (*SendOutputsResponse, error) {
out := new(SendOutputsResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/SendOutputs", in, out, opts...)
@ -641,6 +654,10 @@ type WalletKitServer interface {
// attempt to re-broadcast the transaction on start up, until it enters the
// chain.
PublishTransaction(context.Context, *Transaction) (*PublishResponse, error)
// lncli: `wallet removetx`
// RemoveTransaction attempts to remove the provided transaction from the
// internal transaction store of the wallet.
RemoveTransaction(context.Context, *GetTransactionRequest) (*RemoveTransactionResponse, error)
// SendOutputs is similar to the existing sendmany call in Bitcoind, and
// allows the caller to create a transaction that sends to several outputs at
// once. This is ideal when wanting to batch create a set of transactions.
@ -800,6 +817,9 @@ func (UnimplementedWalletKitServer) ImportTapscript(context.Context, *ImportTaps
func (UnimplementedWalletKitServer) PublishTransaction(context.Context, *Transaction) (*PublishResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method PublishTransaction not implemented")
}
func (UnimplementedWalletKitServer) RemoveTransaction(context.Context, *GetTransactionRequest) (*RemoveTransactionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RemoveTransaction not implemented")
}
func (UnimplementedWalletKitServer) SendOutputs(context.Context, *SendOutputsRequest) (*SendOutputsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendOutputs not implemented")
}
@ -1146,6 +1166,24 @@ func _WalletKit_PublishTransaction_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _WalletKit_RemoveTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetTransactionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletKitServer).RemoveTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/RemoveTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).RemoveTransaction(ctx, req.(*GetTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_SendOutputs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendOutputsRequest)
if err := dec(in); err != nil {
@ -1383,6 +1421,10 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{
MethodName: "PublishTransaction",
Handler: _WalletKit_PublishTransaction_Handler,
},
{
MethodName: "RemoveTransaction",
Handler: _WalletKit_RemoveTransaction_Handler,
},
{
MethodName: "SendOutputs",
Handler: _WalletKit_SendOutputs_Handler,

View file

@ -172,6 +172,10 @@ var (
Entity: "onchain",
Action: "write",
}},
"/walletrpc.WalletKit/RemoveTransaction": {{
Entity: "onchain",
Action: "write",
}},
}
// DefaultWalletKitMacFilename is the default name of the wallet kit
@ -672,6 +676,64 @@ func (w *WalletKit) PublishTransaction(ctx context.Context,
return &PublishResponse{}, nil
}
// RemoveTransaction attempts to remove the transaction and all of its
// descendants resulting from further spends of the outputs of the provided
// transaction id.
// NOTE: We do not remove the transaction from the rebroadcaster which might
// run in the background rebroadcasting not yet confirmed transactions. We do
// not have access to the rebroadcaster here nor should we. This command is not
// a way to remove transactions from the network. It is a way to shortcircuit
// wallet utxo housekeeping while transactions are still unconfirmed and we know
// that a transaction will never confirm because a replacement already pays
// higher fees.
func (w *WalletKit) RemoveTransaction(_ context.Context,
req *GetTransactionRequest) (*RemoveTransactionResponse, error) {
// If the client doesn't specify a hash, then there's nothing to
// return.
if req.Txid == "" {
return nil, fmt.Errorf("must provide a transaction hash")
}
txHash, err := chainhash.NewHashFromStr(req.Txid)
if err != nil {
return nil, err
}
// Query the tx store of our internal wallet for the specified
// transaction.
res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
if err != nil {
return nil, fmt.Errorf("transaction with txid=%v not found "+
"in the internal wallet store", txHash)
}
// Only allow unconfirmed transactions to be removed because as soon
// as a transaction is confirmed it will be evaluated by the wallet
// again and the wallet state would be updated in case the user had
// removed the transaction accidentally.
if res.NumConfirmations > 0 {
return nil, fmt.Errorf("transaction with txid=%v is already "+
"confirmed (numConfs=%d) cannot be removed", txHash,
res.NumConfirmations)
}
tx := &wire.MsgTx{}
txReader := bytes.NewReader(res.RawTx)
if err := tx.Deserialize(txReader); err != nil {
return nil, err
}
err = w.cfg.Wallet.RemoveDescendants(tx)
if err != nil {
return nil, err
}
return &RemoveTransactionResponse{
Status: "Successfully removed transaction",
}, nil
}
// SendOutputs is similar to the existing sendmany call in Bitcoind, and allows
// the caller to create a transaction that sends to several outputs at once.
// This is ideal when wanting to batch create a set of transactions.