diff --git a/lnd-rpc-test/src/test/scala/org/bitcoins/lnd/rpc/LndRemoteClientTest.scala b/lnd-rpc-test/src/test/scala/org/bitcoins/lnd/rpc/LndRemoteClientTest.scala new file mode 100644 index 0000000000..9537ba606e --- /dev/null +++ b/lnd-rpc-test/src/test/scala/org/bitcoins/lnd/rpc/LndRemoteClientTest.scala @@ -0,0 +1,119 @@ +package org.bitcoins.lnd.rpc + +import lnrpc.Invoice.InvoiceState +import org.bitcoins.core.currency.Satoshis +import org.bitcoins.core.protocol.ln.LnInvoice +import org.bitcoins.core.protocol.ln.currency._ +import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0 +import org.bitcoins.testkit.fixtures.RemoteLndFixture + +import scala.concurrent.Future + +class LndRemoteClientTest extends RemoteLndFixture { + + it must "get info from lnd" in { lnd => + for { + info <- lnd.getInfo + } yield assert(info.blockHeight >= 0) + } + + it must "create an invoice using sats" in { lnd => + val memo = "this is my memo" + val amount = Satoshis(1000) + + for { + invoiceResult <- lnd.addInvoice(memo, amount, 1000) + } yield { + val invoice = invoiceResult.invoice + + assert(invoice.lnTags.description.map(_.string).contains(memo)) + assert(invoice.amount.map(_.toSatoshis).contains(amount)) + } + } + + it must "create an invoice using msats" in { lnd => + val memo = "this is my memo" + val amount = MilliSatoshis(1000) + + for { + invoiceResult <- lnd.addInvoice(memo, amount, 1000) + } yield { + val invoice = invoiceResult.invoice + + assert(invoice.lnTags.description.map(_.string).contains(memo)) + assert(invoice.amount.map(_.toMSat).contains(amount)) + } + } + + it must "get an on-chain address" in { lnd => + for { + addr <- lnd.getNewAddress + } yield assert(addr.scriptPubKey.isInstanceOf[P2WPKHWitnessSPKV0]) + } + + it must "get unspent utxos" in { lnd => + for { + utxos <- lnd.listUnspent + } yield assert(utxos.isEmpty) + } + + it must "look up an invoice" in { lnd => + val memo = "this is my memo" + val amount = MilliSatoshis(1000) + + for { + invoiceResult <- lnd.addInvoice(memo, amount, 1000) + invoice = invoiceResult.invoice + _ = { + assert(invoice.lnTags.description.map(_.string).contains(memo)) + assert(invoice.amount.map(_.toMSat).contains(amount)) + } + + lookupResult <- lnd.lookupInvoice(invoiceResult.rHash) + } yield { + val lookupInvoice = LnInvoice.fromString(lookupResult.paymentRequest) + + assert(lookupInvoice.lnTags.description.map(_.string).contains(memo)) + assert(lookupInvoice.amount.map(_.toMSat).contains(amount)) + assert(lookupResult.state == InvoiceState.OPEN) + } + } + + it must "get wallet balance" in { lnd => + for { + balances <- lnd.walletBalance() + } yield { + assert(balances.balance == Satoshis.zero) + assert(balances.unconfirmedBalance == Satoshis.zero) + assert(balances.confirmedBalance == Satoshis.zero) + } + } + + it must "get channel balance" in { lnd => + for { + balances <- lnd.channelBalance() + } yield { + assert(balances.localBalance == Satoshis.zero) + assert(balances.remoteBalance == Satoshis.zero) + assert(balances.pendingOpenLocalBalance == Satoshis.zero) + assert(balances.pendingOpenRemoteBalance == Satoshis.zero) + assert(balances.unsettledLocalBalance == Satoshis.zero) + assert(balances.unsettledRemoteBalance == Satoshis.zero) + } + } + + it must "lease and release an output" in { lnd => + for { + utxos <- lnd.listUnspent + leaseFs = utxos.map(u => lnd.leaseOutput(u.outPointOpt.get, 100)) + _ <- Future.sequence(leaseFs) + leases <- lnd.listLeases() + _ = assert(leases.size == utxos.size) + + releaseFs = utxos.map(u => lnd.releaseOutput(u.outPointOpt.get)) + _ <- Future.sequence(releaseFs) + + leases <- lnd.listLeases() + } yield assert(leases.isEmpty) + } +} diff --git a/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/LndRpcClient.scala b/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/LndRpcClient.scala index 677663c8b6..ccde89c42b 100644 --- a/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/LndRpcClient.scala +++ b/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/LndRpcClient.scala @@ -31,7 +31,7 @@ import org.bitcoins.core.wallet.fee.{SatoshisPerKW, SatoshisPerVirtualByte} import org.bitcoins.crypto._ import org.bitcoins.lnd.rpc.LndRpcClient._ import org.bitcoins.lnd.rpc.LndUtils._ -import org.bitcoins.lnd.rpc.config.{LndInstance, LndInstanceLocal} +import org.bitcoins.lnd.rpc.config._ import scodec.bits._ import signrpc._ import walletrpc.{ @@ -62,12 +62,14 @@ class LndRpcClient(val instance: LndInstance, binaryOpt: Option[File] = None)( case _: LndInstanceLocal => require(binaryOpt.isDefined, s"Binary must be defined with a local instance of lnd") + case _: LndInstanceRemote => () } /** The command to start the daemon on the underlying OS */ override def cmd: String = instance match { case local: LndInstanceLocal => s"${binaryOpt.get} --lnddir=${local.datadir.toAbsolutePath}" + case _: LndInstanceRemote => "" } implicit val executionContext: ExecutionContext = system.dispatcher diff --git a/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/config/LndInstance.scala b/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/config/LndInstance.scala index ea97a0ada9..acf8097c37 100644 --- a/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/config/LndInstance.scala +++ b/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/config/LndInstance.scala @@ -13,18 +13,8 @@ import java.nio.file._ import scala.util.Properties sealed trait LndInstance { - def network: BitcoinNetwork - def listenBinding: URI - def restUri: URI def rpcUri: URI - def bitcoindAuthCredentials: PasswordBased - def bitcoindRpcUri: URI - def zmqConfig: ZmqConfig - def debugLevel: LogLevel def macaroon: String - - def datadir: Path - def certFile: File } @@ -107,3 +97,6 @@ object LndInstanceLocal config.lndInstance } } + +case class LndInstanceRemote(rpcUri: URI, macaroon: String, certFile: File) + extends LndInstance diff --git a/testkit/src/main/scala/org/bitcoins/testkit/fixtures/LndFixture.scala b/testkit/src/main/scala/org/bitcoins/testkit/fixtures/LndFixture.scala index af9152192f..b9f565f778 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/fixtures/LndFixture.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/fixtures/LndFixture.scala @@ -1,6 +1,7 @@ package org.bitcoins.testkit.fixtures import org.bitcoins.lnd.rpc.LndRpcClient +import org.bitcoins.lnd.rpc.config.LndInstanceRemote import org.bitcoins.rpc.client.common.BitcoindRpcClient import org.bitcoins.testkit.lnd._ import org.bitcoins.testkit.rpc._ @@ -62,3 +63,37 @@ trait DualLndFixture extends BitcoinSFixture with CachedBitcoindV21 { )(test) } } + +trait RemoteLndFixture extends BitcoinSFixture with CachedBitcoindV21 { + + override type FixtureParam = LndRpcClient + + override def withFixture(test: OneArgAsyncTest): FutureOutcome = { + withLnd(test) + } + + def withLnd(test: OneArgAsyncTest): FutureOutcome = { + makeDependentFixture[LndRpcClient]( + () => { + for { + bitcoind <- cachedBitcoindWithFundsF + + // start and initialize a lnd + client = LndRpcTestClient.fromSbtDownload(Some(bitcoind)) + lnd <- client.start() + + // create a remote instance and client + remoteInstance = LndInstanceRemote(lnd.instance.rpcUri, + lnd.instance.macaroon, + lnd.instance.certFile) + remoteLnd = LndRpcClient(remoteInstance) + } yield remoteLnd + }, + { lnd => + for { + _ <- lnd.stop() + } yield () + } + )(test) + } +} diff --git a/testkit/src/main/scala/org/bitcoins/testkit/lnd/LndRpcTestUtil.scala b/testkit/src/main/scala/org/bitcoins/testkit/lnd/LndRpcTestUtil.scala index 190193117a..c182d0e80d 100644 --- a/testkit/src/main/scala/org/bitcoins/testkit/lnd/LndRpcTestUtil.scala +++ b/testkit/src/main/scala/org/bitcoins/testkit/lnd/LndRpcTestUtil.scala @@ -8,7 +8,7 @@ import org.bitcoins.core.protocol.ln.node.NodeId import org.bitcoins.core.protocol.transaction.TransactionOutPoint import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte import org.bitcoins.lnd.rpc.LndRpcClient -import org.bitcoins.lnd.rpc.config.LndInstanceLocal +import org.bitcoins.lnd.rpc.config.{LndInstanceLocal, LndInstanceRemote} import org.bitcoins.rpc.client.common.{BitcoindRpcClient, BitcoindVersion} import org.bitcoins.rpc.config.{ BitcoindAuthCredentials, @@ -22,7 +22,7 @@ import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil import org.bitcoins.testkit.util.{FileUtil, TestkitBinaries} import java.io.{File, PrintWriter} -import java.net.InetSocketAddress +import java.net.{InetSocketAddress, URI} import java.nio.file.Path import scala.concurrent.duration.DurationInt import scala.concurrent.{ExecutionContext, Future} @@ -75,6 +75,7 @@ trait LndRpcTestUtil extends Logging { |debuglevel=critical |listen=127.0.0.1:$port |rpclisten=127.0.0.1:$rpcPort + |externalip=127.0.0.1 |bitcoind.rpcuser = ${bitcoindInstance.authCredentials .asInstanceOf[BitcoindAuthCredentials.PasswordBased] .username} @@ -164,9 +165,14 @@ trait LndRpcTestUtil extends Logging { val infoF = otherClient.getInfo val nodeIdF = client.getInfo.map(_.identityPubkey) val connectionF: Future[Unit] = infoF.flatMap { info => - val uri = otherClient.instance.listenBinding - client.connectPeer(NodeId(info.identityPubkey), - new InetSocketAddress(uri.getHost, uri.getPort)) + val uriF: Future[URI] = otherClient.instance match { + case local: LndInstanceLocal => Future.successful(local.listenBinding) + case _: LndInstanceRemote => + otherClient.getInfo.map(info => new URI(info.uris.head)) + } + uriF.flatMap(uri => + client.connectPeer(NodeId(info.identityPubkey), + new InetSocketAddress(uri.getHost, uri.getPort))) } def isConnected: Future[Boolean] = {