Merge pull request #7654 from ErikEk/listchaintxns-add-txhash

rpc: add gettx command to walletrpc
This commit is contained in:
Oliver Gugger authored and GitHub committed 2023-12-11 10:08:42 +01:00
commit 0ec9ac7070
18 files changed
+1187 -766

No files matched your search

+3 -3
View File
@@ -1853,9 +1853,9 @@ var listChainTxnsCommand = cli.Command{
cli.Int64Flag{ cli.Int64Flag{
Name: "end_height", Name: "end_height",
Usage: "the block height until which to list " + Usage: "the block height until which to list " +
"transactions, inclusive, to get transactions " + "transactions, inclusive, to get " +
"until the chain tip, including unconfirmed, " + "transactions until the chain tip, including " +
"set this value to -1", "unconfirmed, set this value to -1",
}, },
}, },
Description: ` Description: `
+38
View File
@@ -74,6 +74,7 @@ func walletCommands() []cli.Command {
listSweepsCommand, listSweepsCommand,
labelTxCommand, labelTxCommand,
publishTxCommand, publishTxCommand,
getTxCommand,
releaseOutputCommand, releaseOutputCommand,
leaseOutputCommand, leaseOutputCommand,
listLeasesCommand, listLeasesCommand,
@@ -561,6 +562,43 @@ func publishTransaction(ctx *cli.Context) error {
return nil return nil
} }
var getTxCommand = cli.Command{
Name: "gettx",
Usage: "Returns details of a transaction.",
ArgsUsage: "txid",
Description: `
Query the transaction using the given transaction id and return its
details. An error is returned if the transaction is not found.
`,
Action: actionDecorator(getTransaction),
}
func getTransaction(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() != 1 {
return cli.ShowCommandHelp(ctx, "gettx")
}
walletClient, cleanUp := getWalletClient(ctx)
defer cleanUp()
req := &walletrpc.GetTransactionRequest{
Txid: ctx.Args().First(),
}
res, err := walletClient.GetTransaction(ctxc, req)
if err != nil {
return err
}
printRespJSON(res)
return nil
}
// utxoLease contains JSON annotations for a lease on an unspent output. // utxoLease contains JSON annotations for a lease on an unspent output.
type utxoLease struct { type utxoLease struct {
ID string `json:"id"` ID string `json:"id"`
@@ -78,6 +78,9 @@
running this new version, please make sure to upgrade your client application running this new version, please make sure to upgrade your client application
to include this new status so it can understand the RPC response properly. to include this new status so it can understand the RPC response properly.
* Adds a new rpc endpoint gettx to the walletrpc sub-server to [fetch
transaction details](https://github.com/lightningnetwork/lnd/pull/7654).
## lncli Additions ## lncli Additions
# Improvements # Improvements
@@ -175,6 +178,7 @@
* Andras Banki-Horvath * Andras Banki-Horvath
* Carla Kirk-Cohen * Carla Kirk-Cohen
* Elle Mouton * Elle Mouton
* ErikEk
* Keagan McClelland * Keagan McClelland
* Marcos Fernandez Perez * Marcos Fernandez Perez
* Matt Morehouse * Matt Morehouse
+11 -21
View File
@@ -829,32 +829,22 @@ func testSweepAllCoins(ht *lntest.HarnessTest) {
// assertTxLabel is a helper function which finds a target tx in our // assertTxLabel is a helper function which finds a target tx in our
// set of transactions and checks that it has the desired label. // set of transactions and checks that it has the desired label.
assertTxLabel := func(targetTx, label string) error { assertTxLabel := func(targetTx, label string) error {
// List all transactions relevant to our wallet, and find the // Get the transaction from our wallet so that we can check
// tx so that we can check the correct label has been set. // that the correct label has been set.
txResp := ainz.RPC.GetTransactions(nil) txResp := ainz.RPC.GetTransaction(
&walletrpc.GetTransactionRequest{
Txid: targetTx,
},
)
require.NotNilf(ht, txResp, "target tx %v not found", targetTx)
var target *lnrpc.Transaction // Make sure the labels match.
if txResp.Label == label {
// First we need to find the target tx.
for _, txn := range txResp.Transactions {
if txn.TxHash == targetTx {
target = txn
break
}
}
// If we cannot find it, return an error.
if target == nil {
return fmt.Errorf("target tx %v not found", targetTx)
}
// Otherwise, check the labels are matched.
if target.Label == label {
return nil return nil
} }
return fmt.Errorf("labels not match, want: "+ return fmt.Errorf("labels not match, want: "+
"%v, got %v", label, target.Label) "%v, got %v", label, txResp.Label)
} }
// waitTxLabel waits until the desired tx label is found or timeout. // waitTxLabel waits until the desired tx label is found or timeout.
File diff suppressed because it is too large. Load diff
+83
View File
@@ -254,6 +254,42 @@ func local_request_WalletKit_NextAddr_0(ctx context.Context, marshaler runtime.M
} }
var (
filter_WalletKit_GetTransaction_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
)
func request_WalletKit_GetTransaction_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
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_GetTransaction_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetTransaction(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_WalletKit_GetTransaction_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
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_WalletKit_GetTransaction_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetTransaction(ctx, &protoReq)
return msg, metadata, err
}
var ( var (
filter_WalletKit_ListAccounts_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} filter_WalletKit_ListAccounts_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
) )
@@ -1043,6 +1079,29 @@ func RegisterWalletKitHandlerServer(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_WalletKit_GetTransaction_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/GetTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_WalletKit_GetTransaction_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_GetTransaction_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListAccounts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_WalletKit_ListAccounts_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()
@@ -1638,6 +1697,26 @@ func RegisterWalletKitHandlerClient(ctx context.Context, mux *runtime.ServeMux,
}) })
mux.Handle("GET", pattern_WalletKit_GetTransaction_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/GetTransaction", runtime.WithHTTPPathPattern("/v2/wallet/tx"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_WalletKit_GetTransaction_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_GetTransaction_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_WalletKit_ListAccounts_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("GET", pattern_WalletKit_ListAccounts_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()
@@ -2016,6 +2095,8 @@ var (
pattern_WalletKit_NextAddr_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "address", "next"}, "")) pattern_WalletKit_NextAddr_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"v2", "wallet", "address", "next"}, ""))
pattern_WalletKit_GetTransaction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "tx"}, ""))
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_RequiredReserve_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v2", "wallet", "reserve"}, ""))
@@ -2068,6 +2149,8 @@ var (
forward_WalletKit_NextAddr_0 = runtime.ForwardResponseMessage forward_WalletKit_NextAddr_0 = runtime.ForwardResponseMessage
forward_WalletKit_GetTransaction_0 = runtime.ForwardResponseMessage
forward_WalletKit_ListAccounts_0 = runtime.ForwardResponseMessage forward_WalletKit_ListAccounts_0 = runtime.ForwardResponseMessage
forward_WalletKit_RequiredReserve_0 = runtime.ForwardResponseMessage forward_WalletKit_RequiredReserve_0 = runtime.ForwardResponseMessage
+25
View File
@@ -197,6 +197,31 @@ func RegisterWalletKitJSONCallbacks(registry map[string]func(ctx context.Context
callback(string(respBytes), nil) callback(string(respBytes), nil)
} }
registry["walletrpc.WalletKit.GetTransaction"] = 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.GetTransaction(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.ListAccounts"] = func(ctx context.Context, registry["walletrpc.WalletKit.ListAccounts"] = func(ctx context.Context,
conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { conn *grpc.ClientConn, reqJSON string, callback func(string, error)) {
+10
View File
@@ -75,6 +75,11 @@ service WalletKit {
*/ */
rpc NextAddr (AddrRequest) returns (AddrResponse); rpc NextAddr (AddrRequest) returns (AddrResponse);
/* lncli: `wallet gettx`
GetTransaction returns details for a transaction found in the wallet.
*/
rpc GetTransaction (GetTransactionRequest) returns (lnrpc.Transaction);
/* lncli: `wallet accounts list` /* lncli: `wallet accounts list`
ListAccounts retrieves all accounts belonging to the wallet by default. A ListAccounts retrieves all accounts belonging to the wallet by default. A
name and key scope filter can be provided to filter through all of the name and key scope filter can be provided to filter through all of the
@@ -556,6 +561,11 @@ message ListAddressesResponse {
repeated AccountWithAddresses account_with_addresses = 1; repeated AccountWithAddresses account_with_addresses = 1;
} }
message GetTransactionRequest {
// The txid of the transaction.
string txid = 1;
}
message SignMessageWithAddrRequest { message SignMessageWithAddrRequest {
// The message to be signed. When using REST, this field must be encoded as // The message to be signed. When using REST, this field must be encoded as
// base64. // base64.
+30
View File
@@ -663,6 +663,36 @@
} }
}, },
"/v2/wallet/tx": { "/v2/wallet/tx": {
"get": {
"summary": "lncli: `wallet gettx`\nGetTransaction returns details for a transaction found in the wallet.",
"operationId": "WalletKit_GetTransaction",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/lnrpcTransaction"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "txid",
"description": "The txid of the transaction.",
"in": "query",
"required": false,
"type": "string"
}
],
"tags": [
"WalletKit"
]
},
"post": { "post": {
"summary": "lncli: `wallet publishtx`\nPublishTransaction attempts to publish the passed transaction to the\nnetwork. Once this returns without an error, the wallet will continually\nattempt to re-broadcast the transaction on start up, until it enters the\nchain.", "summary": "lncli: `wallet publishtx`\nPublishTransaction attempts to publish the passed transaction to the\nnetwork. Once this returns without an error, the wallet will continually\nattempt to re-broadcast the transaction on start up, until it enters the\nchain.",
"operationId": "WalletKit_PublishTransaction", "operationId": "WalletKit_PublishTransaction",
+2
View File
@@ -29,6 +29,8 @@ http:
- selector: walletrpc.WalletKit.NextAddr - selector: walletrpc.WalletKit.NextAddr
post: "/v2/wallet/address/next" post: "/v2/wallet/address/next"
body: "*" body: "*"
- selector: walletrpc.WalletKit.GetTransaction
get: "/v2/wallet/tx"
- selector: walletrpc.WalletKit.PublishTransaction - selector: walletrpc.WalletKit.PublishTransaction
post: "/v2/wallet/tx" post: "/v2/wallet/tx"
body: "*" body: "*"
+41
View File
@@ -4,6 +4,7 @@ package walletrpc
import ( import (
context "context" context "context"
lnrpc "github.com/lightningnetwork/lnd/lnrpc"
signrpc "github.com/lightningnetwork/lnd/lnrpc/signrpc" signrpc "github.com/lightningnetwork/lnd/lnrpc/signrpc"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes" codes "google.golang.org/grpc/codes"
@@ -48,6 +49,9 @@ type WalletKitClient interface {
DeriveKey(ctx context.Context, in *signrpc.KeyLocator, opts ...grpc.CallOption) (*signrpc.KeyDescriptor, error) DeriveKey(ctx context.Context, in *signrpc.KeyLocator, opts ...grpc.CallOption) (*signrpc.KeyDescriptor, error)
// NextAddr returns the next unused address within the wallet. // NextAddr returns the next unused address within the wallet.
NextAddr(ctx context.Context, in *AddrRequest, opts ...grpc.CallOption) (*AddrResponse, error) NextAddr(ctx context.Context, in *AddrRequest, opts ...grpc.CallOption) (*AddrResponse, error)
// lncli: `wallet gettx`
// GetTransaction returns details for a transaction found in the wallet.
GetTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*lnrpc.Transaction, error)
// lncli: `wallet accounts list` // lncli: `wallet accounts list`
// ListAccounts retrieves all accounts belonging to the wallet by default. A // ListAccounts retrieves all accounts belonging to the wallet by default. A
// name and key scope filter can be provided to filter through all of the // name and key scope filter can be provided to filter through all of the
@@ -326,6 +330,15 @@ func (c *walletKitClient) NextAddr(ctx context.Context, in *AddrRequest, opts ..
return out, nil return out, nil
} }
func (c *walletKitClient) GetTransaction(ctx context.Context, in *GetTransactionRequest, opts ...grpc.CallOption) (*lnrpc.Transaction, error) {
out := new(lnrpc.Transaction)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/GetTransaction", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletKitClient) ListAccounts(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error) { func (c *walletKitClient) ListAccounts(ctx context.Context, in *ListAccountsRequest, opts ...grpc.CallOption) (*ListAccountsResponse, error) {
out := new(ListAccountsResponse) out := new(ListAccountsResponse)
err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ListAccounts", in, out, opts...) err := c.cc.Invoke(ctx, "/walletrpc.WalletKit/ListAccounts", in, out, opts...)
@@ -521,6 +534,9 @@ type WalletKitServer interface {
DeriveKey(context.Context, *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) DeriveKey(context.Context, *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error)
// NextAddr returns the next unused address within the wallet. // NextAddr returns the next unused address within the wallet.
NextAddr(context.Context, *AddrRequest) (*AddrResponse, error) NextAddr(context.Context, *AddrRequest) (*AddrResponse, error)
// lncli: `wallet gettx`
// GetTransaction returns details for a transaction found in the wallet.
GetTransaction(context.Context, *GetTransactionRequest) (*lnrpc.Transaction, error)
// lncli: `wallet accounts list` // lncli: `wallet accounts list`
// ListAccounts retrieves all accounts belonging to the wallet by default. A // ListAccounts retrieves all accounts belonging to the wallet by default. A
// name and key scope filter can be provided to filter through all of the // name and key scope filter can be provided to filter through all of the
@@ -754,6 +770,9 @@ func (UnimplementedWalletKitServer) DeriveKey(context.Context, *signrpc.KeyLocat
func (UnimplementedWalletKitServer) NextAddr(context.Context, *AddrRequest) (*AddrResponse, error) { func (UnimplementedWalletKitServer) NextAddr(context.Context, *AddrRequest) (*AddrResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method NextAddr not implemented") return nil, status.Errorf(codes.Unimplemented, "method NextAddr not implemented")
} }
func (UnimplementedWalletKitServer) GetTransaction(context.Context, *GetTransactionRequest) (*lnrpc.Transaction, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetTransaction not implemented")
}
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")
} }
@@ -947,6 +966,24 @@ func _WalletKit_NextAddr_Handler(srv interface{}, ctx context.Context, dec func(
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletKit_GetTransaction_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).GetTransaction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/walletrpc.WalletKit/GetTransaction",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletKitServer).GetTransaction(ctx, req.(*GetTransactionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletKit_ListAccounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletKit_ListAccounts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAccountsRequest) in := new(ListAccountsRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@@ -1306,6 +1343,10 @@ var WalletKit_ServiceDesc = grpc.ServiceDesc{
MethodName: "NextAddr", MethodName: "NextAddr",
Handler: _WalletKit_NextAddr_Handler, Handler: _WalletKit_NextAddr_Handler,
}, },
{
MethodName: "GetTransaction",
Handler: _WalletKit_GetTransaction_Handler,
},
{ {
MethodName: "ListAccounts", MethodName: "ListAccounts",
Handler: _WalletKit_ListAccounts_Handler, Handler: _WalletKit_ListAccounts_Handler,
+27
View File
@@ -80,6 +80,10 @@ var (
Entity: "address", Entity: "address",
Action: "read", Action: "read",
}}, }},
"/walletrpc.WalletKit/GetTransaction": {{
Entity: "onchain",
Action: "read",
}},
"/walletrpc.WalletKit/PublishTransaction": {{ "/walletrpc.WalletKit/PublishTransaction": {{
Entity: "onchain", Entity: "onchain",
Action: "write", Action: "write",
@@ -612,6 +616,29 @@ func (w *WalletKit) NextAddr(ctx context.Context,
}, nil }, nil
} }
// GetTransaction returns a transaction from the wallet given its hash.
func (w *WalletKit) GetTransaction(_ context.Context,
req *GetTransactionRequest) (*lnrpc.Transaction, 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
}
res, err := w.cfg.Wallet.GetTransactionDetails(txHash)
if err != nil {
return nil, err
}
return lnrpc.RPCTransaction(res), nil
}
// Attempts to publish the passed transaction to the network. Once this returns // Attempts to publish the passed transaction to the network. Once this returns
// without an error, the wallet will continually attempt to re-broadcast the // without an error, the wallet will continually attempt to re-broadcast the
// transaction on start up, until it enters the chain. // transaction on start up, until it enters the chain.
+7
View File
@@ -233,6 +233,13 @@ func (w *WalletController) PublishTransaction(tx *wire.MsgTx, _ string) error {
return nil return nil
} }
// GetTransactionDetails currently does nothing.
func (w *WalletController) GetTransactionDetails(
txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
return nil, nil
}
// LabelTransaction currently does nothing. // LabelTransaction currently does nothing.
func (w *WalletController) LabelTransaction(chainhash.Hash, string, func (w *WalletController) LabelTransaction(chainhash.Hash, string,
bool) error { bool) error {
+14
View File
@@ -3,6 +3,7 @@ package rpc
import ( import (
"context" "context"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc" "github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -196,6 +197,19 @@ func (h *HarnessRPC) PublishTransaction(
return resp return resp
} }
// GetTransaction makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) GetTransaction(
req *walletrpc.GetTransactionRequest) *lnrpc.Transaction {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.WalletKit.GetTransaction(ctxt, req)
h.NoError(err, "GetTransaction")
return resp
}
// BumpFee makes a RPC call to the node's WalletKitClient and asserts. // BumpFee makes a RPC call to the node's WalletKitClient and asserts.
func (h *HarnessRPC) BumpFee( func (h *HarnessRPC) BumpFee(
req *walletrpc.BumpFeeRequest) *walletrpc.BumpFeeResponse { req *walletrpc.BumpFeeRequest) *walletrpc.BumpFeeResponse {
+39
View File
@@ -1285,6 +1285,45 @@ func getPreviousOutpoints(wireTx *wire.MsgTx,
return previousOutpoints return previousOutpoints
} }
// GetTransactionDetails returns details of a transaction given its
// transaction hash.
func (b *BtcWallet) GetTransactionDetails(
txHash *chainhash.Hash) (*lnwallet.TransactionDetail, error) {
// Grab the best block the wallet knows of, we'll use this to calculate
// # of confirmations shortly below.
bestBlock := b.wallet.Manager.SyncedTo()
currentHeight := bestBlock.Height
tx, err := b.wallet.GetTransaction(*txHash)
if err != nil {
return nil, err
}
// For both confirmed and unconfirmed transactions, create a
// TransactionDetail which re-packages the data returned by the base
// wallet.
if tx.Confirmations > 0 {
txDetails, err := minedTransactionsToDetails(
currentHeight,
base.Block{
Transactions: []base.TransactionSummary{
tx.Summary,
},
Hash: tx.BlockHash,
Height: tx.Height,
Timestamp: tx.Summary.Timestamp},
b.netParams,
)
if err != nil {
return nil, err
}
return txDetails[0], nil
}
return unminedTransactionsToDetail(tx.Summary, b.netParams)
}
// minedTransactionsToDetails is a helper function which converts a summary // minedTransactionsToDetails is a helper function which converts a summary
// information about mined transactions to a TransactionDetail. // information about mined transactions to a TransactionDetail.
func minedTransactionsToDetails( func minedTransactionsToDetails(
+5
View File
@@ -356,6 +356,11 @@ type WalletController interface {
CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight, CreateSimpleTx(outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight,
minConfs int32, dryRun bool) (*txauthor.AuthoredTx, error) minConfs int32, dryRun bool) (*txauthor.AuthoredTx, error)
// GetTransactionDetails returns a detailed description of a transaction
// given its transaction hash.
GetTransactionDetails(txHash *chainhash.Hash) (
*TransactionDetail, error)
// ListUnspentWitness returns all unspent outputs which are version 0 // ListUnspentWitness returns all unspent outputs which are version 0
// witness programs. The 'minConfs' and 'maxConfs' parameters // witness programs. The 'minConfs' and 'maxConfs' parameters
// indicate the minimum and maximum number of confirmations an output // indicate the minimum and maximum number of confirmations an output
+7
View File
@@ -243,6 +243,13 @@ func (w *mockWalletController) PublishTransaction(tx *wire.MsgTx,
return nil return nil
} }
// GetTransactionDetails currently does nothing.
func (w *mockWalletController) GetTransactionDetails(*chainhash.Hash) (
*TransactionDetail, error) {
return nil, nil
}
// LabelTransaction currently does nothing. // LabelTransaction currently does nothing.
func (w *mockWalletController) LabelTransaction(chainhash.Hash, string, func (w *mockWalletController) LabelTransaction(chainhash.Hash, string,
bool) error { bool) error {
+28
View File
@@ -1685,6 +1685,30 @@ func newTx(t *testing.T, r *rpctest.Harness, pubKey *btcec.PublicKey,
return tx1 return tx1
} }
// testGetTransactionDetails checks that GetTransactionDetails returns the
// correct amount after a transaction has been sent from alice to bob.
func testGetTransactionDetails(r *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T) {
const txFee = int64(14500)
bobPkScript := newPkScript(t, bob, lnwallet.WitnessPubKey)
txFeeRate := chainfee.SatPerKWeight(2500)
amountSats := btcutil.Amount(btcutil.SatoshiPerBitcoin - txFee)
output := &wire.TxOut{
Value: int64(amountSats),
PkScript: bobPkScript,
}
tx := sendCoins(t, r, alice, bob, output, txFeeRate, true, 1)
txHash := tx.TxHash()
assertTxInWallet(t, alice, txHash, true)
assertTxInWallet(t, bob, txHash, true)
txDetails, err := bob.GetTransactionDetails(&txHash)
require.NoError(t, err, "unable to receive transaction details")
require.Equal(t, txDetails.Value, amountSats, "tx value")
}
// testPublishTransaction checks that PublishTransaction returns the expected // testPublishTransaction checks that PublishTransaction returns the expected
// error types in case the transaction being published conflicts with the // error types in case the transaction being published conflicts with the
// current mempool or chain. // current mempool or chain.
@@ -2794,6 +2818,10 @@ var walletTests = []walletTestCase{
name: "transaction details", name: "transaction details",
test: testListTransactionDetails, test: testListTransactionDetails,
}, },
{
name: "get transaction details",
test: testGetTransactionDetails,
},
{ {
name: "publish transaction", name: "publish transaction",
test: testPublishTransaction, test: testPublishTransaction,