Skip to content

Commit a0385a5

Browse files
authored
Merge pull request #7197 from ffranr/add_chain_rpc_calls
lncli+chainrpc: add chainkit RPC service with endpoints GetBlock, GetBestBlock, GetBlockHash
2 parents 33a5c30 + d72d2ed commit a0385a5

23 files changed

+1771
-90
lines changed

cmd/lncli/chainrpc_active.go

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//go:build chainrpc
2+
// +build chainrpc
3+
4+
package main
5+
6+
import (
7+
"bytes"
8+
"fmt"
9+
"strconv"
10+
11+
"github.com/btcsuite/btcd/chaincfg/chainhash"
12+
"github.com/btcsuite/btcd/wire"
13+
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
14+
"github.com/urfave/cli"
15+
)
16+
17+
// chainCommands will return the set of commands to enable for chainrpc builds.
18+
func chainCommands() []cli.Command {
19+
return []cli.Command{
20+
{
21+
Name: "chain",
22+
Category: "On-chain",
23+
Usage: "Interact with the bitcoin blockchain.",
24+
Subcommands: []cli.Command{
25+
getBlockCommand,
26+
getBestBlockCommand,
27+
getBlockHashCommand,
28+
},
29+
},
30+
}
31+
}
32+
33+
func getChainClient(ctx *cli.Context) (chainrpc.ChainKitClient, func()) {
34+
conn := getClientConn(ctx, false)
35+
36+
cleanUp := func() {
37+
conn.Close()
38+
}
39+
40+
return chainrpc.NewChainKitClient(conn), cleanUp
41+
}
42+
43+
var getBlockCommand = cli.Command{
44+
Name: "getblock",
45+
Category: "On-chain",
46+
Usage: "Get block by block hash.",
47+
Description: "Returns a block given the corresponding block hash.",
48+
Flags: []cli.Flag{
49+
cli.StringFlag{
50+
Name: "hash",
51+
Usage: "the target block hash",
52+
},
53+
cli.BoolFlag{
54+
Name: "verbose",
55+
Usage: "print entire block as JSON",
56+
},
57+
},
58+
Action: actionDecorator(getBlock),
59+
}
60+
61+
func getBlock(ctx *cli.Context) error {
62+
ctxc := getContext()
63+
64+
var (
65+
args = ctx.Args()
66+
blockHashString string
67+
)
68+
69+
verbose := false
70+
if ctx.IsSet("verbose") {
71+
verbose = true
72+
}
73+
74+
switch {
75+
case ctx.IsSet("hash"):
76+
blockHashString = ctx.String("hash")
77+
78+
case args.Present():
79+
blockHashString = args.First()
80+
81+
default:
82+
return fmt.Errorf("hash argument missing")
83+
}
84+
85+
blockHash, err := chainhash.NewHashFromStr(blockHashString)
86+
if err != nil {
87+
return err
88+
}
89+
90+
client, cleanUp := getChainClient(ctx)
91+
defer cleanUp()
92+
93+
req := &chainrpc.GetBlockRequest{BlockHash: blockHash.CloneBytes()}
94+
resp, err := client.GetBlock(ctxc, req)
95+
if err != nil {
96+
return err
97+
}
98+
99+
// Convert raw block bytes into wire.MsgBlock.
100+
msgBlock := &wire.MsgBlock{}
101+
blockReader := bytes.NewReader(resp.RawBlock)
102+
err = msgBlock.Deserialize(blockReader)
103+
if err != nil {
104+
return err
105+
}
106+
107+
if verbose {
108+
printJSON(msgBlock)
109+
} else {
110+
printJSON(msgBlock.Header)
111+
}
112+
113+
return nil
114+
}
115+
116+
var getBestBlockCommand = cli.Command{
117+
Name: "getbestblock",
118+
Category: "On-chain",
119+
Usage: "Get best block.",
120+
Description: "Returns the latest block hash and height from the " +
121+
"valid most-work chain.",
122+
Action: actionDecorator(getBestBlock),
123+
}
124+
125+
func getBestBlock(ctx *cli.Context) error {
126+
ctxc := getContext()
127+
128+
client, cleanUp := getChainClient(ctx)
129+
defer cleanUp()
130+
131+
resp, err := client.GetBestBlock(ctxc, &chainrpc.GetBestBlockRequest{})
132+
if err != nil {
133+
return err
134+
}
135+
136+
// Cast gRPC block hash bytes as chain hash type.
137+
var blockHash chainhash.Hash
138+
copy(blockHash[:], resp.BlockHash)
139+
140+
printJSON(struct {
141+
BlockHash chainhash.Hash `json:"block_hash"`
142+
BlockHeight int32 `json:"block_height"`
143+
}{
144+
BlockHash: blockHash,
145+
BlockHeight: resp.BlockHeight,
146+
})
147+
148+
return nil
149+
}
150+
151+
var getBlockHashCommand = cli.Command{
152+
Name: "getblockhash",
153+
Category: "On-chain",
154+
Usage: "Get block hash by block height.",
155+
Description: "Returns the block hash from the best chain at a given " +
156+
"height.",
157+
Flags: []cli.Flag{
158+
cli.Int64Flag{
159+
Name: "height",
160+
Usage: "target block height",
161+
},
162+
},
163+
Action: actionDecorator(getBlockHash),
164+
}
165+
166+
func getBlockHash(ctx *cli.Context) error {
167+
ctxc := getContext()
168+
169+
// Display the command's help message if we do not have the expected
170+
// number of arguments/flags.
171+
if ctx.NArg()+ctx.NumFlags() != 1 {
172+
return cli.ShowCommandHelp(ctx, "getblockhash")
173+
}
174+
175+
var (
176+
args = ctx.Args()
177+
blockHeight int64
178+
)
179+
180+
switch {
181+
case ctx.IsSet("height"):
182+
blockHeight = ctx.Int64("height")
183+
184+
case args.Present():
185+
blockHeightString := args.First()
186+
187+
// Convert block height positional argument from string to
188+
// int64.
189+
var err error
190+
blockHeight, err = strconv.ParseInt(blockHeightString, 10, 64)
191+
if err != nil {
192+
return err
193+
}
194+
195+
default:
196+
return fmt.Errorf("block height argument missing")
197+
}
198+
199+
client, cleanUp := getChainClient(ctx)
200+
defer cleanUp()
201+
202+
req := &chainrpc.GetBlockHashRequest{BlockHeight: blockHeight}
203+
resp, err := client.GetBlockHash(ctxc, req)
204+
if err != nil {
205+
return err
206+
}
207+
208+
// Cast gRPC block hash bytes as chain hash type.
209+
var blockHash chainhash.Hash
210+
copy(blockHash[:], resp.BlockHash)
211+
212+
printJSON(struct {
213+
BlockHash chainhash.Hash `json:"block_hash"`
214+
}{
215+
BlockHash: blockHash,
216+
})
217+
218+
return nil
219+
}

