@@ -2,6 +2,7 @@ package lndclient
22
33import (
44 "context"
5+ "errors"
56 "fmt"
67 "net"
78 "path/filepath"
@@ -11,11 +12,46 @@ import (
1112 "github.com/btcsuite/btcutil"
1213 "github.com/lightninglabs/loop/swap"
1314 "github.com/lightningnetwork/lnd/lncfg"
15+ "github.com/lightningnetwork/lnd/lnrpc/verrpc"
1416 "google.golang.org/grpc"
17+ "google.golang.org/grpc/codes"
1518 "google.golang.org/grpc/credentials"
19+ "google.golang.org/grpc/status"
1620)
1721
18- var rpcTimeout = 30 * time .Second
22+ var (
23+ rpcTimeout = 30 * time .Second
24+
25+ // minimalCompatibleVersion is the minimum version and build tags
26+ // required in lnd to get all functionality implemented in lndclient.
27+ // Users can provide their own, specific version if needed. If only a
28+ // subset of the lndclient functionality is needed, the required build
29+ // tags can be adjusted accordingly. This default will be used as a fall
30+ // back version if none is specified in the configuration.
31+ minimalCompatibleVersion = & verrpc.Version {
32+ AppMajor : 0 ,
33+ AppMinor : 10 ,
34+ AppPatch : 0 ,
35+ BuildTags : []string {
36+ "signrpc" , "walletrpc" , "chainrpc" , "invoicesrpc" ,
37+ },
38+ }
39+
40+ // ErrVersionCheckNotImplemented is the error that is returned if the
41+ // version RPC is not implemented in lnd. This means the version of lnd
42+ // is lower than v0.10.0-beta.
43+ ErrVersionCheckNotImplemented = errors .New ("version check not " +
44+ "implemented, need minimum lnd version of v0.10.0-beta" )
45+
46+ // ErrVersionIncompatible is the error that is returned if the connected
47+ // lnd instance is not supported.
48+ ErrVersionIncompatible = errors .New ("version incompatible" )
49+
50+ // ErrBuildTagsMissing is the error that is returned if the
51+ // connected lnd instance does not have all built tags activated that
52+ // are required.
53+ ErrBuildTagsMissing = errors .New ("build tags missing" )
54+ )
1955
2056// LndServicesConfig holds all configuration settings that are needed to connect
2157// to an lnd node.
@@ -33,6 +69,12 @@ type LndServicesConfig struct {
3369 // TLSPath is the path to lnd's TLS certificate file.
3470 TLSPath string
3571
72+ // CheckVersion is the minimum version the connected lnd node needs to
73+ // be in order to be compatible. The node will be checked against this
74+ // when connecting. If no version is supplied, the default minimum
75+ // version will be used.
76+ CheckVersion * verrpc.Version
77+
3678 // Dialer is an optional dial function that can be passed in if the
3779 // default lncfg.ClientAddressDialer should not be used.
3880 Dialer DialerFunc
@@ -54,6 +96,7 @@ type LndServices struct {
5496 ChainParams * chaincfg.Params
5597 NodeAlias string
5698 NodePubkey [33 ]byte
99+ Version * verrpc.Version
57100
58101 macaroons * macaroonPouch
59102}
@@ -74,6 +117,11 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) {
74117 cfg .Dialer = lncfg .ClientAddressDialer (defaultRPCPort )
75118 }
76119
120+ // Fall back to minimal compatible version if none if specified.
121+ if cfg .CheckVersion == nil {
122+ cfg .CheckVersion = minimalCompatibleVersion
123+ }
124+
77125 // Based on the network, if the macaroon directory isn't set, then
78126 // we'll use the expected default locations.
79127 macaroonDir := cfg .MacaroonDir
@@ -135,8 +183,8 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) {
135183 if err != nil {
136184 return nil , err
137185 }
138- nodeAlias , nodeKey , err := checkLndCompatibility (
139- conn , chainParams , readonlyMac , cfg .Network ,
186+ nodeAlias , nodeKey , version , err := checkLndCompatibility (
187+ conn , chainParams , readonlyMac , cfg .Network , cfg . CheckVersion ,
140188 )
141189 if err != nil {
142190 return nil , err
@@ -195,6 +243,7 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) {
195243 ChainParams : chainParams ,
196244 NodeAlias : nodeAlias ,
197245 NodePubkey : nodeKey ,
246+ Version : version ,
198247 macaroons : macaroons ,
199248 },
200249 cleanup : cleanup ,
@@ -214,25 +263,36 @@ func (s *GrpcLndServices) Close() {
214263}
215264
216265// checkLndCompatibility makes sure the connected lnd instance is running on the
217- // correct network.
266+ // correct network, has the version RPC implemented, is the correct minimal
267+ // version and supports all required build tags/subservers.
218268func checkLndCompatibility (conn * grpc.ClientConn , chainParams * chaincfg.Params ,
219- readonlyMac serializedMacaroon , network string ) ( string , [ 33 ] byte ,
220- error ) {
269+ readonlyMac serializedMacaroon , network string ,
270+ minVersion * verrpc. Version ) ( string , [ 33 ] byte , * verrpc. Version , error ) {
221271
222272 // onErr is a closure that simplifies returning multiple values in the
223273 // error case.
224- onErr := func (err error ) (string , [33 ]byte , error ) {
274+ onErr := func (err error ) (string , [33 ]byte , * verrpc. Version , error ) {
225275 closeErr := conn .Close ()
226276 if closeErr != nil {
227277 log .Errorf ("Error closing lnd connection: %v" , closeErr )
228278 }
229279
230- return "" , [33 ]byte {}, err
280+ // Make static error messages a bit less cryptic by adding the
281+ // version or build tag that we expect.
282+ newErr := fmt .Errorf ("lnd compatibility check failed: %v" , err )
283+ if err == ErrVersionIncompatible || err == ErrBuildTagsMissing {
284+ newErr = fmt .Errorf ("error checking connected lnd " +
285+ "version. at least version \" %s\" is " +
286+ "required" , VersionString (minVersion ))
287+ }
288+
289+ return "" , [33 ]byte {}, nil , newErr
231290 }
232291
233- // We use our own client with a readonly macaroon here, because we know
292+ // We use our own clients with a readonly macaroon here, because we know
234293 // that's all we need for the checks.
235294 lightningClient := newLightningClient (conn , chainParams , readonlyMac )
295+ versionerClient := newVersionerClient (conn , readonlyMac )
236296
237297 // With our readonly macaroon obtained, we'll ensure that the network
238298 // for lnd matches our expected network.
@@ -247,9 +307,101 @@ func checkLndCompatibility(conn *grpc.ClientConn, chainParams *chaincfg.Params,
247307 return onErr (err )
248308 }
249309
310+ // Now let's also check the version of the connected lnd node.
311+ version , err := checkVersionCompatibility (versionerClient , minVersion )
312+ if err != nil {
313+ return onErr (err )
314+ }
315+
250316 // Return the static part of the info we just queried from the node so
251317 // it can be cached for later use.
252- return info .Alias , info .IdentityPubkey , nil
318+ return info .Alias , info .IdentityPubkey , version , nil
319+ }
320+
321+ // checkVersionCompatibility makes sure the connected lnd node has the correct
322+ // version and required build tags enabled.
323+ //
324+ // NOTE: This check will **never** return a non-nil error for a version of
325+ // lnd < 0.10.0 because any version previous to 0.10.0 doesn't have the version
326+ // endpoint implemented!
327+ func checkVersionCompatibility (client VersionerClient ,
328+ expected * verrpc.Version ) (* verrpc.Version , error ) {
329+
330+ // First, test that the version RPC is even implemented.
331+ version , err := client .GetVersion (context .Background ())
332+ if err != nil {
333+ // The version service has only been added in lnd v0.10.0. If
334+ // we get an unimplemented error, it means the lnd version is
335+ // definitely older than that.
336+ s , ok := status .FromError (err )
337+ if ok && s .Code () == codes .Unimplemented {
338+ return nil , ErrVersionCheckNotImplemented
339+ }
340+ return nil , fmt .Errorf ("GetVersion error: %v" , err )
341+ }
342+
343+ // Now check the version and make sure all required build tags are set.
344+ err = assertVersionCompatible (version , expected )
345+ if err != nil {
346+ return nil , err
347+ }
348+ err = assertBuildTagsEnabled (version , expected .BuildTags )
349+ if err != nil {
350+ return nil , err
351+ }
352+
353+ // All check positive, version is fully compatible.
354+ return version , nil
355+ }
356+
357+ // assertVersionCompatible makes sure the detected lnd version is compatible
358+ // with our current version requirements.
359+ func assertVersionCompatible (actual * verrpc.Version ,
360+ expected * verrpc.Version ) error {
361+
362+ // We need to check the versions parts sequentially as they are
363+ // hierarchical.
364+ if actual .AppMajor != expected .AppMajor {
365+ if actual .AppMajor > expected .AppMajor {
366+ return nil
367+ }
368+ return ErrVersionIncompatible
369+ }
370+
371+ if actual .AppMinor != expected .AppMinor {
372+ if actual .AppMinor > expected .AppMinor {
373+ return nil
374+ }
375+ return ErrVersionIncompatible
376+ }
377+
378+ if actual .AppPatch != expected .AppPatch {
379+ if actual .AppPatch > expected .AppPatch {
380+ return nil
381+ }
382+ return ErrVersionIncompatible
383+ }
384+
385+ // The actual version and expected version are identical.
386+ return nil
387+ }
388+
389+ // assertBuildTagsEnabled makes sure all required build tags are set.
390+ func assertBuildTagsEnabled (actual * verrpc.Version ,
391+ requiredTags []string ) error {
392+
393+ tagMap := make (map [string ]struct {})
394+ for _ , tag := range actual .BuildTags {
395+ tagMap [tag ] = struct {}{}
396+ }
397+ for _ , required := range requiredTags {
398+ if _ , ok := tagMap [required ]; ! ok {
399+ return ErrBuildTagsMissing
400+ }
401+ }
402+
403+ // All tags found.
404+ return nil
253405}
254406
255407var (
0 commit comments