mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
Somewhat dirty standalone server and CLI binary (#558)
* PoC bitcoin-s-cli * Add CLI, Server sbt projects, remove Ammonite In this commit we set up sbt configuration for CLI, Server (in-work-name) and corresponding test projects. We also remove Ammonite shell from sbt, as that isn't really being used. bloop console offers the same functionality way more ergonimic. * Move BitcoinSAppConfig into new server project Server project depends on node, chain wand wallet so this is a good time for introducing this class into main sources. We also introduce BitcoinSTestAppConfig in testkit, to replace the functionality in BitcoinSAppConfig related to tests. * Type chain in blockchainresult * MVP server setup for node, chain and wallet * Extremely dirty CLI for interacting with server * initial attempt at mimicking Bitcoin Core API * WalletStorage: add method for checking for seed existance * Check for seed existance on wallet startup * Fix bug where MnemonicNotFound was not an error * Segregate confirmed and unconfirmed balance methods * Add error handling, improve formatting of CLI output * Tweak build Bump Sttp version, downgrade to uPickle 2.11 compat, skip publish in cli-test and server-test * Add CLI, server and picklers to root project
This commit is contained in:
parent
2632e1a628
commit
30e6d7030f
@ -1,3 +1,4 @@
|
|||||||
|
version = "1.5.1"
|
||||||
# See Documentation at https://scalameta.org/scalafmt/#Configuration
|
# See Documentation at https://scalameta.org/scalafmt/#Configuration
|
||||||
maxColumn=80
|
maxColumn=80
|
||||||
docstrings=ScalaDoc
|
docstrings=ScalaDoc
|
||||||
|
3
app/cli-test/cli-test.sbt
Normal file
3
app/cli-test/cli-test.sbt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
name := "bitcoin-s-cli-test"
|
||||||
|
|
||||||
|
publish / skip := true
|
9
app/cli/cli.sbt
Normal file
9
app/cli/cli.sbt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
name := "bitcoin-s-cli"
|
||||||
|
|
||||||
|
libraryDependencies ++= Deps.cli
|
||||||
|
|
||||||
|
publish / skip := true
|
||||||
|
|
||||||
|
graalVMNativeImageOptions += "-H:EnableURLProtocols=http"
|
||||||
|
|
||||||
|
enablePlugins(JavaAppPackaging, GraalVMNativeImagePlugin)
|
238
app/cli/src/main/scala/org/bitcoins/cli/Cli.scala
Normal file
238
app/cli/src/main/scala/org/bitcoins/cli/Cli.scala
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
package org.bitcoins.cli
|
||||||
|
|
||||||
|
import org.bitcoins.picklers._
|
||||||
|
|
||||||
|
import scopt.OParser
|
||||||
|
import org.bitcoins.core.config.NetworkParameters
|
||||||
|
|
||||||
|
import upickle.{default => up}
|
||||||
|
|
||||||
|
import CliReaders._
|
||||||
|
import org.bitcoins.core.protocol._
|
||||||
|
import org.bitcoins.core.currency._
|
||||||
|
import org.bitcoins.cli.CliCommand.GetBalance
|
||||||
|
import org.bitcoins.cli.CliCommand.GetNewAddress
|
||||||
|
import org.bitcoins.cli.CliCommand.SendToAddress
|
||||||
|
import org.bitcoins.cli.CliCommand.GetBlockCount
|
||||||
|
import org.bitcoins.cli.CliCommand.GetBestBlockHash
|
||||||
|
import org.bitcoins.cli.CliCommand.GetPeers
|
||||||
|
import org.bitcoins.cli.CliCommand.NoCommand
|
||||||
|
import java.net.ConnectException
|
||||||
|
import java.{util => ju}
|
||||||
|
import ujson.Num
|
||||||
|
import ujson.Str
|
||||||
|
|
||||||
|
case class Config(
|
||||||
|
command: CliCommand = CliCommand.NoCommand,
|
||||||
|
network: Option[NetworkParameters] = None,
|
||||||
|
debug: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
sealed abstract class CliCommand
|
||||||
|
|
||||||
|
object CliCommand {
|
||||||
|
case object NoCommand extends CliCommand
|
||||||
|
|
||||||
|
// Wallet
|
||||||
|
case class SendToAddress(destination: BitcoinAddress, amount: Bitcoins)
|
||||||
|
extends CliCommand
|
||||||
|
case object GetNewAddress extends CliCommand
|
||||||
|
case object GetBalance extends CliCommand
|
||||||
|
|
||||||
|
// Node
|
||||||
|
case object GetPeers extends CliCommand
|
||||||
|
|
||||||
|
// Chain
|
||||||
|
case object GetBestBlockHash extends CliCommand
|
||||||
|
case object GetBlockCount extends CliCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
object Cli extends App {
|
||||||
|
|
||||||
|
val builder = OParser.builder[Config]
|
||||||
|
|
||||||
|
val parser = {
|
||||||
|
import CliCommand._
|
||||||
|
import builder._
|
||||||
|
OParser.sequence(
|
||||||
|
programName("bitcoin-s-cli"),
|
||||||
|
opt[NetworkParameters]('n', "network")
|
||||||
|
.action((np, conf) => conf.copy(network = Some(np)))
|
||||||
|
.text("Select the active network."),
|
||||||
|
opt[Unit]("debug")
|
||||||
|
.action((_, conf) => conf.copy(debug = true))
|
||||||
|
.text("Print debugging information"),
|
||||||
|
cmd("getblockcount")
|
||||||
|
.hidden()
|
||||||
|
.action((_, conf) => conf.copy(command = GetBlockCount))
|
||||||
|
.text(s"Get the block height"),
|
||||||
|
cmd("getbestblockhash")
|
||||||
|
.hidden()
|
||||||
|
.action((_, conf) => conf.copy(command = GetBestBlockHash))
|
||||||
|
.text(s"Get the best block hash"),
|
||||||
|
cmd("getbalance")
|
||||||
|
.hidden()
|
||||||
|
.action((_, conf) => conf.copy(command = GetBalance))
|
||||||
|
.text("Get the wallet balance"),
|
||||||
|
cmd("getnewaddress")
|
||||||
|
.hidden()
|
||||||
|
.action((_, conf) => conf.copy(command = GetNewAddress))
|
||||||
|
.text("Get a new address"),
|
||||||
|
cmd("sendtoaddress")
|
||||||
|
.hidden()
|
||||||
|
.action(
|
||||||
|
// TODO how to handle null here?
|
||||||
|
(_, conf) => conf.copy(command = SendToAddress(null, 0.bitcoin)))
|
||||||
|
.text("Send money to the given address")
|
||||||
|
.children(
|
||||||
|
opt[BitcoinAddress]("address")
|
||||||
|
.required()
|
||||||
|
.action((addr, conf) =>
|
||||||
|
conf.copy(command = conf.command match {
|
||||||
|
case send: SendToAddress =>
|
||||||
|
send.copy(destination = addr)
|
||||||
|
case other => other
|
||||||
|
})),
|
||||||
|
opt[Bitcoins]("amount")
|
||||||
|
.required()
|
||||||
|
.action((btc, conf) =>
|
||||||
|
conf.copy(command = conf.command match {
|
||||||
|
case send: SendToAddress =>
|
||||||
|
send.copy(amount = btc)
|
||||||
|
case other => other
|
||||||
|
}))
|
||||||
|
),
|
||||||
|
cmd("getpeers")
|
||||||
|
.hidden()
|
||||||
|
.action((_, conf) => conf.copy(command = GetPeers))
|
||||||
|
.text(s"List the connected peers"),
|
||||||
|
help('h', "help").text("Display this help message and exit"),
|
||||||
|
arg[String]("<cmd>")
|
||||||
|
.optional()
|
||||||
|
.text(
|
||||||
|
"The command and arguments to be executed. Try bitcoin-s-cli help for a list of all commands"),
|
||||||
|
checkConfig {
|
||||||
|
case Config(NoCommand, _, _) =>
|
||||||
|
failure("You need to provide a command!")
|
||||||
|
case _ => success
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO make this dynamic
|
||||||
|
val port = 9999
|
||||||
|
val host = "localhost"
|
||||||
|
|
||||||
|
val config: Config = OParser.parse(parser, args, Config()) match {
|
||||||
|
case None => sys.exit(1)
|
||||||
|
case Some(conf) => conf
|
||||||
|
}
|
||||||
|
|
||||||
|
import System.err.{println => printerr}
|
||||||
|
|
||||||
|
/** Prints the given message to stderr if debug is set */
|
||||||
|
def debug(message: Any): Unit = {
|
||||||
|
if (config.debug) {
|
||||||
|
printerr(s"DEBUG: $message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prints the given message to stderr and exist */
|
||||||
|
def error(message: String): Nothing = {
|
||||||
|
printerr(message)
|
||||||
|
// TODO error codes?
|
||||||
|
sys.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
case class RequestParam(
|
||||||
|
method: String,
|
||||||
|
params: Seq[ujson.Value.Value] = Nil) {
|
||||||
|
|
||||||
|
lazy val toJsonMap: Map[String, ujson.Value] = {
|
||||||
|
Map("method" -> method, "params" -> params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestParam: RequestParam = config.command match {
|
||||||
|
case GetBalance =>
|
||||||
|
RequestParam("getbalance")
|
||||||
|
case GetNewAddress =>
|
||||||
|
RequestParam("getnewaddress")
|
||||||
|
|
||||||
|
case SendToAddress(address, bitcoins) =>
|
||||||
|
RequestParam("sendtoaddress",
|
||||||
|
Seq(up.writeJs(address), up.writeJs(bitcoins)))
|
||||||
|
// height
|
||||||
|
case GetBlockCount => RequestParam("getblockcount")
|
||||||
|
// besthash
|
||||||
|
case GetBestBlockHash => RequestParam("getbestblockhash")
|
||||||
|
// peers
|
||||||
|
case GetPeers => RequestParam("getpeers")
|
||||||
|
case NoCommand => ???
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
import com.softwaremill.sttp._
|
||||||
|
implicit val backend = HttpURLConnectionBackend()
|
||||||
|
val request =
|
||||||
|
sttp
|
||||||
|
.post(uri"http://$host:$port/")
|
||||||
|
.contentType("application/json")
|
||||||
|
.body({
|
||||||
|
val uuid = ju.UUID.randomUUID.toString
|
||||||
|
val paramsWithID: Map[String, ujson.Value] = requestParam.toJsonMap + ("id" -> up
|
||||||
|
.writeJs(uuid))
|
||||||
|
up.write(paramsWithID)
|
||||||
|
})
|
||||||
|
debug(s"HTTP request: $request")
|
||||||
|
val response = request.send()
|
||||||
|
|
||||||
|
debug(s"HTTP response:")
|
||||||
|
debug(response)
|
||||||
|
|
||||||
|
// in order to mimic Bitcoin Core we always send
|
||||||
|
// an object looking like {"result": ..., "error": ...}
|
||||||
|
val rawBody = response.body match {
|
||||||
|
case Left(err) => err
|
||||||
|
case Right(response) => response
|
||||||
|
}
|
||||||
|
|
||||||
|
val js = ujson.read(rawBody)
|
||||||
|
val jsObj = try { js.obj } catch {
|
||||||
|
case _: Throwable =>
|
||||||
|
error(s"Response was not a JSON object! Got: $rawBody")
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the given key from jsObj if it exists
|
||||||
|
* and is not null */
|
||||||
|
def getKey(key: String): Option[ujson.Value] =
|
||||||
|
jsObj
|
||||||
|
.get(key)
|
||||||
|
.flatMap(result => if (result.isNull) None else Some(result))
|
||||||
|
|
||||||
|
/** Converts a `ujson.Value` to String, making an
|
||||||
|
* effort to avoid preceding and trailing `"`s */
|
||||||
|
def jsValueToString(value: ujson.Value) = value match {
|
||||||
|
case Str(string) => string
|
||||||
|
case Num(num) if num.isWhole => num.toLong.toString()
|
||||||
|
case Num(num) => num.toString()
|
||||||
|
case rest: ujson.Value => rest.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
(getKey("result"), getKey("error")) match {
|
||||||
|
case (Some(result), None) =>
|
||||||
|
val msg = jsValueToString(result)
|
||||||
|
println(msg)
|
||||||
|
case (None, Some(err)) =>
|
||||||
|
val msg = jsValueToString(err)
|
||||||
|
error(msg)
|
||||||
|
case (None, None) | (Some(_), Some(_)) =>
|
||||||
|
error(s"Got unexpected response: $rawBody")
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
case _: ConnectException =>
|
||||||
|
error(
|
||||||
|
"Connection refused! Check that the server is running and configured correctly.")
|
||||||
|
}
|
||||||
|
}
|
44
app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala
Normal file
44
app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package org.bitcoins.cli
|
||||||
|
|
||||||
|
import scopt._
|
||||||
|
import org.bitcoins.core.config.NetworkParameters
|
||||||
|
import org.bitcoins.core.protocol._
|
||||||
|
import org.bitcoins.core.currency._
|
||||||
|
import org.bitcoins.core.config.Networks
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Success
|
||||||
|
|
||||||
|
/** scopt readers for parsing CLI params and options */
|
||||||
|
object CliReaders {
|
||||||
|
|
||||||
|
implicit val npReads: Read[NetworkParameters] =
|
||||||
|
new Read[NetworkParameters] {
|
||||||
|
val arity: Int = 1
|
||||||
|
|
||||||
|
val reads: String => NetworkParameters = str =>
|
||||||
|
Networks.knownNetworks
|
||||||
|
.find(_.toString.toLowerCase == str.toLowerCase)
|
||||||
|
.getOrElse {
|
||||||
|
val networks =
|
||||||
|
Networks.knownNetworks
|
||||||
|
.map(_.toString.toLowerCase)
|
||||||
|
.mkString(", ")
|
||||||
|
val msg =
|
||||||
|
s"$str is not a valid network! Valid networks: $networks"
|
||||||
|
sys.error(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val bitcoinAddressReads: Read[BitcoinAddress] =
|
||||||
|
new Read[BitcoinAddress] {
|
||||||
|
val arity: Int = 1
|
||||||
|
|
||||||
|
val reads: String => BitcoinAddress = BitcoinAddress.fromStringExn
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val bitcoinsReads: Read[Bitcoins] =
|
||||||
|
new Read[Bitcoins] {
|
||||||
|
val arity: Int = 1
|
||||||
|
val reads: String => Bitcoins = str => Bitcoins(BigDecimal(str))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.bitcoins
|
||||||
|
|
||||||
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
|
import org.bitcoins.core.currency.Bitcoins
|
||||||
|
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||||
|
import upickle.default._
|
||||||
|
|
||||||
|
package object picklers {
|
||||||
|
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||||
|
implicit val bitcoinAddressPickler: ReadWriter[BitcoinAddress] =
|
||||||
|
readwriter[String]
|
||||||
|
.bimap(_.value, BitcoinAddress.fromStringExn(_))
|
||||||
|
|
||||||
|
implicit val bitcoinsPickler: ReadWriter[Bitcoins] =
|
||||||
|
readwriter[Double].bimap(_.toBigDecimal.toDouble, Bitcoins(_))
|
||||||
|
|
||||||
|
implicit val doubleSha256DigestBEPickler: ReadWriter[DoubleSha256DigestBE] =
|
||||||
|
readwriter[String].bimap(_.hex, DoubleSha256DigestBE.fromHex)
|
||||||
|
|
||||||
|
}
|
3
app/server-test/server-test.sbt
Normal file
3
app/server-test/server-test.sbt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
name := "bitcoin-s-server-test"
|
||||||
|
|
||||||
|
publish / skip := true
|
9
app/server/server.sbt
Normal file
9
app/server/server.sbt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
name := "bitcoin-s-server"
|
||||||
|
|
||||||
|
// Ensure actor system is shut down
|
||||||
|
// when server is quit
|
||||||
|
Compile / fork := true
|
||||||
|
|
||||||
|
publish / skip := true
|
||||||
|
|
||||||
|
libraryDependencies ++= Deps.server
|
4
app/server/src/main/resources/application.conf
Normal file
4
app/server/src/main/resources/application.conf
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
akka {
|
||||||
|
loggers = ["akka.event.slf4j.Slf4jLogger"]
|
||||||
|
loglevel = "DEBUG"
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package org.bitcoins.testkit
|
package org.bitcoins.server
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import org.bitcoins.wallet.config.WalletAppConfig
|
import org.bitcoins.wallet.config.WalletAppConfig
|
||||||
@ -6,8 +6,6 @@ import org.bitcoins.node.config.NodeAppConfig
|
|||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import java.nio.file.Files
|
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A unified config class for all submodules of Bitcoin-S
|
* A unified config class for all submodules of Bitcoin-S
|
||||||
@ -31,7 +29,7 @@ case class BitcoinSAppConfig(private val confs: Config*) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** The underlying config the result of our fields derive from */
|
/** The underlying config the result of our fields derive from */
|
||||||
lazy val config = {
|
lazy val config: Config = {
|
||||||
assert(chainConf.config == nodeConf.config)
|
assert(chainConf.config == nodeConf.config)
|
||||||
assert(nodeConf.config == walletConf.config)
|
assert(nodeConf.config == walletConf.config)
|
||||||
|
|
||||||
@ -75,61 +73,4 @@ object BitcoinSAppConfig {
|
|||||||
implicit def toNodeConf(conf: BitcoinSAppConfig): NodeAppConfig =
|
implicit def toNodeConf(conf: BitcoinSAppConfig): NodeAppConfig =
|
||||||
conf.nodeConf
|
conf.nodeConf
|
||||||
|
|
||||||
/**
|
|
||||||
* App configuration suitable for test purposes:
|
|
||||||
*
|
|
||||||
* 1) Data directory is set to user temp directory
|
|
||||||
*/
|
|
||||||
def getTestConfig(config: Config*) = {
|
|
||||||
val tmpDir = Files.createTempDirectory("bitcoin-s-")
|
|
||||||
val confStr = s"""
|
|
||||||
| bitcoin-s {
|
|
||||||
| datadir = $tmpDir
|
|
||||||
| }
|
|
||||||
|
|
|
||||||
|""".stripMargin
|
|
||||||
val conf = ConfigFactory.parseString(confStr)
|
|
||||||
val allConfs = conf +: config
|
|
||||||
BitcoinSAppConfig(allConfs: _*)
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed trait ProjectType
|
|
||||||
|
|
||||||
object ProjectType {
|
|
||||||
case object Wallet extends ProjectType
|
|
||||||
case object Node extends ProjectType
|
|
||||||
case object Chain extends ProjectType
|
|
||||||
|
|
||||||
val all = List(Wallet, Node, Chain)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Generates a Typesafe config with DBs set to memory
|
|
||||||
* databases for the given project (or all, if no
|
|
||||||
* project is given). This configuration can then be
|
|
||||||
* given as a override to other configs.
|
|
||||||
*/
|
|
||||||
def configWithMemoryDb(project: Option[ProjectType]): Config = {
|
|
||||||
def memConfigForProject(project: ProjectType): String = {
|
|
||||||
val name = project.toString().toLowerCase()
|
|
||||||
s"""
|
|
||||||
| $name.db {
|
|
||||||
| url = "jdbc:sqlite:file:$name.db:?mode=memory&cache=shared"
|
|
||||||
| connectionPool = disabled
|
|
||||||
| keepAliveConnection = true
|
|
||||||
| }
|
|
||||||
|""".stripMargin
|
|
||||||
}
|
|
||||||
|
|
||||||
val confStr = project match {
|
|
||||||
case None => ProjectType.all.map(memConfigForProject).mkString("\n")
|
|
||||||
case Some(p) => memConfigForProject(p)
|
|
||||||
}
|
|
||||||
val nestedConfStr = s"""
|
|
||||||
| bitcoin-s {
|
|
||||||
| $confStr
|
|
||||||
| }
|
|
||||||
|""".stripMargin
|
|
||||||
ConfigFactory.parseString(nestedConfStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl.server._
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
import akka.stream.ActorMaterializer
|
||||||
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
|
import org.bitcoins.chain.api.ChainApi
|
||||||
|
|
||||||
|
import org.bitcoins.picklers._
|
||||||
|
|
||||||
|
case class ChainRoutes(chain: ChainApi)(implicit system: ActorSystem)
|
||||||
|
extends BitcoinSLogger
|
||||||
|
with ServerRoute {
|
||||||
|
implicit val materializer = ActorMaterializer()
|
||||||
|
import system.dispatcher
|
||||||
|
|
||||||
|
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
||||||
|
case ServerCommand("getblockcount", _) =>
|
||||||
|
complete {
|
||||||
|
chain.getBlockCount.map { count =>
|
||||||
|
Server.httpSuccess(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ServerCommand("getbestblockhash", _) =>
|
||||||
|
complete {
|
||||||
|
chain.getBestBlockHash.map { hash =>
|
||||||
|
Server.httpSuccess(hash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
/** HTTP errors our server knows how to handle.
|
||||||
|
* These gets picked up by the exceptions handler
|
||||||
|
* in Main
|
||||||
|
*/
|
||||||
|
sealed abstract class HttpError extends Error
|
||||||
|
|
||||||
|
object HttpError {
|
||||||
|
|
||||||
|
/** The RPC method was not found */
|
||||||
|
final case class MethodNotFound(method: String) extends HttpError
|
||||||
|
}
|
134
app/server/src/main/scala/org/bitcoins/server/Main.scala
Normal file
134
app/server/src/main/scala/org/bitcoins/server/Main.scala
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
import org.bitcoins.rpc.config.BitcoindInstance
|
||||||
|
import org.bitcoins.node.models.Peer
|
||||||
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
|
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import scala.concurrent.Await
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import org.bitcoins.wallet.config.WalletAppConfig
|
||||||
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
|
import java.nio.file.Files
|
||||||
|
import scala.concurrent.Future
|
||||||
|
import org.bitcoins.wallet.LockedWallet
|
||||||
|
import org.bitcoins.wallet.Wallet
|
||||||
|
import org.bitcoins.wallet.api.InitializeWalletSuccess
|
||||||
|
import org.bitcoins.wallet.api.InitializeWalletError
|
||||||
|
import org.bitcoins.node.SpvNode
|
||||||
|
import org.bitcoins.chain.blockchain.ChainHandler
|
||||||
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
|
import org.bitcoins.wallet.api.UnlockedWalletApi
|
||||||
|
import org.bitcoins.wallet.api.UnlockWalletSuccess
|
||||||
|
import org.bitcoins.wallet.api.UnlockWalletError
|
||||||
|
import org.bitcoins.node.networking.peer.DataMessageHandler
|
||||||
|
import org.bitcoins.node.SpvNodeCallbacks
|
||||||
|
import org.bitcoins.wallet.WalletStorage
|
||||||
|
|
||||||
|
object Main
|
||||||
|
extends App
|
||||||
|
// TODO we want to log to user data directory
|
||||||
|
// how do we do this?
|
||||||
|
with BitcoinSLogger {
|
||||||
|
implicit val conf = {
|
||||||
|
// val custom = ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
||||||
|
BitcoinSAppConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val walletConf: WalletAppConfig = conf.walletConf
|
||||||
|
implicit val nodeConf: NodeAppConfig = conf.nodeConf
|
||||||
|
implicit val chainConf: ChainAppConfig = conf.chainConf
|
||||||
|
|
||||||
|
implicit val system = ActorSystem("bitcoin-s")
|
||||||
|
import system.dispatcher
|
||||||
|
|
||||||
|
sys.addShutdownHook {
|
||||||
|
logger.error(s"Exiting process")
|
||||||
|
system.terminate().foreach(_ => logger.info(s"Actor system terminated"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Log the given message, shut down the actor system and quit. */
|
||||||
|
def error(message: Any): Nothing = {
|
||||||
|
logger.error(s"FATAL: $message")
|
||||||
|
logger.error(s"Shutting down actor system")
|
||||||
|
Await.result(system.terminate(), 10.seconds)
|
||||||
|
logger.error("Actor system terminated")
|
||||||
|
logger.error(s"Exiting")
|
||||||
|
sys.error(message.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks if the user already has a wallet */
|
||||||
|
def hasWallet(): Boolean = {
|
||||||
|
val walletDB = walletConf.dbPath resolve walletConf.dbName
|
||||||
|
Files.exists(walletDB) && WalletStorage.seedExists()
|
||||||
|
}
|
||||||
|
|
||||||
|
val walletInitF: Future[UnlockedWalletApi] = if (hasWallet()) {
|
||||||
|
logger.info(s"Using pre-existing wallet")
|
||||||
|
val locked = LockedWallet()
|
||||||
|
|
||||||
|
// TODO change me when we implement proper password handling
|
||||||
|
locked.unlock(Wallet.badPassphrase) match {
|
||||||
|
case UnlockWalletSuccess(wallet) => Future.successful(wallet)
|
||||||
|
case err: UnlockWalletError => error(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.info(s"Creating new wallet")
|
||||||
|
Wallet.initialize().map {
|
||||||
|
case InitializeWalletSuccess(wallet) => wallet
|
||||||
|
case err: InitializeWalletError => error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val bitcoind = BitcoindInstance.fromDatadir()
|
||||||
|
val bitcoindCli = new BitcoindRpcClient(bitcoind)
|
||||||
|
val peer = Peer.fromBitcoind(bitcoind)
|
||||||
|
|
||||||
|
val startFut = for {
|
||||||
|
_ <- bitcoindCli.isStartedF.map { started =>
|
||||||
|
if (!started) error("Local bitcoind is not started!")
|
||||||
|
}
|
||||||
|
_ <- bitcoindCli.getBlockChainInfo.map { bitcoindInfo =>
|
||||||
|
if (bitcoindInfo.chain != nodeConf.network)
|
||||||
|
error(
|
||||||
|
s"bitcoind and Bitcoin-S node are on different chains! Bitcoind: ${bitcoindInfo.chain}. Bitcoin-S node: ${nodeConf.network}")
|
||||||
|
}
|
||||||
|
|
||||||
|
_ <- conf.initialize()
|
||||||
|
wallet <- walletInitF
|
||||||
|
|
||||||
|
bloom <- wallet.getBloomFilter()
|
||||||
|
_ = logger.info(s"Got bloom filter with ${bloom.filterSize.toInt} elements")
|
||||||
|
|
||||||
|
node <- {
|
||||||
|
|
||||||
|
val callbacks = {
|
||||||
|
import DataMessageHandler._
|
||||||
|
val onTX: OnTxReceived = { tx =>
|
||||||
|
wallet.processTransaction(tx, confirmations = 0)
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
SpvNodeCallbacks(onTxReceived = Seq(onTX))
|
||||||
|
}
|
||||||
|
val blockheaderDAO = BlockHeaderDAO()
|
||||||
|
val chain = ChainHandler(blockheaderDAO, conf)
|
||||||
|
SpvNode(peer, chain, bloom, callbacks).start()
|
||||||
|
}
|
||||||
|
_ = logger.info(s"Starting SPV node sync")
|
||||||
|
_ <- node.sync()
|
||||||
|
|
||||||
|
start <- {
|
||||||
|
val walletRoutes = WalletRoutes(wallet)
|
||||||
|
val nodeRoutes = NodeRoutes(node)
|
||||||
|
val chainRoutes = ChainRoutes(node.chainApi)
|
||||||
|
val server = Server(Seq(walletRoutes, nodeRoutes, chainRoutes))
|
||||||
|
server.start()
|
||||||
|
}
|
||||||
|
} yield start
|
||||||
|
|
||||||
|
startFut.failed.foreach { err =>
|
||||||
|
logger.info(s"Error on server startup!", err)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl.server._
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
import akka.stream.ActorMaterializer
|
||||||
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
|
import org.bitcoins.node.SpvNode
|
||||||
|
|
||||||
|
case class NodeRoutes(node: SpvNode)(implicit system: ActorSystem)
|
||||||
|
extends BitcoinSLogger
|
||||||
|
with ServerRoute {
|
||||||
|
implicit val materializer = ActorMaterializer()
|
||||||
|
|
||||||
|
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
||||||
|
case ServerCommand("getpeers", _) =>
|
||||||
|
complete {
|
||||||
|
Server.httpSuccess("TODO implement getpeers")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
app/server/src/main/scala/org/bitcoins/server/Server.scala
Normal file
131
app/server/src/main/scala/org/bitcoins/server/Server.scala
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
import upickle.{default => up}
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl._
|
||||||
|
import akka.stream.ActorMaterializer
|
||||||
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
|
import akka.http.scaladsl.model._
|
||||||
|
import akka.http.scaladsl.server._
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
|
||||||
|
import de.heikoseeberger.akkahttpupickle.UpickleSupport._
|
||||||
|
import akka.http.scaladsl.server.directives.DebuggingDirectives
|
||||||
|
import akka.event.Logging
|
||||||
|
|
||||||
|
case class Server(handlers: Seq[ServerRoute])(implicit system: ActorSystem)
|
||||||
|
extends BitcoinSLogger {
|
||||||
|
implicit val materializer = ActorMaterializer()
|
||||||
|
import system.dispatcher
|
||||||
|
|
||||||
|
/** Handles all server commands by throwing a MethodNotFound */
|
||||||
|
private val catchAllHandler: PartialFunction[ServerCommand, StandardRoute] = {
|
||||||
|
case ServerCommand(name, _) => throw HttpError.MethodNotFound(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** HTTP directive that handles both exceptions and rejections */
|
||||||
|
private def withErrorHandling(route: Route): Route = {
|
||||||
|
|
||||||
|
val rejectionHandler =
|
||||||
|
RejectionHandler
|
||||||
|
.newBuilder()
|
||||||
|
.handleNotFound {
|
||||||
|
complete {
|
||||||
|
Server.httpError(
|
||||||
|
"""Resource not found. Hint: all RPC calls are made against root ('/')""",
|
||||||
|
StatusCodes.BadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.result()
|
||||||
|
|
||||||
|
val exceptionHandler = ExceptionHandler {
|
||||||
|
case HttpError.MethodNotFound(method) =>
|
||||||
|
complete(
|
||||||
|
Server.httpError(s"'$method' is not a valid method",
|
||||||
|
StatusCodes.BadRequest))
|
||||||
|
case err: Throwable =>
|
||||||
|
logger.info(s"Unhandled error in server:", err)
|
||||||
|
complete(Server.httpError("There was an error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRejections(rejectionHandler) {
|
||||||
|
handleExceptions(exceptionHandler) {
|
||||||
|
route
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val route =
|
||||||
|
// TODO implement better logging
|
||||||
|
DebuggingDirectives.logRequestResult("http-rpc-server", Logging.InfoLevel) {
|
||||||
|
withErrorHandling {
|
||||||
|
pathSingleSlash {
|
||||||
|
post {
|
||||||
|
entity(as[ServerCommand]) { cmd =>
|
||||||
|
val init = PartialFunction.empty[ServerCommand, StandardRoute]
|
||||||
|
val handler = handlers.foldLeft(init) {
|
||||||
|
case (accum, curr) => accum.orElse(curr.handleCommand)
|
||||||
|
}
|
||||||
|
handler.orElse(catchAllHandler).apply(cmd)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def start() = {
|
||||||
|
val httpFut =
|
||||||
|
Http().bindAndHandle(route, "localhost", 9999)
|
||||||
|
httpFut.foreach { http =>
|
||||||
|
logger.info(s"Started Bitcoin-S HTTP server at ${http.localAddress}")
|
||||||
|
}
|
||||||
|
httpFut
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Server extends BitcoinSLogger {
|
||||||
|
|
||||||
|
// TODO id parameter
|
||||||
|
case class Response(
|
||||||
|
result: Option[ujson.Value] = None,
|
||||||
|
error: Option[String] = None) {
|
||||||
|
|
||||||
|
def toJsonMap: Map[String, ujson.Value] = {
|
||||||
|
Map(
|
||||||
|
"result" -> (result match {
|
||||||
|
case None => ujson.Null
|
||||||
|
case Some(res) => res
|
||||||
|
}),
|
||||||
|
"error" -> (error match {
|
||||||
|
case None => ujson.Null
|
||||||
|
case Some(err) => err
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a HTTP response with the given body as a JSON response */
|
||||||
|
def httpSuccess[T](body: T)(
|
||||||
|
implicit writer: up.Writer[T]): HttpEntity.Strict = {
|
||||||
|
val response = Response(result = Some(up.writeJs(body)))
|
||||||
|
HttpEntity(
|
||||||
|
ContentTypes.`application/json`,
|
||||||
|
up.write(response.toJsonMap)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
def httpError(
|
||||||
|
msg: String,
|
||||||
|
status: StatusCode = StatusCodes.InternalServerError): HttpResponse = {
|
||||||
|
|
||||||
|
val entity = {
|
||||||
|
val response = Response(error = Some(msg))
|
||||||
|
HttpEntity(
|
||||||
|
ContentTypes.`application/json`,
|
||||||
|
up.write(response.toJsonMap)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse(status = status, entity = entity)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
import upickle.default._
|
||||||
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
|
import org.bitcoins.core.currency.Bitcoins
|
||||||
|
|
||||||
|
import org.bitcoins.picklers._
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Try
|
||||||
|
import scala.util.Success
|
||||||
|
import akka.io.Udp.Send
|
||||||
|
|
||||||
|
// TODO ID?
|
||||||
|
case class ServerCommand(method: String, params: ujson.Arr)
|
||||||
|
|
||||||
|
object ServerCommand {
|
||||||
|
implicit val rw: ReadWriter[ServerCommand] = macroRW
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SendToAddress(address: BitcoinAddress, amount: Bitcoins)
|
||||||
|
|
||||||
|
object SendToAddress {
|
||||||
|
|
||||||
|
/// TODO do this in a more coherent fashion
|
||||||
|
// custom akka-http directive?
|
||||||
|
def fromJsArr(jsArr: ujson.Arr): Try[SendToAddress] = {
|
||||||
|
jsArr.arr.toList match {
|
||||||
|
case addrJs :: bitcoinsJs :: Nil =>
|
||||||
|
try {
|
||||||
|
val address = BitcoinAddress.fromStringExn(addrJs.str)
|
||||||
|
val bitcoins = Bitcoins(bitcoinsJs.num)
|
||||||
|
Success(SendToAddress(address, bitcoins))
|
||||||
|
} catch {
|
||||||
|
case e: Throwable => Failure(e)
|
||||||
|
}
|
||||||
|
case Nil =>
|
||||||
|
Failure(
|
||||||
|
new IllegalArgumentException("Missing address and amount argument"))
|
||||||
|
|
||||||
|
case other =>
|
||||||
|
Failure(
|
||||||
|
new IllegalArgumentException(
|
||||||
|
s"Bad number of arguments: ${other.length}. Expected: 2"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
import akka.http.scaladsl.server.StandardRoute
|
||||||
|
|
||||||
|
trait ServerRoute {
|
||||||
|
def handleCommand: PartialFunction[ServerCommand, StandardRoute]
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
import org.bitcoins.picklers._
|
||||||
|
|
||||||
|
import akka.actor.ActorSystem
|
||||||
|
import akka.http.scaladsl.server._
|
||||||
|
import akka.http.scaladsl.server.Directives._
|
||||||
|
import akka.stream.ActorMaterializer
|
||||||
|
|
||||||
|
import org.bitcoins.core.util.BitcoinSLogger
|
||||||
|
import org.bitcoins.core.currency._
|
||||||
|
import org.bitcoins.wallet.api.UnlockedWalletApi
|
||||||
|
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
||||||
|
|
||||||
|
import de.heikoseeberger.akkahttpupickle.UpickleSupport._
|
||||||
|
import scala.util.Failure
|
||||||
|
import scala.util.Success
|
||||||
|
|
||||||
|
case class WalletRoutes(wallet: UnlockedWalletApi)(implicit system: ActorSystem)
|
||||||
|
extends BitcoinSLogger
|
||||||
|
with ServerRoute {
|
||||||
|
import system.dispatcher
|
||||||
|
implicit val materializer = ActorMaterializer()
|
||||||
|
|
||||||
|
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
||||||
|
case ServerCommand("getbalance", _) =>
|
||||||
|
complete {
|
||||||
|
wallet.getBalance().map { balance =>
|
||||||
|
Server.httpSuccess(
|
||||||
|
Bitcoins(balance.satoshis)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case ServerCommand("getnewaddress", _) =>
|
||||||
|
complete {
|
||||||
|
wallet.getNewAddress().map { address =>
|
||||||
|
Server.httpSuccess(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case ServerCommand("sendtoaddress", arr) =>
|
||||||
|
// TODO create custom directive for this?
|
||||||
|
SendToAddress.fromJsArr(arr) match {
|
||||||
|
case Failure(exception) =>
|
||||||
|
reject(ValidationRejection("failure", Some(exception)))
|
||||||
|
case Success(SendToAddress(address, bitcoins)) =>
|
||||||
|
complete {
|
||||||
|
// TODO dynamic fees
|
||||||
|
val feeRate = SatoshisPerByte(100.sats)
|
||||||
|
wallet.sendToAddress(address, bitcoins, feeRate).map { tx =>
|
||||||
|
// TODO this TX isn't being broadcast anywhere
|
||||||
|
// would be better to dump the entire TX hex until that's implemented?
|
||||||
|
Server.httpSuccess(tx.txIdBE)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -34,4 +34,7 @@ buildInfoPackage := "org.bitcoins.docs"
|
|||||||
// Mdoc end
|
// Mdoc end
|
||||||
///////
|
///////
|
||||||
|
|
||||||
|
Test / bloopGenerate := None
|
||||||
|
Compile / bloopGenerate := None
|
||||||
|
|
||||||
libraryDependencies ++= Deps.docs
|
libraryDependencies ++= Deps.docs
|
||||||
|
@ -9,6 +9,7 @@ import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
|||||||
import org.bitcoins.testkit.util.BitcoindRpcTest
|
import org.bitcoins.testkit.util.BitcoindRpcTest
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
|
import org.bitcoins.core.config.RegTest
|
||||||
|
|
||||||
class BlockchainRpcTest extends BitcoindRpcTest {
|
class BlockchainRpcTest extends BitcoindRpcTest {
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ class BlockchainRpcTest extends BitcoindRpcTest {
|
|||||||
info <- client.getBlockChainInfo
|
info <- client.getBlockChainInfo
|
||||||
bestHash <- client.getBestBlockHash
|
bestHash <- client.getBestBlockHash
|
||||||
} yield {
|
} yield {
|
||||||
assert(info.chain == "regtest")
|
assert(info.chain == RegTest)
|
||||||
assert(info.softforks.length >= 3)
|
assert(info.softforks.length >= 3)
|
||||||
assert(info.bip9_softforks.keySet.size >= 2)
|
assert(info.bip9_softforks.keySet.size >= 2)
|
||||||
assert(info.bestblockhash == bestHash)
|
assert(info.bestblockhash == bestHash)
|
||||||
|
@ -5,6 +5,7 @@ import org.bitcoins.core.currency.Bitcoins
|
|||||||
import org.bitcoins.core.number.{Int32, UInt32}
|
import org.bitcoins.core.number.{Int32, UInt32}
|
||||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||||
import org.bitcoins.core.wallet.fee.BitcoinFeeUnit
|
import org.bitcoins.core.wallet.fee.BitcoinFeeUnit
|
||||||
|
import org.bitcoins.core.config.NetworkParameters
|
||||||
|
|
||||||
sealed abstract class BlockchainResult
|
sealed abstract class BlockchainResult
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ case class GetBlockWithTransactionsResult(
|
|||||||
extends BlockchainResult
|
extends BlockchainResult
|
||||||
|
|
||||||
case class GetBlockChainInfoResult(
|
case class GetBlockChainInfoResult(
|
||||||
chain: String,
|
chain: NetworkParameters,
|
||||||
blocks: Int,
|
blocks: Int,
|
||||||
headers: Int,
|
headers: Int,
|
||||||
bestblockhash: DoubleSha256DigestBE,
|
bestblockhash: DoubleSha256DigestBE,
|
||||||
@ -106,6 +107,7 @@ case class GetBlockHeaderResult(
|
|||||||
previousblockhash: Option[DoubleSha256DigestBE],
|
previousblockhash: Option[DoubleSha256DigestBE],
|
||||||
nextblockhash: Option[DoubleSha256DigestBE])
|
nextblockhash: Option[DoubleSha256DigestBE])
|
||||||
extends BlockchainResult {
|
extends BlockchainResult {
|
||||||
|
|
||||||
def blockHeader: BlockHeader = {
|
def blockHeader: BlockHeader = {
|
||||||
|
|
||||||
//prevblockhash is only empty if we have the genesis block
|
//prevblockhash is only empty if we have the genesis block
|
||||||
|
75
build.sbt
75
build.sbt
@ -77,18 +77,6 @@ lazy val commonSettings = List(
|
|||||||
assemblyOption in assembly := (assemblyOption in assembly).value
|
assemblyOption in assembly := (assemblyOption in assembly).value
|
||||||
.copy(includeScala = false),
|
.copy(includeScala = false),
|
||||||
licenses += ("MIT", url("http://opensource.org/licenses/MIT")),
|
licenses += ("MIT", url("http://opensource.org/licenses/MIT")),
|
||||||
/**
|
|
||||||
* Adding Ammonite REPL to test scope, can access both test and compile
|
|
||||||
* sources. Docs: http://ammonite.io/#Ammonite-REPL
|
|
||||||
* Creates an ad-hoc main file that can be run by doing
|
|
||||||
* test:run (or test:runMain amm if there's multiple main files
|
|
||||||
* in scope)
|
|
||||||
*/
|
|
||||||
Test / sourceGenerators += Def.task {
|
|
||||||
val file = (Test / sourceManaged).value / "amm.scala"
|
|
||||||
IO.write(file, """object amm extends App { ammonite.Main.main(args) }""")
|
|
||||||
Seq(file)
|
|
||||||
}.taskValue,
|
|
||||||
// Travis has performance issues on macOS
|
// Travis has performance issues on macOS
|
||||||
Test / parallelExecution := !(Properties.isMac && sys.props
|
Test / parallelExecution := !(Properties.isMac && sys.props
|
||||||
.get("CI")
|
.get("CI")
|
||||||
@ -133,6 +121,8 @@ lazy val bitcoins = project
|
|||||||
secp256k1jni,
|
secp256k1jni,
|
||||||
chain,
|
chain,
|
||||||
chainTest,
|
chainTest,
|
||||||
|
cli,
|
||||||
|
cliTest,
|
||||||
core,
|
core,
|
||||||
coreTest,
|
coreTest,
|
||||||
dbCommons,
|
dbCommons,
|
||||||
@ -143,8 +133,11 @@ lazy val bitcoins = project
|
|||||||
eclairRpcTest,
|
eclairRpcTest,
|
||||||
node,
|
node,
|
||||||
nodeTest,
|
nodeTest,
|
||||||
|
picklers,
|
||||||
wallet,
|
wallet,
|
||||||
walletTest,
|
walletTest,
|
||||||
|
walletServer,
|
||||||
|
walletServerTest,
|
||||||
testkit,
|
testkit,
|
||||||
scripts,
|
scripts,
|
||||||
zmq
|
zmq
|
||||||
@ -152,7 +145,6 @@ lazy val bitcoins = project
|
|||||||
.settings(commonSettings: _*)
|
.settings(commonSettings: _*)
|
||||||
// crossScalaVersions must be set to Nil on the aggregating project
|
// crossScalaVersions must be set to Nil on the aggregating project
|
||||||
.settings(crossScalaVersions := Nil)
|
.settings(crossScalaVersions := Nil)
|
||||||
.settings(libraryDependencies ++= Deps.root)
|
|
||||||
.enablePlugins(ScalaUnidocPlugin, GitVersioning)
|
.enablePlugins(ScalaUnidocPlugin, GitVersioning)
|
||||||
.settings(
|
.settings(
|
||||||
// we modify the unidoc task to move the generated Scaladocs into the
|
// we modify the unidoc task to move the generated Scaladocs into the
|
||||||
@ -292,6 +284,48 @@ lazy val coreTest = project
|
|||||||
)
|
)
|
||||||
.enablePlugins()
|
.enablePlugins()
|
||||||
|
|
||||||
|
lazy val walletServer = project
|
||||||
|
.in(file("app/server"))
|
||||||
|
.settings(commonSettings: _*)
|
||||||
|
.dependsOn(
|
||||||
|
picklers,
|
||||||
|
node,
|
||||||
|
chain,
|
||||||
|
wallet,
|
||||||
|
bitcoindRpc
|
||||||
|
)
|
||||||
|
|
||||||
|
lazy val walletServerTest = project
|
||||||
|
.in(file("app/server-test"))
|
||||||
|
.settings(commonTestSettings)
|
||||||
|
.dependsOn(
|
||||||
|
walletServer,
|
||||||
|
testkit
|
||||||
|
)
|
||||||
|
|
||||||
|
// internal picklers used by server
|
||||||
|
// and CLI
|
||||||
|
lazy val picklers = project
|
||||||
|
.in(file("app/picklers"))
|
||||||
|
.dependsOn(core % testAndCompile)
|
||||||
|
|
||||||
|
|
||||||
|
lazy val cli = project
|
||||||
|
.in(file("app/cli"))
|
||||||
|
.settings(commonSettings: _*)
|
||||||
|
.dependsOn(
|
||||||
|
picklers
|
||||||
|
)
|
||||||
|
|
||||||
|
lazy val cliTest = project
|
||||||
|
.in(file("app/cli-test"))
|
||||||
|
.settings(commonTestSettings: _*)
|
||||||
|
.dependsOn(
|
||||||
|
cli,
|
||||||
|
testkit
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
lazy val chainDbSettings = dbFlywaySettings("chaindb")
|
lazy val chainDbSettings = dbFlywaySettings("chaindb")
|
||||||
lazy val chain = project
|
lazy val chain = project
|
||||||
.in(file("chain"))
|
.in(file("chain"))
|
||||||
@ -428,6 +462,8 @@ lazy val testkit = project
|
|||||||
.settings(commonSettings: _*)
|
.settings(commonSettings: _*)
|
||||||
.dependsOn(
|
.dependsOn(
|
||||||
core % testAndCompile,
|
core % testAndCompile,
|
||||||
|
walletServer,
|
||||||
|
cli,
|
||||||
chain,
|
chain,
|
||||||
bitcoindRpc,
|
bitcoindRpc,
|
||||||
eclairRpc,
|
eclairRpc,
|
||||||
@ -492,19 +528,6 @@ lazy val scripts = project
|
|||||||
zmq
|
zmq
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ammonite is invoked through running
|
|
||||||
// a main class it places in test sources
|
|
||||||
// for us. This makes it a bit less awkward
|
|
||||||
// to start the Ammonite shell. Sadly,
|
|
||||||
// prepending the project and then doing
|
|
||||||
// `amm` (e.g. sbt coreTest/amm`) does not
|
|
||||||
// work. For that you either have to do
|
|
||||||
// `sbt coreTest/test:run` or:
|
|
||||||
// sbt
|
|
||||||
// project coreTest
|
|
||||||
// amm
|
|
||||||
addCommandAlias("amm", "test:run")
|
|
||||||
|
|
||||||
publishArtifact in bitcoins := false
|
publishArtifact in bitcoins := false
|
||||||
|
|
||||||
def dbFlywaySettings(dbName: String): List[Setting[_]] = {
|
def dbFlywaySettings(dbName: String): List[Setting[_]] = {
|
||||||
|
@ -20,7 +20,8 @@ import org.scalatest.{Assertion, FutureOutcome}
|
|||||||
import play.api.libs.json.Json
|
import play.api.libs.json.Json
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
|
||||||
class ChainHandlerTest extends ChainUnitTest {
|
class ChainHandlerTest extends ChainUnitTest {
|
||||||
|
|
||||||
@ -30,8 +31,10 @@ class ChainHandlerTest extends ChainUnitTest {
|
|||||||
|
|
||||||
// we're working with mainnet data
|
// we're working with mainnet data
|
||||||
implicit override lazy val appConfig: ChainAppConfig = {
|
implicit override lazy val appConfig: ChainAppConfig = {
|
||||||
val memoryDb = BitcoinSAppConfig.configWithMemoryDb(
|
import BitcoinSTestAppConfig.ProjectType
|
||||||
Some(BitcoinSAppConfig.ProjectType.Chain))
|
|
||||||
|
val memoryDb =
|
||||||
|
BitcoinSTestAppConfig.configWithMemoryDb(Some(ProjectType.Chain))
|
||||||
mainnetAppConfig.withOverrides(memoryDb)
|
mainnetAppConfig.withOverrides(memoryDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,15 +9,17 @@ import org.bitcoins.testkit.chain.{ChainTestUtil, ChainUnitTest}
|
|||||||
import org.scalatest.FutureOutcome
|
import org.scalatest.FutureOutcome
|
||||||
|
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
|
||||||
class BitcoinPowTest extends ChainUnitTest {
|
class BitcoinPowTest extends ChainUnitTest {
|
||||||
|
|
||||||
override type FixtureParam = ChainFixture
|
override type FixtureParam = ChainFixture
|
||||||
|
|
||||||
implicit override lazy val appConfig: ChainAppConfig = {
|
implicit override lazy val appConfig: ChainAppConfig = {
|
||||||
val memoryDb = BitcoinSAppConfig.configWithMemoryDb(
|
import BitcoinSTestAppConfig.ProjectType
|
||||||
Some(BitcoinSAppConfig.ProjectType.Chain))
|
val memoryDb =
|
||||||
|
BitcoinSTestAppConfig.configWithMemoryDb(Some(ProjectType.Chain))
|
||||||
mainnetAppConfig.withOverrides(memoryDb)
|
mainnetAppConfig.withOverrides(memoryDb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import org.scalatest.{Assertion, FutureOutcome}
|
|||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import org.bitcoins.chain.config.ChainAppConfig
|
import org.bitcoins.chain.config.ChainAppConfig
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
|
|
||||||
class TipValidationTest extends ChainUnitTest {
|
class TipValidationTest extends ChainUnitTest {
|
||||||
|
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
scalaVersion in ThisBuild := "2.12.8"
|
val scala2_11 = "2.11.12"
|
||||||
|
val scala2_12 = "2.12.8"
|
||||||
|
|
||||||
crossScalaVersions in ThisBuild := List("2.11.12", "2.12.8")
|
scalaVersion in ThisBuild := scala2_12
|
||||||
|
|
||||||
|
crossScalaVersions in ThisBuild := List(scala2_11, scala2_12)
|
||||||
|
|
||||||
organization in ThisBuild := "org.bitcoins"
|
organization in ThisBuild := "org.bitcoins"
|
||||||
|
@ -7,7 +7,7 @@ import org.bitcoins.chain.models.BlockHeaderDAO
|
|||||||
import org.bitcoins.node.config.NodeAppConfig
|
import org.bitcoins.node.config.NodeAppConfig
|
||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.scalatest.FutureOutcome
|
import org.scalatest.FutureOutcome
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
import org.bitcoins.wallet.config.WalletAppConfig
|
import org.bitcoins.wallet.config.WalletAppConfig
|
||||||
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
|
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
|
||||||
|
|
||||||
|
@ -5,7 +5,8 @@ import akka.testkit.{TestActorRef, TestProbe}
|
|||||||
import org.bitcoins.node.models.Peer
|
import org.bitcoins.node.models.Peer
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
import org.bitcoins.node.networking.peer.PeerMessageReceiver
|
||||||
import org.bitcoins.node.networking.peer.PeerMessageReceiverState.Preconnection
|
import org.bitcoins.node.networking.peer.PeerMessageReceiverState.Preconnection
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
import org.bitcoins.testkit.async.TestAsyncUtil
|
import org.bitcoins.testkit.async.TestAsyncUtil
|
||||||
import org.bitcoins.testkit.node.NodeTestUtil
|
import org.bitcoins.testkit.node.NodeTestUtil
|
||||||
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||||
@ -25,19 +26,21 @@ class ClientTest
|
|||||||
with BeforeAndAfterAll {
|
with BeforeAndAfterAll {
|
||||||
|
|
||||||
implicit private val config: BitcoinSAppConfig =
|
implicit private val config: BitcoinSAppConfig =
|
||||||
BitcoinSAppConfig.getTestConfig()
|
BitcoinSTestAppConfig.getTestConfig()
|
||||||
implicit private val chainConf = config.chainConf
|
implicit private val chainConf = config.chainConf
|
||||||
implicit private val nodeConf = config.nodeConf
|
implicit private val nodeConf = config.nodeConf
|
||||||
|
|
||||||
implicit val np = config.chainConf.network
|
implicit val np = config.chainConf.network
|
||||||
|
|
||||||
lazy val bitcoindRpcF = BitcoindRpcTestUtil.startedBitcoindRpcClient(clientAccum = clientAccum)
|
lazy val bitcoindRpcF =
|
||||||
|
BitcoindRpcTestUtil.startedBitcoindRpcClient(clientAccum = clientAccum)
|
||||||
|
|
||||||
lazy val bitcoindPeerF = bitcoindRpcF.map { bitcoind =>
|
lazy val bitcoindPeerF = bitcoindRpcF.map { bitcoind =>
|
||||||
NodeTestUtil.getBitcoindPeer(bitcoind)
|
NodeTestUtil.getBitcoindPeer(bitcoind)
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy val bitcoindRpc2F = BitcoindRpcTestUtil.startedBitcoindRpcClient(clientAccum = clientAccum)
|
lazy val bitcoindRpc2F =
|
||||||
|
BitcoindRpcTestUtil.startedBitcoindRpcClient(clientAccum = clientAccum)
|
||||||
|
|
||||||
lazy val bitcoindPeer2F = bitcoindRpcF.map { bitcoind =>
|
lazy val bitcoindPeer2F = bitcoindRpcF.map { bitcoind =>
|
||||||
NodeTestUtil.getBitcoindPeer(bitcoind)
|
NodeTestUtil.getBitcoindPeer(bitcoind)
|
||||||
|
@ -25,8 +25,16 @@ object Deps {
|
|||||||
val akkaActorV = akkaStreamv
|
val akkaActorV = akkaStreamv
|
||||||
val slickV = "3.3.1"
|
val slickV = "3.3.1"
|
||||||
val sqliteV = "3.27.2.1"
|
val sqliteV = "3.27.2.1"
|
||||||
val uJsonV = "0.7.1"
|
|
||||||
val scalameterV = "0.17"
|
val scalameterV = "0.17"
|
||||||
|
|
||||||
|
// Wallet/node/chain server deps
|
||||||
|
val uPickleV = "0.7.4"
|
||||||
|
val akkaHttpUpickleV = "1.27.0"
|
||||||
|
val uJsonV = uPickleV // Li Haoyi ecosystem does common versioning
|
||||||
|
|
||||||
|
// CLI deps
|
||||||
|
val scoptV = "4.0.0-RC2"
|
||||||
|
val sttpV = "1.6.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Compile {
|
object Compile {
|
||||||
@ -37,6 +45,8 @@ object Deps {
|
|||||||
val akkaHttp = "com.typesafe.akka" %% "akka-http" % V.akkav withSources () withJavadoc ()
|
val akkaHttp = "com.typesafe.akka" %% "akka-http" % V.akkav withSources () withJavadoc ()
|
||||||
val akkaStream = "com.typesafe.akka" %% "akka-stream" % V.akkaStreamv withSources () withJavadoc ()
|
val akkaStream = "com.typesafe.akka" %% "akka-stream" % V.akkaStreamv withSources () withJavadoc ()
|
||||||
val akkaActor = "com.typesafe.akka" %% "akka-actor" % V.akkaStreamv withSources () withJavadoc ()
|
val akkaActor = "com.typesafe.akka" %% "akka-actor" % V.akkaStreamv withSources () withJavadoc ()
|
||||||
|
val akkaLog = "com.typesafe.akka" %% "akka-slf4j" % V.akkaStreamv
|
||||||
|
|
||||||
val playJson = "com.typesafe.play" %% "play-json" % V.playv withSources () withJavadoc ()
|
val playJson = "com.typesafe.play" %% "play-json" % V.playv withSources () withJavadoc ()
|
||||||
val typesafeConfig = "com.typesafe" % "config" % V.typesafeConfigV withSources () withJavadoc ()
|
val typesafeConfig = "com.typesafe" % "config" % V.typesafeConfigV withSources () withJavadoc ()
|
||||||
|
|
||||||
@ -55,6 +65,18 @@ object Deps {
|
|||||||
val postgres = "org.postgresql" % "postgresql" % V.postgresV
|
val postgres = "org.postgresql" % "postgresql" % V.postgresV
|
||||||
val uJson = "com.lihaoyi" %% "ujson" % V.uJsonV
|
val uJson = "com.lihaoyi" %% "ujson" % V.uJsonV
|
||||||
|
|
||||||
|
// serializing to and from JSON
|
||||||
|
val uPickle = "com.lihaoyi" %% "upickle" % V.uPickleV
|
||||||
|
|
||||||
|
// make akka-http play nice with upickle
|
||||||
|
val akkaHttpUpickle = "de.heikoseeberger" %% "akka-http-upickle" % V.akkaHttpUpickleV
|
||||||
|
|
||||||
|
// parsing of CLI opts and args
|
||||||
|
val scopt = "com.github.scopt" %% "scopt" % V.scoptV
|
||||||
|
|
||||||
|
// HTTP client lib
|
||||||
|
val sttp = "com.softwaremill.sttp" %% "core" % V.sttpV
|
||||||
|
|
||||||
val scalacheck = "org.scalacheck" %% "scalacheck" % V.scalacheck withSources () withJavadoc ()
|
val scalacheck = "org.scalacheck" %% "scalacheck" % V.scalacheck withSources () withJavadoc ()
|
||||||
val scalaTest = "org.scalatest" %% "scalatest" % V.scalaTest withSources () withJavadoc ()
|
val scalaTest = "org.scalatest" %% "scalatest" % V.scalaTest withSources () withJavadoc ()
|
||||||
}
|
}
|
||||||
@ -71,37 +93,28 @@ object Deps {
|
|||||||
val spray = "io.spray" %% "spray-json" % V.spray % "test" withSources () withJavadoc ()
|
val spray = "io.spray" %% "spray-json" % V.spray % "test" withSources () withJavadoc ()
|
||||||
val akkaHttp = "com.typesafe.akka" %% "akka-http-testkit" % V.akkav % "test" withSources () withJavadoc ()
|
val akkaHttp = "com.typesafe.akka" %% "akka-http-testkit" % V.akkav % "test" withSources () withJavadoc ()
|
||||||
val akkaStream = "com.typesafe.akka" %% "akka-stream-testkit" % V.akkaStreamv % "test" withSources () withJavadoc ()
|
val akkaStream = "com.typesafe.akka" %% "akka-stream-testkit" % V.akkaStreamv % "test" withSources () withJavadoc ()
|
||||||
val ammonite = Compile.ammonite % "test"
|
|
||||||
val playJson = Compile.playJson % "test"
|
val playJson = Compile.playJson % "test"
|
||||||
val akkaTestkit = "com.typesafe.akka" %% "akka-testkit" % V.akkaActorV withSources () withJavadoc ()
|
val akkaTestkit = "com.typesafe.akka" %% "akka-testkit" % V.akkaActorV withSources () withJavadoc ()
|
||||||
val scalameter = "com.storm-enroute" %% "scalameter" % V.scalameterV % "test" withSources () withJavadoc ()
|
val scalameter = "com.storm-enroute" %% "scalameter" % V.scalameterV % "test" withSources () withJavadoc ()
|
||||||
}
|
}
|
||||||
|
|
||||||
val root = List(
|
|
||||||
Test.ammonite
|
|
||||||
)
|
|
||||||
|
|
||||||
val chain = List(
|
val chain = List(
|
||||||
Compile.slf4j,
|
Compile.slf4j
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val chainTest = List(
|
val chainTest = List(
|
||||||
Test.ammonite,
|
|
||||||
Test.logback
|
Test.logback
|
||||||
)
|
)
|
||||||
|
|
||||||
val core = List(
|
val core = List(
|
||||||
Compile.bouncycastle,
|
Compile.bouncycastle,
|
||||||
Compile.scodec,
|
Compile.scodec,
|
||||||
Compile.slf4j,
|
Compile.slf4j
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val secp256k1jni = List(
|
val secp256k1jni = List(
|
||||||
Compile.nativeLoader,
|
Compile.nativeLoader,
|
||||||
Test.junitInterface,
|
Test.junitInterface
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val coreTest = List(
|
val coreTest = List(
|
||||||
@ -110,7 +123,6 @@ object Deps {
|
|||||||
Test.logback,
|
Test.logback,
|
||||||
Test.scalaTest,
|
Test.scalaTest,
|
||||||
Test.spray,
|
Test.spray,
|
||||||
Test.ammonite,
|
|
||||||
Test.playJson
|
Test.playJson
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -119,8 +131,7 @@ object Deps {
|
|||||||
Compile.slf4j,
|
Compile.slf4j,
|
||||||
Test.logback,
|
Test.logback,
|
||||||
Test.scalacheck,
|
Test.scalacheck,
|
||||||
Test.scalaTest,
|
Test.scalaTest
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val bitcoindRpc = List(
|
val bitcoindRpc = List(
|
||||||
@ -128,8 +139,7 @@ object Deps {
|
|||||||
Compile.akkaStream,
|
Compile.akkaStream,
|
||||||
Compile.playJson,
|
Compile.playJson,
|
||||||
Compile.slf4j,
|
Compile.slf4j,
|
||||||
Compile.typesafeConfig,
|
Compile.typesafeConfig
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val bitcoindRpcTest = List(
|
val bitcoindRpcTest = List(
|
||||||
@ -138,29 +148,43 @@ object Deps {
|
|||||||
Test.logback,
|
Test.logback,
|
||||||
Test.scalaTest,
|
Test.scalaTest,
|
||||||
Test.scalacheck,
|
Test.scalacheck,
|
||||||
Test.async,
|
Test.async
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val bench = List(
|
val bench = List(
|
||||||
"org.slf4j" % "slf4j-api" % V.slf4j withSources () withJavadoc (),
|
"org.slf4j" % "slf4j-api" % V.slf4j withSources () withJavadoc (),
|
||||||
Compile.logback,
|
Compile.logback
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val dbCommons = List(
|
val dbCommons = List(
|
||||||
Compile.slick,
|
Compile.slick,
|
||||||
Compile.sqlite,
|
Compile.sqlite,
|
||||||
Compile.slickHikari,
|
Compile.slickHikari
|
||||||
Test.ammonite
|
)
|
||||||
|
|
||||||
|
val cli = List(
|
||||||
|
Compile.sttp,
|
||||||
|
Compile.uPickle,
|
||||||
|
Compile.scopt
|
||||||
|
)
|
||||||
|
|
||||||
|
val picklers = List(
|
||||||
|
Compile.uPickle
|
||||||
|
)
|
||||||
|
|
||||||
|
val server = List(
|
||||||
|
Compile.akkaHttpUpickle,
|
||||||
|
Compile.uPickle,
|
||||||
|
Compile.logback,
|
||||||
|
Compile.akkaLog,
|
||||||
|
Compile.akkaHttp
|
||||||
)
|
)
|
||||||
|
|
||||||
val eclairRpc = List(
|
val eclairRpc = List(
|
||||||
Compile.akkaHttp,
|
Compile.akkaHttp,
|
||||||
Compile.akkaStream,
|
Compile.akkaStream,
|
||||||
Compile.playJson,
|
Compile.playJson,
|
||||||
Compile.slf4j,
|
Compile.slf4j
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val eclairRpcTest = List(
|
val eclairRpcTest = List(
|
||||||
@ -168,8 +192,7 @@ object Deps {
|
|||||||
Test.akkaStream,
|
Test.akkaStream,
|
||||||
Test.logback,
|
Test.logback,
|
||||||
Test.scalaTest,
|
Test.scalaTest,
|
||||||
Test.scalacheck,
|
Test.scalacheck
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val node = List(
|
val node = List(
|
||||||
@ -178,23 +201,20 @@ object Deps {
|
|||||||
Compile.joda,
|
Compile.joda,
|
||||||
Compile.slick,
|
Compile.slick,
|
||||||
Compile.slickHikari,
|
Compile.slickHikari,
|
||||||
Compile.sqlite,
|
Compile.sqlite
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val nodeTest = List(
|
val nodeTest = List(
|
||||||
Test.akkaTestkit,
|
Test.akkaTestkit,
|
||||||
Test.logback,
|
Test.logback,
|
||||||
Test.scalaTest,
|
Test.scalaTest
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val testkit = List(
|
val testkit = List(
|
||||||
Compile.slf4j,
|
Compile.slf4j,
|
||||||
Compile.scalacheck,
|
Compile.scalacheck,
|
||||||
Compile.scalaTest,
|
Compile.scalaTest,
|
||||||
Test.akkaTestkit,
|
Test.akkaTestkit
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val scripts = List(
|
val scripts = List(
|
||||||
@ -203,18 +223,15 @@ object Deps {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val wallet = List(
|
val wallet = List(
|
||||||
Test.ammonite,
|
|
||||||
Compile.uJson
|
Compile.uJson
|
||||||
)
|
)
|
||||||
|
|
||||||
val walletTest = List(
|
val walletTest = List(
|
||||||
Test.logback,
|
Test.logback,
|
||||||
Test.akkaTestkit,
|
Test.akkaTestkit
|
||||||
Test.ammonite
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val docs = List(
|
val docs = List(
|
||||||
Compile.ammonite,
|
|
||||||
Compile.logback,
|
Compile.logback,
|
||||||
Test.scalaTest,
|
Test.scalaTest,
|
||||||
Test.logback
|
Test.logback
|
||||||
|
@ -0,0 +1,65 @@
|
|||||||
|
package org.bitcoins.testkit
|
||||||
|
|
||||||
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
|
import com.typesafe.config._
|
||||||
|
import java.nio.file._
|
||||||
|
|
||||||
|
object BitcoinSTestAppConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App configuration suitable for test purposes:
|
||||||
|
*
|
||||||
|
* 1) Data directory is set to user temp directory
|
||||||
|
*/
|
||||||
|
def getTestConfig(config: Config*): BitcoinSAppConfig = {
|
||||||
|
val tmpDir = Files.createTempDirectory("bitcoin-s-")
|
||||||
|
val confStr = s"""
|
||||||
|
| bitcoin-s {
|
||||||
|
| datadir = $tmpDir
|
||||||
|
| }
|
||||||
|
|
|
||||||
|
|""".stripMargin
|
||||||
|
val conf = ConfigFactory.parseString(confStr)
|
||||||
|
val allConfs = conf +: config
|
||||||
|
BitcoinSAppConfig(allConfs: _*)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed trait ProjectType
|
||||||
|
|
||||||
|
object ProjectType {
|
||||||
|
case object Wallet extends ProjectType
|
||||||
|
case object Node extends ProjectType
|
||||||
|
case object Chain extends ProjectType
|
||||||
|
|
||||||
|
val all = List(Wallet, Node, Chain)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Generates a Typesafe config with DBs set to memory
|
||||||
|
* databases for the given project (or all, if no
|
||||||
|
* project is given). This configuration can then be
|
||||||
|
* given as a override to other configs.
|
||||||
|
*/
|
||||||
|
def configWithMemoryDb(project: Option[ProjectType]): Config = {
|
||||||
|
def memConfigForProject(project: ProjectType): String = {
|
||||||
|
val name = project.toString().toLowerCase()
|
||||||
|
s"""
|
||||||
|
| $name.db {
|
||||||
|
| url = "jdbc:sqlite:file:$name.db:?mode=memory&cache=shared"
|
||||||
|
| connectionPool = disabled
|
||||||
|
| keepAliveConnection = true
|
||||||
|
| }
|
||||||
|
|""".stripMargin
|
||||||
|
}
|
||||||
|
|
||||||
|
val confStr = project match {
|
||||||
|
case None => ProjectType.all.map(memConfigForProject).mkString("\n")
|
||||||
|
case Some(p) => memConfigForProject(p)
|
||||||
|
}
|
||||||
|
val nestedConfStr = s"""
|
||||||
|
| bitcoin-s {
|
||||||
|
| $confStr
|
||||||
|
| }
|
||||||
|
|""".stripMargin
|
||||||
|
ConfigFactory.parseString(nestedConfStr)
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,6 @@ import org.bitcoins.testkit.chain.fixture._
|
|||||||
import org.bitcoins.testkit.fixtures.BitcoinSFixture
|
import org.bitcoins.testkit.fixtures.BitcoinSFixture
|
||||||
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||||
import org.bitcoins.zmq.ZMQSubscriber
|
import org.bitcoins.zmq.ZMQSubscriber
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
|
||||||
import org.scalatest._
|
import org.scalatest._
|
||||||
import play.api.libs.json.{JsError, JsSuccess, Json}
|
import play.api.libs.json.{JsError, JsSuccess, Json}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.ByteVector
|
||||||
@ -29,6 +28,7 @@ import scala.annotation.tailrec
|
|||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import org.bitcoins.db.AppConfig
|
import org.bitcoins.db.AppConfig
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
|
||||||
trait ChainUnitTest
|
trait ChainUnitTest
|
||||||
extends org.scalatest.fixture.AsyncFlatSpec
|
extends org.scalatest.fixture.AsyncFlatSpec
|
||||||
@ -46,7 +46,7 @@ trait ChainUnitTest
|
|||||||
implicit lazy val chainParam: ChainParams = appConfig.chain
|
implicit lazy val chainParam: ChainParams = appConfig.chain
|
||||||
|
|
||||||
implicit lazy val appConfig: ChainAppConfig =
|
implicit lazy val appConfig: ChainAppConfig =
|
||||||
BitcoinSAppConfig.getTestConfig()
|
BitcoinSTestAppConfig.getTestConfig()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Behaves exactly like the default conf, execpt
|
* Behaves exactly like the default conf, execpt
|
||||||
@ -54,7 +54,7 @@ trait ChainUnitTest
|
|||||||
*/
|
*/
|
||||||
lazy val mainnetAppConfig: ChainAppConfig = {
|
lazy val mainnetAppConfig: ChainAppConfig = {
|
||||||
val mainnetConf = ConfigFactory.parseString("bitcoin-s.network = mainnet")
|
val mainnetConf = ConfigFactory.parseString("bitcoin-s.network = mainnet")
|
||||||
BitcoinSAppConfig.getTestConfig(mainnetConf)
|
BitcoinSTestAppConfig.getTestConfig(mainnetConf)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def beforeAll(): Unit = {
|
override def beforeAll(): Unit = {
|
||||||
|
@ -14,8 +14,8 @@ import org.bitcoins.node.networking.peer.{
|
|||||||
PeerMessageSender
|
PeerMessageSender
|
||||||
}
|
}
|
||||||
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig._
|
import org.bitcoins.server.BitcoinSAppConfig._
|
||||||
import org.bitcoins.testkit.chain.ChainUnitTest
|
import org.bitcoins.testkit.chain.ChainUnitTest
|
||||||
import org.bitcoins.testkit.fixtures.BitcoinSFixture
|
import org.bitcoins.testkit.fixtures.BitcoinSFixture
|
||||||
import org.bitcoins.testkit.node.fixture.SpvNodeConnectedWithBitcoind
|
import org.bitcoins.testkit.node.fixture.SpvNodeConnectedWithBitcoind
|
||||||
@ -29,6 +29,7 @@ import org.scalatest.{
|
|||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
|
||||||
trait NodeUnitTest
|
trait NodeUnitTest
|
||||||
extends BitcoinSFixture
|
extends BitcoinSFixture
|
||||||
@ -57,7 +58,7 @@ trait NodeUnitTest
|
|||||||
|
|
||||||
/** Wallet config with data directory set to user temp directory */
|
/** Wallet config with data directory set to user temp directory */
|
||||||
implicit protected lazy val config: BitcoinSAppConfig =
|
implicit protected lazy val config: BitcoinSAppConfig =
|
||||||
BitcoinSAppConfig.getTestConfig()
|
BitcoinSTestAppConfig.getTestConfig()
|
||||||
|
|
||||||
implicit lazy val np: NetworkParameters = config.nodeConf.network
|
implicit lazy val np: NetworkParameters = config.nodeConf.network
|
||||||
|
|
||||||
|
@ -19,9 +19,10 @@ import org.scalatest._
|
|||||||
import scala.concurrent.duration.{DurationInt, FiniteDuration}
|
import scala.concurrent.duration.{DurationInt, FiniteDuration}
|
||||||
import scala.concurrent.{ExecutionContext, Future}
|
import scala.concurrent.{ExecutionContext, Future}
|
||||||
import org.bitcoins.db.AppConfig
|
import org.bitcoins.db.AppConfig
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
|
||||||
trait BitcoinSWalletTest
|
trait BitcoinSWalletTest
|
||||||
extends fixture.AsyncFlatSpec
|
extends fixture.AsyncFlatSpec
|
||||||
@ -35,7 +36,7 @@ trait BitcoinSWalletTest
|
|||||||
|
|
||||||
/** Wallet config with data directory set to user temp directory */
|
/** Wallet config with data directory set to user temp directory */
|
||||||
implicit protected lazy val config: BitcoinSAppConfig =
|
implicit protected lazy val config: BitcoinSAppConfig =
|
||||||
BitcoinSAppConfig.getTestConfig()
|
BitcoinSTestAppConfig.getTestConfig()
|
||||||
|
|
||||||
/** Timeout for async operations */
|
/** Timeout for async operations */
|
||||||
protected val timeout: FiniteDuration = 10.seconds
|
protected val timeout: FiniteDuration = 10.seconds
|
||||||
|
@ -2,8 +2,8 @@ package org.bitcoins.testkit.db
|
|||||||
|
|
||||||
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
import org.bitcoins.testkit.util.BitcoinSUnitTest
|
||||||
import org.bitcoins.testkit.Implicits._
|
import org.bitcoins.testkit.Implicits._
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig._
|
import org.bitcoins.server.BitcoinSAppConfig._
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import org.bitcoins.core.config.TestNet3
|
import org.bitcoins.core.config.TestNet3
|
||||||
import org.bitcoins.chain.models.BlockHeaderDAO
|
import org.bitcoins.chain.models.BlockHeaderDAO
|
||||||
@ -26,6 +26,7 @@ import org.bitcoins.db.SQLiteTableInfo
|
|||||||
import slick.jdbc.SQLiteProfile.api._
|
import slick.jdbc.SQLiteProfile.api._
|
||||||
import org.bitcoins.db.CRUD
|
import org.bitcoins.db.CRUD
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
|
||||||
class AppConfigTest extends BitcoinSUnitTest {
|
class AppConfigTest extends BitcoinSUnitTest {
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ class AppConfigTest extends BitcoinSUnitTest {
|
|||||||
val networkOverride =
|
val networkOverride =
|
||||||
ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
||||||
|
|
||||||
val config = BitcoinSAppConfig.getTestConfig(networkOverride)
|
val config = BitcoinSTestAppConfig.getTestConfig(networkOverride)
|
||||||
val chainConf = config.chainConf
|
val chainConf = config.chainConf
|
||||||
val walletConf = config.walletConf
|
val walletConf = config.walletConf
|
||||||
val nodeConf = config.nodeConf
|
val nodeConf = config.nodeConf
|
||||||
@ -52,7 +53,7 @@ class AppConfigTest extends BitcoinSUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it must "have the same DB path" in {
|
it must "have the same DB path" in {
|
||||||
val conf = BitcoinSAppConfig.getTestConfig()
|
val conf = BitcoinSTestAppConfig.getTestConfig()
|
||||||
val chainConf = conf.chainConf
|
val chainConf = conf.chainConf
|
||||||
val walletConf = conf.walletConf
|
val walletConf = conf.walletConf
|
||||||
val nodeConf = conf.nodeConf
|
val nodeConf = conf.nodeConf
|
||||||
@ -61,7 +62,7 @@ class AppConfigTest extends BitcoinSUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it must "have distinct databases" in {
|
it must "have distinct databases" in {
|
||||||
val conf = BitcoinSAppConfig.getTestConfig()
|
val conf = BitcoinSTestAppConfig.getTestConfig()
|
||||||
val chainConf = conf.chainConf
|
val chainConf = conf.chainConf
|
||||||
val walletConf = conf.walletConf
|
val walletConf = conf.walletConf
|
||||||
val nodeConf = conf.nodeConf
|
val nodeConf = conf.nodeConf
|
||||||
@ -70,7 +71,7 @@ class AppConfigTest extends BitcoinSUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
it must "be able to write to distinct databases" in {
|
it must "be able to write to distinct databases" in {
|
||||||
implicit val config = BitcoinSAppConfig.getTestConfig()
|
implicit val config = BitcoinSTestAppConfig.getTestConfig()
|
||||||
val chainConf = config.chainConf
|
val chainConf = config.chainConf
|
||||||
val walletConf = config.walletConf
|
val walletConf = config.walletConf
|
||||||
val nodeConf = config.nodeConf
|
val nodeConf = config.nodeConf
|
||||||
|
@ -21,7 +21,7 @@ import org.bitcoins.core.hd.HDCoin
|
|||||||
import org.bitcoins.core.hd.HDChainType
|
import org.bitcoins.core.hd.HDChainType
|
||||||
import org.bitcoins.core.hd.HDPurposes
|
import org.bitcoins.core.hd.HDPurposes
|
||||||
import org.bitcoins.wallet.config.WalletAppConfig
|
import org.bitcoins.wallet.config.WalletAppConfig
|
||||||
import org.bitcoins.testkit.BitcoinSAppConfig
|
import org.bitcoins.server.BitcoinSAppConfig
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import com.typesafe.config.ConfigFactory
|
import com.typesafe.config.ConfigFactory
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.ActorSystem
|
||||||
@ -39,6 +39,7 @@ import org.bitcoins.wallet.models.AccountDb
|
|||||||
import _root_.akka.actor.Address
|
import _root_.akka.actor.Address
|
||||||
import org.scalatest.compatible.Assertion
|
import org.scalatest.compatible.Assertion
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||||
|
|
||||||
class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
|
class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
|
||||||
|
|
||||||
@ -189,7 +190,7 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
|
|||||||
private def testAccountType(purpose: HDPurpose): Future[Assertion] = {
|
private def testAccountType(purpose: HDPurpose): Future[Assertion] = {
|
||||||
val confOverride = configForPurpose(purpose)
|
val confOverride = configForPurpose(purpose)
|
||||||
implicit val conf: WalletAppConfig =
|
implicit val conf: WalletAppConfig =
|
||||||
BitcoinSAppConfig.getTestConfig(confOverride)
|
BitcoinSTestAppConfig.getTestConfig(confOverride)
|
||||||
|
|
||||||
val vectors = purpose match {
|
val vectors = purpose match {
|
||||||
case HDPurposes.Legacy => legacyVectors
|
case HDPurposes.Legacy => legacyVectors
|
||||||
|
@ -68,6 +68,9 @@ class WalletIntegrationTest extends BitcoinSWalletTest {
|
|||||||
// it should not be confirmed
|
// it should not be confirmed
|
||||||
utxosPostAdd <- wallet.listUtxos()
|
utxosPostAdd <- wallet.listUtxos()
|
||||||
_ = assert(utxosPostAdd.length == 1)
|
_ = assert(utxosPostAdd.length == 1)
|
||||||
|
_ <- wallet
|
||||||
|
.getConfirmedBalance()
|
||||||
|
.map(confirmed => assert(confirmed == 0.bitcoin))
|
||||||
_ <- wallet
|
_ <- wallet
|
||||||
.getConfirmedBalance()
|
.getConfirmedBalance()
|
||||||
.map(confirmed => assert(confirmed == 0.bitcoin))
|
.map(confirmed => assert(confirmed == 0.bitcoin))
|
||||||
|
@ -19,6 +19,12 @@ import org.bitcoins.core.crypto.AesIV
|
|||||||
// what do we do if seed exists? error if they aren't equal?
|
// what do we do if seed exists? error if they aren't equal?
|
||||||
object WalletStorage extends BitcoinSLogger {
|
object WalletStorage extends BitcoinSLogger {
|
||||||
|
|
||||||
|
/** Checks if a wallet seed exists in datadir */
|
||||||
|
def seedExists()(implicit config: WalletAppConfig): Boolean = {
|
||||||
|
val seedPath = config.datadir.resolve(ENCRYPTED_SEED_FILE_NAME)
|
||||||
|
Files.exists(seedPath)
|
||||||
|
}
|
||||||
|
|
||||||
private[wallet] val ENCRYPTED_SEED_FILE_NAME: String =
|
private[wallet] val ENCRYPTED_SEED_FILE_NAME: String =
|
||||||
"encrypted_bitcoin-s_seed.json"
|
"encrypted_bitcoin-s_seed.json"
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user