cmd/lncli/chainrpc_default.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//go:build !chainrpc
2+
// +build !chainrpc
3+
4+
package main
5+
6+
import "github.com/urfave/cli"
7+
8+
// chainCommands will return nil for non-chainrpc builds.
9+
func chainCommands() []cli.Command {
10+
return nil
11+
}

cmd/lncli/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ func main() {
500500
app.Commands = append(app.Commands, wtclientCommands()...)
501501
app.Commands = append(app.Commands, devCommands()...)
502502
app.Commands = append(app.Commands, peersCommands()...)
503+
app.Commands = append(app.Commands, chainCommands()...)
503504

504505
if err := app.Run(os.Args); err != nil {
505506
fatal(err)

cmd/lncli/neutrino_active.go

-81
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
package main
55

66
import (
7-
"strconv"
8-
97
"github.com/lightningnetwork/lnd/lnrpc/neutrinorpc"
108
"github.com/urfave/cli"
119
)
@@ -193,42 +191,6 @@ func getBlockHeader(ctx *cli.Context) error {
193191
return nil
194192
}
195193

196-
var getBlockCommand = cli.Command{
197-
Name: "getblock",
198-
Usage: "Get a block.",
199-
Category: "Neutrino",
200-
Description: "Returns a block with a particular block hash.",
201-
ArgsUsage: "hash",
202-
Action: actionDecorator(getBlock),
203-
}
204-
205-
func getBlock(ctx *cli.Context) error {
206-
ctxc := getContext()
207-
args := ctx.Args()
208-
209-
// Display the command's help message if we do not have the expected
210-
// number of arguments/flags.
211-
if !args.Present() {
212-
return cli.ShowCommandHelp(ctx, "getblock")
213-
}
214-
215-
client, cleanUp := getNeutrinoKitClient(ctx)
216-
defer cleanUp()
217-
218-
req := &neutrinorpc.GetBlockRequest{
219-
Hash: args.First(),
220-
}
221-
222-
resp, err := client.GetBlock(ctxc, req)
223-
if err != nil {
224-
return err
225-
}
226-
227-
printRespJSON(resp)
228-
229-
return nil
230-
}
231-
232194
var getCFilterCommand = cli.Command{
233195
Name: "getcfilter",
234196
Usage: "Get a compact filter.",
@@ -263,47 +225,6 @@ func getCFilter(ctx *cli.Context) error {
263225
return nil
264226
}
265227

266-
var getBlockHashCommand = cli.Command{
267-
Name: "getblockhash",
268-
Usage: "Get a block hash.",
269-
Category: "Neutrino",
270-
Description: "Returns the header hash of a block at a given height.",
271-
ArgsUsage: "height",
272-
Action: actionDecorator(getBlockHash),
273-
}
274-
275-
func getBlockHash(ctx *cli.Context) error {
276-
ctxc := getContext()
277-
args := ctx.Args()
278-
279-
// Display the command's help message if we do not have the expected
280-
// number of arguments/flags.
281-
if !args.Present() {
282-
return cli.ShowCommandHelp(ctx, "getblockhash")
283-
}
284-
285-
client, cleanUp := getNeutrinoKitClient(ctx)
286-
defer cleanUp()
287-
288-
height, err := strconv.ParseInt(args.First(), 10, 32)
289-
if err != nil {
290-
return err
291-
}
292-
293-
req := &neutrinorpc.GetBlockHashRequest{
294-
Height: int32(height),
295-
}
296-
297-
resp, err := client.GetBlockHash(ctxc, req)
298-
if err != nil {
299-
return err
300-
}
301-
302-
printRespJSON(resp)
303-
304-
return nil
305-
}
306-
307228
// neutrinoCommands will return the set of commands to enable for neutrinorpc
308229
// builds.
309230
func neutrinoCommands() []cli.Command {
@@ -318,10 +239,8 @@ func neutrinoCommands() []cli.Command {
318239
addPeerCommand,
319240
disconnectPeerCommand,
320241
isBannedCommand,
321-
getBlockCommand,
322242
getBlockHeaderCommand,
323243
getCFilterCommand,
324-
getBlockHashCommand,
325244
},
326245
},
327246
}

docs/release-notes/release-notes-0.16.0.md

+10
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@
7373
`ListInvoiceRequest` and
7474
`ListPaymentsRequest`](https://github.com/lightningnetwork/lnd/pull/7159).
7575

76+
* [Add chainkit RPC endpoints](https://github.com/lightningnetwork/lnd/pull/7197):
77+
GetBlock, GetBestBlock, GetBlockHash. These endpoints provide access to chain
78+
block data.
79+
80+
7681
## Wallet
7782

7883
* [Allows Taproot public keys and tap scripts to be imported as watch-only
@@ -205,6 +210,11 @@ certain large transactions](https://github.com/lightningnetwork/lnd/pull/7100).
205210
* [Allow lncli to read binary PSBTs](https://github.com/lightningnetwork/lnd/pull/7122)
206211
from a file during PSBT channel funding flow to comply with [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#specification)
207212

213+
* [Add interface to chainkit RPC](https://github.com/lightningnetwork/lnd/pull/7197).
214+
This addition consists of the `chain` subcommand group: `getblock`,
215+
`getblockhash`, and `getbestblock`. These commands provide access to chain
216+
block data.
217+
208218
## Code Health
209219

210220
* [test: use `T.TempDir` to create temporary test

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/btcsuite/btcd/btcec/v2 v2.2.2
88
github.com/btcsuite/btcd/btcutil v1.1.3
99
github.com/btcsuite/btcd/btcutil/psbt v1.1.5
10-
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1
10+
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2
1111
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
1212
github.com/btcsuite/btcwallet v0.16.6-0.20221203002441-6c7480c8a46b
1313
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ github.com/btcsuite/btcd/btcutil/psbt v1.1.5/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8
9696
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
9797
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
9898
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
99+
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 h1:KdUfX2zKommPRa+PD0sWZUyXe9w277ABlgELO7H04IM=
100+
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
99101
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
100102
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
101103
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=

0 commit comments

Comments
 (0)