github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/litrpc/lndcrpcclient.go (about)

     1  package litrpc
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"os"
     9  	"path/filepath"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/mit-dci/lit/btcutil/hdkeychain"
    14  	"github.com/mit-dci/lit/coinparam"
    15  	"github.com/mit-dci/lit/crypto/koblitz"
    16  	"github.com/mit-dci/lit/lndc"
    17  	"github.com/mit-dci/lit/lnutil"
    18  	"github.com/mit-dci/lit/logging"
    19  	"github.com/mit-dci/lit/portxo"
    20  )
    21  
    22  // LndcRpcClient can be used to remotely talk to a lit node over LNDC, making
    23  // remote control instructions over the remote control interface. It uses a
    24  // regular lndc.Conn to connect to lit over port 2448, and sends
    25  // RemoteControlRpcRequestMsg to it, and receives RemoteControlRpcResponseMsg
    26  type LndcRpcClient struct {
    27  	lnconn             *lndc.Conn
    28  	requestNonce       uint64
    29  	requestNonceMtx    sync.Mutex
    30  	responseChannelMtx sync.Mutex
    31  	responseChannels   map[uint64]chan lnutil.RemoteControlRpcResponseMsg
    32  	key                *koblitz.PrivateKey
    33  	conMtx             sync.Mutex
    34  }
    35  
    36  // LndcRpcCanConnectLocally checks if we can connect to lit using the normal
    37  // home directory. In that case, we read from the privkey.hex and use a different
    38  // derivation than the nodeID to determine the private key. This key is authorized
    39  // by default for remote control.
    40  func LndcRpcCanConnectLocally() bool {
    41  	litHomeDir := os.Getenv("HOME") + "/.lit"
    42  	return LndcRpcCanConnectLocallyWithHomeDir(litHomeDir)
    43  }
    44  
    45  // LndcRpcCanConnectLocallyWithHomeDir checks if we can connect to lit given the
    46  // home directory. In that case, we read from the privkey.hex and use a different
    47  // derivation than the nodeID to determine the private key. This key is authorized
    48  // by default for remote control.
    49  func LndcRpcCanConnectLocallyWithHomeDir(litHomeDir string) bool {
    50  	keyFilePath := filepath.Join(litHomeDir, "privkey.hex")
    51  
    52  	_, err := os.Stat(keyFilePath)
    53  	return (err == nil)
    54  }
    55  
    56  // NewLocalLndcRpcClient is an overload for NewLocalLndcRpcClientWithHomeDirAndPort
    57  // using the default home dir and port
    58  func NewLocalLndcRpcClient() (*LndcRpcClient, error) {
    59  	litHomeDir := os.Getenv("HOME") + "/.lit"
    60  	return NewLocalLndcRpcClientWithHomeDirAndPort(litHomeDir, 2448)
    61  }
    62  
    63  // NewLocalLndcRpcClientWithPort is an overload for
    64  // NewLocalLndcRpcClientWithHomeDirAndPort using the default home dir
    65  func NewLocalLndcRpcClientWithPort(port uint32) (*LndcRpcClient, error) {
    66  	litHomeDir := os.Getenv("HOME") + "/.lit"
    67  	return NewLocalLndcRpcClientWithHomeDirAndPort(litHomeDir, port)
    68  }
    69  
    70  // NewLocalLndcRpcClientWithHomeDirAndPort loads up privkey.hex, and derives
    71  // the local lit node's address from it, as well as derives the default remote
    72  // control private key from it. Then it will connect to the local lit instance.
    73  func NewLocalLndcRpcClientWithHomeDirAndPort(litHomeDir string, port uint32) (*LndcRpcClient, error) {
    74  	keyFilePath := filepath.Join(litHomeDir, "privkey.hex")
    75  	privKey, err := lnutil.ReadKeyFile(keyFilePath)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	rootPrivKey, err := hdkeychain.NewMaster(privKey[:], &coinparam.TestNet3Params)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	var kg portxo.KeyGen
    85  	kg.Depth = 5
    86  	kg.Step[0] = 44 | 1<<31
    87  	kg.Step[1] = 513 | 1<<31
    88  	kg.Step[2] = 9 | 1<<31
    89  	kg.Step[3] = 1 | 1<<31
    90  	kg.Step[4] = 0 | 1<<31
    91  	key, err := kg.DerivePrivateKey(rootPrivKey)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	kg.Step[3] = 0 | 1<<31
    97  	localIDPriv, err := kg.DerivePrivateKey(rootPrivKey)
    98  	if err != nil {
    99  		logging.Errorf(err.Error())
   100  	}
   101  	var localIDPub [33]byte
   102  	copy(localIDPub[:], localIDPriv.PubKey().SerializeCompressed())
   103  
   104  	adr := fmt.Sprintf("%s@127.0.0.1:%d", lnutil.LitAdrFromPubkey(localIDPub), port)
   105  	localIDPriv = nil
   106  
   107  	return NewLndcRpcClient(adr, key)
   108  }
   109  
   110  // NewLndcRpcClient creates a new LNDC client using the given private key, which
   111  // is arbitrary. It will then connect to the lit node specified in address, and
   112  // can then exchange remote control calls with it. In order to succesfully
   113  // execute command, the given key must be authorized in the lit instance we're
   114  // connecting to.
   115  func NewLndcRpcClient(address string, key *koblitz.PrivateKey) (*LndcRpcClient, error) {
   116  	var err error
   117  
   118  	cli := new(LndcRpcClient)
   119  	// Create a map of chan objects to receive returned responses on. These channels
   120  	// are sent to from the ReceiveLoop, and awaited in the Call method.
   121  	cli.responseChannels = make(map[uint64]chan lnutil.RemoteControlRpcResponseMsg)
   122  
   123  	//Parse the address we're connecting to
   124  	who, where := lnutil.ParseAdrString(address)
   125  
   126  	// If we couldn't deduce a URL, look it up on the tracker
   127  	if where == "" {
   128  		// TODO: Implement address lookups
   129  		err = fmt.Errorf("Tracker lookups not supported yet from LNDC proxy")
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  
   135  	// Dial a connection to the lit node
   136  	cli.lnconn, err = lndc.Dial(key, where, who, net.Dial)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	// Start the receive loop for reply messages
   142  	go cli.ReceiveLoop()
   143  	return cli, nil
   144  }
   145  
   146  func (cli *LndcRpcClient) Close() error {
   147  	return cli.lnconn.Close()
   148  }
   149  
   150  func (cli *LndcRpcClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
   151  	var err error
   152  
   153  	// Generate a local unique nonce using the mutex
   154  	cli.requestNonceMtx.Lock()
   155  	cli.requestNonce++
   156  	nonce := cli.requestNonce
   157  	cli.requestNonceMtx.Unlock()
   158  
   159  	// Create the channel to receive the reply on
   160  	cli.responseChannelMtx.Lock()
   161  	cli.responseChannels[nonce] = make(chan lnutil.RemoteControlRpcResponseMsg)
   162  	cli.responseChannelMtx.Unlock()
   163  
   164  	// Send the message in a goroutine
   165  	go func() {
   166  		msg := new(lnutil.RemoteControlRpcRequestMsg)
   167  		msg.Args, err = json.Marshal(args)
   168  		msg.Idx = nonce
   169  		msg.Method = serviceMethod
   170  
   171  		if err != nil {
   172  			logging.Fatal(err)
   173  		}
   174  
   175  		rawMsg := msg.Bytes()
   176  		cli.conMtx.Lock()
   177  		n, err := cli.lnconn.Write(rawMsg)
   178  		cli.conMtx.Unlock()
   179  		if err != nil {
   180  			logging.Fatal(err)
   181  		}
   182  
   183  		if n < len(rawMsg) {
   184  			logging.Fatal(fmt.Errorf("Did not write entire message to peer"))
   185  		}
   186  	}()
   187  
   188  	// If reply is nil the caller apparently doesn't care about the results. So we shouldn't wait for it
   189  	if reply != nil {
   190  		// If not nil, await the reply from the responseChannel for the nonce we sent out.
   191  		// the server will include the same nonce in its reply.
   192  		select {
   193  		case receivedReply := <-cli.responseChannels[nonce]:
   194  			{
   195  				if receivedReply.Error {
   196  					return errors.New(string(receivedReply.Result))
   197  				}
   198  
   199  				err = json.Unmarshal(receivedReply.Result, &reply)
   200  				return err
   201  			}
   202  		case <-time.After(time.Second * 10):
   203  			// If no reply is received within 10 seconds, we time out the request.
   204  			// TODO: We could make this configurable in the call
   205  			return errors.New("RPC call timed out")
   206  		}
   207  	}
   208  	return nil
   209  }
   210  
   211  // ReceiveLoop reads messages from the LNDC connection and check if they are
   212  // RPC responses
   213  func (cli *LndcRpcClient) ReceiveLoop() {
   214  	for {
   215  		msg := make([]byte, 1<<24)
   216  		//	log.Printf("read message from %x\n", l.RemoteLNId)
   217  		n, err := cli.lnconn.Read(msg)
   218  		if err != nil {
   219  			logging.Warnf("Error reading message from LNDC: %s\n", err.Error())
   220  			cli.lnconn.Close()
   221  			return
   222  		}
   223  		msg = msg[:n]
   224  		// We only care about RPC responses (for now)
   225  		if msg[0] == lnutil.MSGID_REMOTE_RPCRESPONSE {
   226  			// Parse the received message
   227  			response, err := lnutil.NewRemoteControlRpcResponseMsgFromBytes(msg, 0)
   228  			if err != nil {
   229  				logging.Warnf("Error while receiving RPC response: %s\n", err.Error())
   230  				cli.lnconn.Close()
   231  				return
   232  			}
   233  
   234  			// Find the response channel to send the reply to
   235  			responseChan, ok := cli.responseChannels[response.Idx]
   236  			if ok {
   237  				// Send the response, but don't depend on someone
   238  				// listening. The caller decides if he's interested in the
   239  				// reply and therefore, it could have not blocked and just
   240  				// ignore the return value.
   241  				select {
   242  				case responseChan <- response:
   243  				default:
   244  				}
   245  
   246  				// Clean up the channel to preserve memory. It's only used once.
   247  				cli.responseChannelMtx.Lock()
   248  				delete(cli.responseChannels, response.Idx)
   249  				cli.responseChannelMtx.Unlock()
   250  
   251  			} else {
   252  				logging.Errorf("Could not find response channel for index %d\n", response.Idx)
   253  			}
   254  		}
   255  	}
   256  
   257  }