Bump Scala versions (#697)

* Bump Scala versions

Support Scala 2.12.9
and 2.13.0.

To make this easier, we delete the `scripts` project. Everything
that was in here was covered by content on the website. We also
delete the `doc` folder, as that was a remnant from when `scripts`
was called `doc`.

* Crib uPickle akka-http support while we wait for publish

* Fix compiler warnings

* Add note on test logging to contribution guide

* Reduce duplication in Blockchain implementation

* Use Scala 2.12 for website

* Introduce compat package object for collections converters

* Fix Either compiler warnings

* Add sync-chain and create-wallet docs from deleted scripts

* Fix rebase goofup
This commit is contained in:
Torkel Rogstad 2019-08-23 20:53:00 +02:00 committed by Chris Stewart
parent f0c9432601
commit a303818c1e
71 changed files with 877 additions and 941 deletions

View file

@ -16,7 +16,8 @@ os:
scala:
- 2.11.12
- 2.12.8
- 2.12.9
- 2.13.0
# These directories are cached to S3 at the end of the build
cache:
@ -70,7 +71,7 @@ jobs:
include:
- stage: test
name: Compile website
script: sbt docs/mdoc
script: sbt ++2.12.9 docs/mdoc
# run ci-release only if previous stages passed
- stage: release
jdk: openjdk8
@ -79,5 +80,5 @@ jobs:
# run website push only if previous stages passed
# we use custom sbt task that first compiles Scaladocs
# and then calls the docusaurusPublishGhpages task
- script: sbt docs/publishWebsite
- script: sbt ++2.12.9 docs/publishWebsite
name: Publish website

View file

@ -1,6 +1,6 @@
name := "bitcoin-s-cli"
libraryDependencies ++= Deps.cli
libraryDependencies ++= Deps.cli(scalaVersion.value)
graalVMNativeImageOptions ++= Seq(
"-H:EnableURLProtocols=http",

View file

@ -5,8 +5,6 @@ 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 {

View file

@ -1,3 +1,3 @@
name := "bitcoin-s-app-picklers"
libraryDependencies ++= Deps.picklers
libraryDependencies ++= Deps.picklers(scalaVersion.value)

View file

@ -2,7 +2,6 @@ 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 {

View file

@ -4,4 +4,4 @@ name := "bitcoin-s-server"
// when server is quit
Compile / fork := true
libraryDependencies ++= Deps.server
libraryDependencies ++= Deps.server(scalaVersion.value)

View file

@ -0,0 +1,72 @@
/*
* Copyright 2015 Heiko Seeberger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.heikoseeberger.akkahttpupickle
import akka.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
import akka.http.scaladsl.model.ContentTypeRange
import akka.http.scaladsl.model.MediaType
import akka.http.scaladsl.model.MediaTypes.`application/json`
import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
import akka.util.ByteString
import upickle.default.{Reader, Writer, read, write}
import scala.collection.immutable.Seq
/**
* Automatic to and from JSON marshalling/unmarshalling using *upickle* protocol.
*/
object UpickleSupport extends UpickleSupport
/**
* Automatic to and from JSON marshalling/unmarshalling using *upickle* protocol.
*/
trait UpickleSupport {
def unmarshallerContentTypes: Seq[ContentTypeRange] =
mediaTypes.map(ContentTypeRange.apply)
def mediaTypes: Seq[MediaType.WithFixedCharset] =
List(`application/json`)
private val jsonStringUnmarshaller =
Unmarshaller.byteStringUnmarshaller
.forContentTypes(unmarshallerContentTypes: _*)
.mapWithCharset {
case (ByteString.empty, _) => throw Unmarshaller.NoContentException
case (data, charset) => data.decodeString(charset.nioCharset.name)
}
private val jsonStringMarshaller =
Marshaller.oneOf(mediaTypes: _*)(Marshaller.stringMarshaller)
/**
* HTTP entity => `A`
*
* @tparam A type to decode
* @return unmarshaller for `A`
*/
implicit def unmarshaller[A: Reader]: FromEntityUnmarshaller[A] =
jsonStringUnmarshaller.map(read(_))
/**
* `A` => HTTP entity
*
* @tparam A type to encode
* @return marshaller for any `A` value
*/
implicit def marshaller[A: Writer]: ToEntityMarshaller[A] =
jsonStringMarshaller.compose(write(_))
}

View file

@ -4,11 +4,9 @@ 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)

View file

@ -138,10 +138,15 @@ trait TransactionRpc { self: Client =>
comment: String = "",
subtractFeeFrom: Vector[BitcoinAddress] = Vector.empty): Future[
DoubleSha256DigestBE] = {
val jsonOutputs: JsValue = Json.toJson {
amounts.map {
case (addr, curr) => addr -> Bitcoins(curr.satoshis)
}
}
bitcoindCall[DoubleSha256DigestBE](
"sendmany",
List(JsString(""),
Json.toJson(amounts.mapValues(curr => Bitcoins(curr.satoshis))),
jsonOutputs,
JsNumber(minconf),
JsString(comment),
Json.toJson(subtractFeeFrom))

View file

@ -47,12 +47,17 @@ trait V17PsbtRpc { self: Client =>
outputs: Map[BitcoinAddress, CurrencyUnit],
locktime: Int = 0,
replacable: Boolean = false): Future[String] = {
bitcoindCall[String](
"createpsbt",
List(Json.toJson(inputs),
Json.toJson(outputs.mapValues(curr => Bitcoins(curr.satoshis))),
JsNumber(locktime),
JsBoolean(replacable)))
val outputsJson =
Json.toJson {
outputs.map {
case (addr, curr) => addr -> Bitcoins(curr.satoshis)
}
}
bitcoindCall[String]("createpsbt",
List(Json.toJson(inputs),
outputsJson,
JsNumber(locktime),
JsBoolean(replacable)))
}
def combinePsbt(psbts: Vector[String]): Future[String] = {
@ -72,15 +77,20 @@ trait V17PsbtRpc { self: Client =>
locktime: Int = 0,
options: WalletCreateFundedPsbtOptions = WalletCreateFundedPsbtOptions(),
bip32derivs: Boolean = false
): Future[WalletCreateFundedPsbtResult] =
): Future[WalletCreateFundedPsbtResult] = {
val jsonOutputs =
Json.toJson {
outputs.map { case (addr, curr) => addr -> Bitcoins(curr.satoshis) }
}
bitcoindCall[WalletCreateFundedPsbtResult](
"walletcreatefundedpsbt",
List(Json.toJson(inputs),
Json.toJson(outputs.mapValues(curr => Bitcoins(curr.satoshis))),
jsonOutputs,
JsNumber(locktime),
Json.toJson(options),
Json.toJson(bip32derivs))
)
}
def walletProcessPsbt(
psbt: String,

View file

@ -20,7 +20,7 @@ sealed trait BitcoindAuthCredentials {
}
object BitcoindAuthCredentials extends BitcoinSLogger {
import scala.collection.JavaConverters._
import org.bitcoins.core.compat.JavaConverters._
/**
* Authenticate by providing a username and password.

View file

@ -299,7 +299,7 @@ object BitcoindConfig extends BitcoinSLogger {
* by splitting it on newlines
*/
def apply(config: String, datadir: File): BitcoindConfig =
apply(config.split("\n"), datadir)
apply(config.split("\n").toList, datadir)
/** Reads the given path and construct a `bitcoind` config from it */
def apply(config: Path): BitcoindConfig =
@ -307,7 +307,7 @@ object BitcoindConfig extends BitcoinSLogger {
/** Reads the given file and construct a `bitcoind` config from it */
def apply(config: File, datadir: File = DEFAULT_DATADIR): BitcoindConfig = {
import scala.collection.JavaConverters._
import org.bitcoins.core.compat.JavaConverters._
val lines = Files
.readAllLines(config.toPath)
.iterator()
@ -369,7 +369,7 @@ object BitcoindConfig extends BitcoinSLogger {
Files.createDirectories(datadir.toPath)
val confFile = datadir.toPath.resolve("bitcoin.conf")
if (datadir == DEFAULT_DATADIR && confFile == DEFAULT_CONF_FILE) {
if (datadir == DEFAULT_DATADIR && confFile == DEFAULT_CONF_FILE.toPath) {
logger.warn(
s"We will not overrwrite the existing bitcoin.conf in default datadir")
} else {

View file

@ -2,7 +2,8 @@ package org.bitcoins.rpc.serializers
import java.io.File
import java.net.{InetAddress, URI}
import java.time.{LocalDateTime, ZoneId, ZoneOffset}
import java.time.LocalDateTime
import java.time.ZoneOffset
import org.bitcoins.core.crypto._
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
@ -30,6 +31,7 @@ import org.bitcoins.rpc.serializers.JsonSerializers._
import play.api.libs.json._
import scala.util.{Failure, Success}
import org.bitcoins.core.config._
object JsonReaders {
@ -42,11 +44,14 @@ object JsonReaders {
implicit readsK: Reads[K],
readsV: Reads[V]): JsResult[Map[K, V]] = {
js.validate[JsObject].flatMap { jsObj =>
val jsResults: Seq[(JsResult[K], JsResult[V])] = jsObj.fields.map {
case (key, value) => JsString(key).validate[K] -> value.validate[V]
}
val jsResults: scala.collection.Seq[(JsResult[K], JsResult[V])] =
jsObj.fields.map {
case (key, value) => JsString(key).validate[K] -> value.validate[V]
}
val allErrors: Seq[(JsPath, Seq[JsonValidationError])] =
val allErrors: scala.collection.Seq[(
JsPath,
scala.collection.Seq[JsonValidationError])] =
jsResults.collect {
case (JsError(keyErrors), _) => keyErrors
case (_, JsError(valueErrors)) => valueErrors
@ -70,7 +75,7 @@ object JsonReaders {
implicit object BigIntReads extends Reads[BigInt] {
override def reads(json: JsValue): JsResult[BigInt] =
SerializerUtil.processJsNumber[BigInt](_.toBigInt())(json)
SerializerUtil.processJsNumber[BigInt](_.toBigInt)(json)
}
implicit object Sha256DigestReads extends Reads[Sha256Digest] {
@ -122,7 +127,7 @@ object JsonReaders {
implicit object Int32Reads extends Reads[Int32] {
override def reads(json: JsValue): JsResult[Int32] = json match {
case JsNumber(n) =>
n.toBigIntExact() match {
n.toBigIntExact match {
case Some(num) => JsSuccess(Int32(num))
case None => SerializerUtil.buildErrorMsg("Int32", n)
}
@ -135,7 +140,7 @@ object JsonReaders {
implicit object UInt32Reads extends Reads[UInt32] {
override def reads(json: JsValue): JsResult[UInt32] = json match {
case JsNumber(n) =>
n.toBigIntExact() match {
n.toBigIntExact match {
case Some(num) =>
if (num >= 0) {
JsSuccess(UInt32(num))
@ -153,7 +158,7 @@ object JsonReaders {
implicit object UInt64Reads extends Reads[UInt64] {
override def reads(json: JsValue): JsResult[UInt64] = json match {
case JsNumber(n) =>
n.toBigIntExact() match {
n.toBigIntExact match {
case Some(num) =>
if (num >= 0) {
JsSuccess(UInt64(num))
@ -353,9 +358,10 @@ object JsonReaders {
case None => JsError("error.expected.balance")
}
bitcoinResult.flatMap { bitcoins =>
val jsStrings: IndexedSeq[String] = array.value
.filter(_.isInstanceOf[JsString])
.map(_.asInstanceOf[JsString].value)
val jsStrings =
array.value
.filter(_.isInstanceOf[JsString])
.map(_.asInstanceOf[JsString].value)
val addressResult = jsStrings.find(BitcoinAddress.isValid) match {
case Some(s) =>
BitcoinAddress.fromString(s) match {
@ -518,7 +524,7 @@ object JsonReaders {
implicit object BitcoinFeeUnitReads extends Reads[BitcoinFeeUnit] {
override def reads(json: JsValue): JsResult[BitcoinFeeUnit] =
SerializerUtil.processJsNumber[BitcoinFeeUnit](num =>
SatoshisPerByte(Satoshis(Int64((num * 100000).toBigInt()))))(json)
SatoshisPerByte(Satoshis(Int64((num * 100000).toBigInt))))(json)
}
implicit object FileReads extends Reads[File] {

View file

@ -1,7 +1,7 @@
package org.bitcoins.rpc.serializers
import org.bitcoins.core.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit}
import org.bitcoins.core.currency.Bitcoins
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
@ -101,7 +101,7 @@ object JsonWriters {
def addToMapIfDefined[T](key: String, opt: Option[T])(
implicit writes: Writes[T]): Unit =
opt.foreach(o => jsOpts + (key -> Json.toJson(o)))
opt.foreach(o => jsOpts += (key -> Json.toJson(o)))
addToMapIfDefined("changeAddress", opts.changeAddress)
addToMapIfDefined("changePosition", opts.changePosition)

View file

@ -7,7 +7,7 @@ sealed abstract class SerializerUtil {
def processJsNumberBigInt[T](numFunc: BigInt => T)(
json: JsValue): JsResult[T] = json match {
case JsNumber(nDecimal) =>
val nOpt = nDecimal.toBigIntExact()
val nOpt = nDecimal.toBigIntExact
nOpt match {
case Some(t) => JsSuccess(numFunc(t))
case None =>

View file

@ -11,31 +11,40 @@ Test / flywayClean / aggregate := true
lazy val commonCompilerOpts = {
List(
"-Xmax-classfile-name",
"128",
"-Xsource:2.12",
"-target:jvm-1.8"
)
}
//https://docs.scala-lang.org/overviews/compiler-options/index.html
lazy val compilerOpts = Seq(
"-encoding",
"UTF-8",
"-unchecked",
"-feature",
"-deprecation",
"-Xfuture",
"-Ywarn-dead-code",
"-Ywarn-unused-import",
"-Ywarn-value-discard",
"-Ywarn-unused",
"-unchecked",
"-deprecation",
"-feature"
) ++ commonCompilerOpts
val scala2_13CompilerOpts = Seq("-Xlint:unused")
lazy val testCompilerOpts = commonCompilerOpts
val nonScala2_13CompilerOpts = Seq(
"-Xmax-classfile-name",
"128",
"-Ywarn-unused",
"-Ywarn-unused-import"
)
//https://docs.scala-lang.org/overviews/compiler-options/index.html
def compilerOpts(scalaVersion: String) =
Seq(
"-encoding",
"UTF-8",
"-unchecked",
"-feature",
"-deprecation",
"-Ywarn-dead-code",
"-Ywarn-value-discard",
"-Ywarn-unused",
"-unchecked",
"-deprecation",
"-feature"
) ++ commonCompilerOpts ++ {
if (scalaVersion.startsWith("2.13")) scala2_13CompilerOpts
else nonScala2_13CompilerOpts
}
val testCompilerOpts = commonCompilerOpts
lazy val isCI = {
sys.props
@ -67,7 +76,7 @@ lazy val commonSettings = List(
apiURL := homepage.value.map(_.toString + "/api").map(url(_)),
// scaladoc settings end
////
scalacOptions in Compile := compilerOpts,
scalacOptions in Compile := compilerOpts(scalaVersion.value),
scalacOptions in Test := testCompilerOpts,
Compile / compile / javacOptions ++= {
if (isCI) {
@ -146,7 +155,6 @@ lazy val `bitcoin-s` = project
walletServer,
walletServerTest,
testkit,
scripts,
zmq
)
.settings(commonSettings: _*)
@ -381,7 +389,7 @@ lazy val bitcoindRpc = project
lazy val bitcoindRpcTest = project
.in(file("bitcoind-rpc-test"))
.settings(commonTestSettings: _*)
.settings(libraryDependencies ++= Deps.bitcoindRpcTest,
.settings(libraryDependencies ++= Deps.bitcoindRpcTest(scalaVersion.value),
name := "bitcoin-s-bitcoind-rpc-test")
.dependsOn(core % testAndCompile, testkit)
@ -486,7 +494,7 @@ lazy val wallet = project
.settings(walletDbSettings: _*)
.settings(
name := "bitcoin-s-wallet",
libraryDependencies ++= Deps.wallet
libraryDependencies ++= Deps.wallet(scalaVersion.value)
)
.dependsOn(core, dbCommons)
.enablePlugins(FlywayPlugin)
@ -502,25 +510,6 @@ lazy val walletTest = project
.dependsOn(core % testAndCompile, testkit, wallet)
.enablePlugins(FlywayPlugin)
lazy val scripts = project
.in(file("scripts"))
.settings(commonTestSettings: _*)
.settings(
name := "bitcoin-s-scripts",
libraryDependencies ++= Deps.scripts
)
.dependsOn(
bitcoindRpc,
chain,
core % testAndCompile,
eclairRpc,
node,
secp256k1jni,
testkit,
wallet,
zmq
)
/** Given a database name, returns the appropriate
* Flyway settings we apply to a project (chain, node, wallet) */
def dbFlywaySettings(dbName: String): List[Setting[_]] = {

View file

@ -0,0 +1,30 @@
package org.bitcoins.chain.blockchain
import org.bitcoins.chain.models.BlockHeaderDb
import scala.collection.{IndexedSeqLike, mutable}
/** @inheritdoc */
case class Blockchain(headers: Vector[BlockHeaderDb])
extends IndexedSeqLike[BlockHeaderDb, Vector[BlockHeaderDb]]
with BaseBlockChain {
protected[blockchain] def compObjectfromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]): Blockchain =
Blockchain.fromHeaders(headers)
/** @inheritdoc */
override def newBuilder: mutable.Builder[
BlockHeaderDb,
Vector[BlockHeaderDb]] = Vector.newBuilder[BlockHeaderDb]
/** @inheritdoc */
override def seq: IndexedSeq[BlockHeaderDb] = headers
}
object Blockchain extends BaseBlockChainCompObject {
def fromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]): Blockchain =
Blockchain(headers.toVector)
}

View file

@ -4,10 +4,6 @@ import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
import scala.collection.mutable
/**
*
* @param blockHeaderDAO
*/
case class BlockchainBuilder(blockHeaderDAO: BlockHeaderDAO)
extends mutable.Builder[BlockHeaderDb, Blockchain] {
private val internal = Vector.newBuilder[BlockHeaderDb]

View file

@ -0,0 +1,32 @@
package org.bitcoins.chain.blockchain
import org.bitcoins.chain.models.BlockHeaderDb
import scala.collection.{IndexedSeqLike, mutable}
/** @inheritdoc */
case class Blockchain(headers: Vector[BlockHeaderDb])
extends IndexedSeqLike[BlockHeaderDb, Vector[BlockHeaderDb]]
with BaseBlockChain {
protected[blockchain] def compObjectfromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]): Blockchain =
Blockchain.fromHeaders(headers)
/** @inheritdoc */
override def newBuilder: mutable.Builder[
BlockHeaderDb,
Vector[BlockHeaderDb]] = Vector.newBuilder[BlockHeaderDb]
/** @inheritdoc */
override def seq: IndexedSeq[BlockHeaderDb] = headers
}
object Blockchain extends BaseBlockChainCompObject {
def fromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]): Blockchain =
Blockchain(headers.toVector)
}

View file

@ -0,0 +1,21 @@
package org.bitcoins.chain.blockchain
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb}
import scala.collection.mutable
case class BlockchainBuilder(blockHeaderDAO: BlockHeaderDAO)
extends mutable.Builder[BlockHeaderDb, Blockchain] {
private val internal = Vector.newBuilder[BlockHeaderDb]
override def result(): Blockchain = {
Blockchain.fromHeaders(internal.result().reverse)
}
override def +=(blockHeaderDb: BlockHeaderDb): this.type = {
internal.+=(blockHeaderDb)
this
}
override def clear(): Unit = internal.clear()
}

View file

@ -0,0 +1,26 @@
package org.bitcoins.chain.blockchain
import org.bitcoins.chain.models.BlockHeaderDb
import scala.collection.immutable.IndexedSeq
/** @inheritdoc */
case class Blockchain(headers: Vector[BlockHeaderDb])
extends IndexedSeq[BlockHeaderDb]
with BaseBlockChain {
protected[blockchain] def compObjectfromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]) =
Blockchain.fromHeaders(headers)
/** @inheritdoc */
override def seq = this
}
object Blockchain extends BaseBlockChainCompObject {
override def fromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]): Blockchain =
Blockchain(headers.toVector)
}

View file

@ -1,13 +1,23 @@
package org.bitcoins.chain.blockchain
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.models.BlockHeaderDb
import org.bitcoins.chain.validation.{TipUpdateResult, TipValidation}
import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.ChainVerificationLogger
import org.bitcoins.chain.validation.TipUpdateResult
import org.bitcoins.chain.validation.TipValidation
import scala.annotation.tailrec
import scala.collection.{IndexedSeqLike, mutable}
// INTERNAL NOTE: Due to changes in the Scala collections in 2.13 this
// class and its companion object
// has to be implemented separetely for the different Scala versions.
// The general idea is that all three implement a collection, but slightly
// different ones (the one that the 2.12 and 2.11 versions implement got
// removed in 2.13). The most interesting method is `compObjectFromHeaders`.
// This is a method that's meant to represent a `fromHeaders` method on the
// companion object. Because Scala has restrictions on where to place companion
// objects (they have to be in the same file as the trait/class), this was
// the least ugly workaround I could come up with.
/**
* In memory implementation of a blockchain
@ -21,23 +31,23 @@ import scala.collection.{IndexedSeqLike, mutable}
* }}}
*
*/
case class Blockchain(headers: Vector[BlockHeaderDb])
extends IndexedSeqLike[BlockHeaderDb, Vector[BlockHeaderDb]] {
private[blockchain] trait BaseBlockChain {
protected[blockchain] def compObjectfromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]): Blockchain
val tip: BlockHeaderDb = headers.head
/** @inheritdoc */
override def newBuilder: mutable.Builder[
BlockHeaderDb,
Vector[BlockHeaderDb]] = Vector.newBuilder[BlockHeaderDb]
/** The height of the chain */
val height: Int = tip.height
/** @inheritdoc */
override def seq: IndexedSeq[BlockHeaderDb] = headers
val length: Int = headers.length
/** @inheritdoc */
override def length: Int = headers.length
def apply(idx: Int): BlockHeaderDb = headers(idx)
/** @inheritdoc */
override def apply(idx: Int): BlockHeaderDb = headers.apply(idx)
def headers: Vector[BlockHeaderDb]
def find(predicate: BlockHeaderDb => Boolean): Option[BlockHeaderDb]
/** Finds a block header at a given height */
def findAtHeight(height: Int): Option[BlockHeaderDb] =
@ -48,7 +58,7 @@ case class Blockchain(headers: Vector[BlockHeaderDb])
val headerIdxOpt = headers.zipWithIndex.find(_._1 == header)
headerIdxOpt.map {
case (header, idx) =>
val newChain = Blockchain.fromHeaders(headers.splitAt(idx)._2)
val newChain = this.compObjectfromHeaders(headers.splitAt(idx)._2)
require(newChain.tip == header)
newChain
}
@ -58,17 +68,13 @@ case class Blockchain(headers: Vector[BlockHeaderDb])
def fromValidHeader(header: BlockHeaderDb): Blockchain = {
fromHeader(header).get
}
/** The height of the chain */
def height: Int = tip.height
}
object Blockchain extends ChainVerificationLogger {
private[blockchain] trait BaseBlockChainCompObject
extends ChainVerificationLogger {
def fromHeaders(headers: Vector[BlockHeaderDb]): Blockchain = {
Blockchain(headers)
}
def fromHeaders(
headers: scala.collection.immutable.Seq[BlockHeaderDb]): Blockchain
/**
* Attempts to connect the given block header with the given blockchain
@ -204,4 +210,5 @@ object Blockchain extends ChainVerificationLogger {
}
}
}

View file

@ -46,7 +46,7 @@ case class ChainAppConfig(
case Success(bool) =>
logger.debug(s"Chain project is initialized")
p.success(bool)
case Failure(err) =>
case Failure(_) =>
logger.info(s"Chain project is not initialized")
p.success(false)
}

View file

@ -44,7 +44,7 @@ class Bech32Spec extends Properties("Bech32Spec") {
val idx = rand % old.length
val (f, l) = old.splitAt(idx)
val replacementChar = pickReplacementChar(l.head)
val replaced = f ++ Seq(replacementChar) ++ l.tail
val replaced = s"$f$replacementChar${l.tail}"
//should fail because we replaced a char in the addr, so checksum invalid
Bech32Address.fromString(replaced).isFailure
}
@ -76,10 +76,14 @@ class Bech32Spec extends Properties("Bech32Spec") {
val (f, l) = addr.splitAt(idx)
if (l.head.isDigit) {
switchCaseRandChar(addr)
} else if (l.head.isUpper) {
f ++ Seq(l.head.toLower) ++ l.tail
} else {
f ++ Seq(l.head.toUpper) ++ l.tail
val middle =
if (l.head.isUpper) {
l.head.toLower
} else {
l.head.toUpper
}
s"$f$middle${l.tail}"
}
}
}

View file

@ -6,14 +6,6 @@ testOptions in Test += Tests.Argument(TestFrameworks.ScalaCheck,
"-verbosity",
"2")
//test in assembly := {}
//testOptions in Test += Tests.Argument("-oF")
//parallelExecution in Test := false
coverageExcludedPackages := ".*gen"
coverageMinimum := 90
coverageFailOnMinimum := true

View file

@ -0,0 +1,17 @@
package org.bitcoins.core
/** This package provides
* compatability functionality
* for compiling Scala 2.11, 2.12
* and 2.13, ideally without leading
* to any compiler warnings in any of
* the versions.
*/
package object compat {
/** Provides imports that allow converting
* Java collections to Scala collections
*/
val JavaConverters = scala.collection.JavaConverters
}

View file

@ -0,0 +1,17 @@
package org.bitcoins.core
/** This package provides
* compatability functionality
* for compiling Scala 2.11, 2.12
* and 2.13, ideally without leading
* to any compiler warnings in any of
* the versions.
*/
package object compat {
/** Provides imports that allow converting
* Java collections to Scala collections
*/
val JavaConverters = scala.collection.JavaConverters
}

View file

@ -0,0 +1,17 @@
package org.bitcoins.core
/** This package provides
* compatability functionality
* for compiling Scala 2.11, 2.12
* and 2.13, ideally without leading
* to any compiler warnings in any of
* the versions.
*/
package object compat {
/** Provides imports that allow converting
* Java collections to Scala collections
*/
val JavaConverters = scala.jdk.CollectionConverters
}

View file

@ -284,10 +284,11 @@ sealed abstract class BloomFilter extends NetworkElement {
private def murmurConstant = UInt32("fba4c795")
/** Adds a sequence of byte vectors to our bloom filter then returns that new filter*/
def insertByteVectors(bytes: Seq[ByteVector]): BloomFilter = {
def insertByteVectors(
bytes: scala.collection.Seq[ByteVector]): BloomFilter = {
@tailrec
def loop(
remainingByteVectors: Seq[ByteVector],
remainingByteVectors: scala.collection.Seq[ByteVector],
accumBloomFilter: BloomFilter): BloomFilter = {
if (remainingByteVectors.isEmpty) accumBloomFilter
else

View file

@ -0,0 +1,87 @@
package org.bitcoins.core.compat
import scala.util.Try
import scala.util.Success
import scala.util.Failure
/** This is an implementation of (parts of)
* `scala.util.Either`, compatible with Scala 2.11,
* 2.12 and 2.13. It is in large parts cribbed from
* the Scala 2.12 standard library.
*/
sealed private[bitcoins] trait CompatEither[A, B] {
protected val underlying: Either[A, B]
/** The given function is applied if this is a `Right`.
*
* {{{
* Right(12).map(x => "flower") // Result: Right("flower")
* Left(12).map(x => "flower") // Result: Left(12)
* }}}
*/
def map[B1](f: B => B1): CompatEither[A, B1] = underlying match {
case Right(b) => CompatRight(f(b))
case _ => this.asInstanceOf[CompatEither[A, B1]]
}
/** Binds the given function across `Right`.
*
* @param f The function to bind across `Right`.
*/
def flatMap[A1 >: A, B1](f: B => CompatEither[A1, B1]): CompatEither[A1, B1] =
underlying match {
case Right(b) =>
f(b) match {
case CompatLeft(value) => CompatLeft(value)
case CompatRight(value) => CompatRight(value)
}
case Left(l) => CompatLeft(l)
}
def toTry(implicit ev: A <:< Throwable): Try[B] = underlying match {
case Right(b) => Success(b)
case Left(a) => Failure(a)
}
/** Applies `fa` if this is a `Left` or `fb` if this is a `Right`.
*
* @example {{{
* val result = util.Try("42".toInt).toEither
* result.fold(
* e => s"Operation failed with $e",
* v => s"Operation produced value: $v"
* )
* }}}
*
* @param fa the function to apply if this is a `Left`
* @param fb the function to apply if this is a `Right`
* @return the results of applying the function
*/
def fold[C](fa: A => C, fb: B => C): C = underlying match {
case Right(b) => fb(b)
case Left(a) => fa(a)
}
}
object CompatEither {
/** Converts the given `scala.util.Either` to a `CompatEither` */
def apply[A, B](either: Either[A, B]): CompatEither[A, B] = either match {
case Left(value) => CompatLeft(value)
case Right(value) => CompatRight(value)
}
}
/** Analogous to `scala.util.Left` */
case class CompatLeft[A, B](value: A) extends CompatEither[A, B] {
val underlying = scala.util.Left[A, B](value)
}
/** Analogous to `scala.util.Right` */
case class CompatRight[A, B](value: B) extends CompatEither[A, B] {
val underlying = scala.util.Right[A, B](value)
}

View file

@ -267,7 +267,10 @@ object ExtPrivateKey extends Factory[ExtPrivateKey] {
* [[https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#master-key-generation BIP32]]
*/
private val BIP32_KEY: ByteVector =
ByteVector.encodeAscii("Bitcoin seed").right.get
ByteVector.encodeAscii("Bitcoin seed") match {
case Left(exception) => throw exception
case Right(bytevec) => bytevec
}
/**
* Generates a master private key

View file

@ -26,7 +26,10 @@ sealed abstract class LnHumanReadablePart extends Bech32HumanReadablePart {
}
lazy val bytes: ByteVector =
ByteVector.encodeAscii(chars).right.get
ByteVector.encodeAscii(chars) match {
case Left(exc) => throw exc
case Right(bytevec) => bytevec
}
override lazy val toString: String = chars
}

View file

@ -36,9 +36,7 @@ sealed abstract class LnPolicy {
private def calc(mul: LnMultiplier): BigInt = {
maxPicoBitcoins /
(mul.multiplier / LnMultiplier.Pico.multiplier)
.toBigIntExact()
.get
(mul.multiplier / LnMultiplier.Pico.multiplier).toBigIntExact.get
}
val DEFAULT_LN_P2P_PORT = 9735

View file

@ -2,7 +2,6 @@ package org.bitcoins.core.protocol.ln
import org.bitcoins.core.number.{UInt5, UInt8}
import org.bitcoins.core.protocol.NetworkElement
import org.bitcoins.core.protocol.ln.LnTag.PaymentHashTag
import org.bitcoins.core.protocol.ln.util.LnUtil
import org.bitcoins.core.util.Bech32
import scodec.bits.ByteVector
@ -92,10 +91,12 @@ object LnTaggedFields {
val (description, descriptionHash): (
Option[LnTag.DescriptionTag],
Option[LnTag.DescriptionHashTag]) = {
if (descriptionOrHash.isLeft) {
(descriptionOrHash.left.toOption, None)
} else {
(None, descriptionOrHash.right.toOption)
descriptionOrHash match {
case Left(description) =>
(Some(description), None)
case Right(hash) =>
(None, Some(hash))
}
}

View file

@ -91,7 +91,7 @@ sealed abstract class LnCurrencyUnit
* would appear as "100p"
*/
def toEncodedString: String = {
toBigInt + character.toString
toBigInt.toString + character.toString
}
override def toString(): String = s"$underlying ${character}BTC"

View file

@ -47,7 +47,7 @@ trait ScriptFlagFactory {
/** Parses a list of [[ScriptFlag]]s that is separated by commas. */
def fromList(str: String): Seq[ScriptFlag] = {
fromList(str.split(","))
fromList(str.split(",").toList)
}
/** Empty script flag. */

View file

@ -758,7 +758,7 @@ sealed abstract class ScriptInterpreter {
calcOpCount(opCount, OP_CHECKSEQUENCEVERIFY))
//no more script operations to run, return whether the program is valid and the final state of the program
case Nil => loop(ScriptProgram.toExecutedProgram(p), opCount)
case h :: _ => throw new RuntimeException(h + " was unmatched")
case h :: _ => throw new RuntimeException(s"$h was unmatched")
}
}
}

View file

@ -32,7 +32,7 @@ case class SatoshisPerKiloByte(currencyUnit: CurrencyUnit)
extends BitcoinFeeUnit {
def toSatPerByte: SatoshisPerByte = {
val conversionOpt = (currencyUnit.toBigDecimal * 0.001).toBigIntExact()
val conversionOpt = (currencyUnit.toBigDecimal * 0.001).toBigIntExact
conversionOpt match {
case Some(conversion) =>
val sat = Satoshis(Int64(conversion))

View file

@ -98,7 +98,7 @@ abstract class DbCommonsColumnMappers {
},
//this has the potential to throw
{ bigDec: BigDecimal =>
UInt64(bigDec.toBigIntExact().get)
UInt64(bigDec.toBigIntExact.get)
}
)
}

View file

@ -1,16 +0,0 @@
## Ammonite scripts
This project contain [Ammonite](https://ammonite.io) scripts that demonstrate
functionality of `bitcoin-s`.
#### Running them with sbt:
```bash
$ sbt "doc/run path/to/script.sc" # this is very slow, not recommended
```
#### Running them with the [Bloop CLI](https://scalacenter.github.io/bloop/):
```bash
$ bloop run doc --args path/to/script.sc # much faster than through sbt
```

View file

@ -1,40 +0,0 @@
# bitcoin-s configuration
bitcoin-s uses [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md)
to configure various parts of the application the library offers. HOCON is a
superset of JSON, that is, all valid JSON is valid HOCON.
All configuration for bitcoin-s is under the `bitcoin-s` key. The most interesting
configurable parts right now are `datadir` and `network`. See
[`db-commons/src/main/resources/reference.conf`](../db-commons/src/main/resources/reference.conf)
for more information. In the future there will be separate keys under `bitcoin-s`
for the `wallet`, `chain` and `node` modules.
If you have a file `application.conf` anywhere on your classpath when using
bitcoin-s, the values there take precedence over the ones found in our
`reference.conf`.
The resolved configuration gets parsed by
[`AppConfig`](../db-commons/src/main/scala/org/bitcoins/db/AppConfig.scala).
You can call the `.withOverrides` on this to override any value in the
bitcoin-s configuration. An example of this would be:
```scala
import org.bitcoins.wallet.config.WalletAppConfig
import com.typesafe.config.ConfigFactory
val myConfig = ConfigFactory.parseString("bitcoin-s.network = testnet3")
val walletConfig = WalletAppConfig.withOverrides(myConfig)
```
You can pass as many configs as you'd like into `withOverrides`. If any
keys appear multiple times the last one encountered. takes precedence.
## Internal configuration
Database connections are also configured by using HOCON. This is done in
[`db.conf`](../db-commons/src/main/resources/db.conf)
(as well as [`application.conf`](../testkit/src/main/resources/application.conf)
in `testkit` for running tests). The options exposed here are **not** intended to
be used by users of bitcoin-s, and are internal only.

View file

@ -1,13 +0,0 @@
## bitcoin-s databases
### node project
This contains information related to peer to peer networking and chainstate for the bitcoin-s project. You can see configuration for these databases [here](../node/src/main/resources/reference.conf)
Database names:
- `nodedb` - the mainnet database
- `nodedb-testnet3` - the testnet3 database
- `nodedb-regtest` - the regtest database
- `nodedb-unittest` - the database used by unit tests.

View file

@ -1,157 +0,0 @@
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.number.{Int32, Int64, UInt32}
import org.bitcoins.core.protocol.script.P2PKHScriptPubKey
import org.bitcoins.core.protocol.transaction.{
BaseTransaction,
Transaction,
TransactionOutPoint,
TransactionOutput
}
import org.bitcoins.core.script.crypto.HashType
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.core.wallet.utxo.BitcoinUTXOSpendingInfo
import org.scalatest.{FlatSpec, MustMatchers}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
class TxBuilderExample extends FlatSpec with MustMatchers {
behavior of "TxBuilderExample"
it must "build a signed tx" in {
//This is a documented example of how to create a signed bitcoin transaction
//with bitcoin-s. You can run this test case with the following sbt command
//$ sbt "doc/testOnly *TxBuilderExample -- -z signed"
//generate a fresh private key that we are going to use in the scriptpubkey
val privKey = ECPrivateKey.freshPrivateKey
//this is the script that the TxBuilder is going to create a
//script signature that validly spends this scriptPubKey
val creditingSpk = P2PKHScriptPubKey(pubKey = privKey.publicKey)
val amount = Satoshis(Int64(10000))
//this is the utxo we are going to be spending
val utxo =
TransactionOutput(currencyUnit = amount, scriptPubKey = creditingSpk)
//the private key that locks the funds for the script we are spending too
val destinationPrivKey = ECPrivateKey.freshPrivateKey
//the amount we are sending -- 5000 satoshis -- to the destinationSPK
val destinationAmount = Satoshis(Int64(5000))
//the script that corresponds to destination private key, this is what is protecting the money
val destinationSPK =
P2PKHScriptPubKey(pubKey = destinationPrivKey.publicKey)
//this is where we are sending money too
//we could add more destinations here if we
//wanted to batch transactions
val destinations = {
val destination1 = TransactionOutput(currencyUnit = destinationAmount,
scriptPubKey = destinationSPK)
List(destination1)
}
//we have to fabricate a transaction that contains the
//utxo we are trying to spend. If this were a real blockchain
//we would need to reference the utxo set
val creditingTx = BaseTransaction(version = Int32.one,
inputs = List.empty,
outputs = List(utxo),
lockTime = UInt32.zero)
//this is the information we need from the crediting tx
//to properly "link" it in the transaction we are creating
val outPoint = TransactionOutPoint(creditingTx.txId, UInt32.zero)
// this contains all the information we need to
// validly sign the utxo above
val utxoSpendingInfo = BitcoinUTXOSpendingInfo(outPoint = outPoint,
output = utxo,
signers = List(privKey),
redeemScriptOpt = None,
scriptWitnessOpt = None,
hashType =
HashType.sigHashAll)
//all of the utxo spending information, since we are only
//spending one utxo, this is just one element
val utxos: List[BitcoinUTXOSpendingInfo] = List(utxoSpendingInfo)
//this is how much we are going to pay as a fee to the network
//for this example, we are going to pay 1 satoshi per byte
val feeRate = SatoshisPerByte(Satoshis.one)
val changePrivKey = ECPrivateKey.freshPrivateKey
val changeSPK = P2PKHScriptPubKey(pubKey = changePrivKey.publicKey)
// the network we are on, for this example we are using
// the regression test network. This is a network you control
// on your own machine
val networkParams = RegTest
//yay! Now we have a TxBuilder object that we can use
//to sign the tx.
val txBuilder: Future[BitcoinTxBuilder] = {
BitcoinTxBuilder(
destinations = destinations,
utxos = utxos,
feeRate = feeRate,
changeSPK = changeSPK,
network = networkParams
)
}
txBuilder.failed.foreach { case err => println(err.getMessage) }
//let's finally produce a validly signed tx
//The 'sign' method is going produce a validly signed transaction
//This is going to iterate through each of the 'utxos' and use
//the corresponding 'UTXOSpendingInfo' to produce a validly
//signed input. This tx has a
//
//1 input
//2 outputs (destination and change outputs)
//3 a fee rate of 1 satoshi/byte
val signedTxF: Future[Transaction] = txBuilder.flatMap(_.sign)
//let's print these things out so you can example them
signedTxF.map { tx =>
println("\nInputs:")
tx.inputs.foreach(println)
println("\nOutputs:")
tx.outputs.foreach(println)
//here is the fully signed serialized tx that
//you COULD broadcast to a cryptocurrency p2p network
println(s"\nFully signed tx in hex:")
println(s"${tx.hex}")
}
//The output from the print statements should read something like this
//Inputs:
//TransactionInputImpl(TransactionOutPointImpl(DoubleSha256DigestImpl(43c75d1d59e6f13f2ad3baf6e124685ba0919bccdbdf89c362fe2f30fee4bdfc),UInt32Impl(0)),P2PKHScriptSignature(6a4730440220573a7bbbd59192c4bf01b8f1dcafe981d11ab8528fead9d66d702c1b72e5dc76022007946a423073c949e85a4ca3901ab10a2d6b72873a347d2a55ef873016adae8601210356d581971934349333066ed933cdea45ae9c72829ce34d8dd6a758d56967e4cb),UInt32Impl(0))
//
//Outputs:
//TransactionOutputImpl(SatoshisImpl(Int64Impl(5000)),P2PKHScriptPubKeyImpl(1976a914dbdadae42124c46a00d81181e5d9ab28fbf546ed88ac))
//TransactionOutputImpl(SatoshisImpl(Int64Impl(4774)),P2PKHScriptPubKeyImpl(1976a914a95eb0d284593f0c8f818f64a55fa6e3852012a688ac))
//
//Fully signed tx in hex:
//020000000143c75d1d59e6f13f2ad3baf6e124685ba0919bccdbdf89c362fe2f30fee4bdfc000000006a4730440220573a7bbbd59192c4bf01b8f1dcafe981d11ab8528fead9d66d702c1b72e5dc76022007946a423073c949e85a4ca3901ab10a2d6b72873a347d2a55ef873016adae8601210356d581971934349333066ed933cdea45ae9c72829ce34d8dd6a758d56967e4cb000000000288130000000000001976a914dbdadae42124c46a00d81181e5d9ab28fbf546ed88aca6120000000000001976a914a95eb0d284593f0c8f818f64a55fa6e3852012a688ac00000000
//remember, you can call .hex on any bitcoin-s data structure to get the hex representation!
}
}

View file

@ -1,82 +0,0 @@
import org.bitcoins.rpc.config._
import akka.actor.ActorSystem
import org.bitcoins.chain.db._
import org.bitcoins.chain.config._
import org.bitcoins.chain.blockchain._
import org.bitcoins.chain.blockchain.sync._
import org.bitcoins.chain.models._
import org.bitcoins.core.protocol.blockchain._
import org.bitcoins.rpc.client.common._
import org.bitcoins.testkit.chain._
import org.bitcoins.wallet._
import org.bitcoins.wallet.api._
import org.slf4j.LoggerFactory
import scala.collection.JavaConverters._
import scala.concurrent._
import scala.concurrent.duration.DurationInt
import scala.util._
//the goal for this script is to create a chain and sync it
//to disk after creation
//we should be able to read this chain on subsequent runs
//assuming we are connected to the same bitcoind instance
//you can run this script with
//$ sbt "doc/run doc/src/main/scala/org/bitcoins/doc/chain/sync-chain.sc"
//boring config stuff
val logger = LoggerFactory.getLogger("org.bitcoins.doc.chain.SyncChain")
val time = System.currentTimeMillis()
implicit val system = ActorSystem(s"sync-chain-${time}")
import system.dispatcher
//first we are assuming that a bitcoind regtest node is running in
//the background, you can see 'connect_bitcoind.sc' script
//to see how to bind to a local/remote bitcoind node
//This script assumes that you have a bitcoind instance running in the
//background and that you have ~/.bitcoin/bitcoin.conf setup.
//you need to have 'rpcuser' and 'rpcpassword' set in that bitcoin.conf file
//You can pass in an alternative datadir if you wish by construct a new java.io.File()
val bitcoindInstance = BitcoindInstance.fromDatadir()
val rpcCli = new BitcoindRpcClient(bitcoindInstance)
logger.info(s"Done configuring rpc client")
//next we need to create a way to monitor the chain
val getBestBlockHash = ChainTestUtil.bestBlockHashFnRpc(Future.successful(rpcCli))
val getBlockHeader = ChainTestUtil.getBlockHeaderFnRpc(Future.successful(rpcCli))
val chainDbConfig = ChainDbConfig.RegTestDbConfig
val chainAppConfig = ChainAppConfig(chainDbConfig)
logger.info(s"Creating chain tables")
//initialize chain tables in bitcoin-s if they do not exist
val chainProjectInitF = ChainTestUtil.initializeIfNeeded(chainAppConfig)
val blockHeaderDAO = BlockHeaderDAO(appConfig = chainAppConfig)
val chainHandler = ChainHandler(blockHeaderDAO, chainAppConfig)
val syncedChainApiF = chainProjectInitF.flatMap { _ =>
logger.info(s"Beginning sync to bitcoin-s chain state")
ChainSync.sync(chainHandler, getBlockHeader, getBestBlockHash)
}
val syncResultF = syncedChainApiF.flatMap { chainApi =>
chainApi.getBlockCount.map(count => logger.info(s"chain api blockcount=${count}"))
rpcCli.getBlockCount.map(count => logger.info(s"bitcoind blockcount=${count}"))
}
syncResultF.onComplete { case result =>
logger.info(s"Sync result=${result}")
system.terminate()
}

View file

@ -1,232 +0,0 @@
import java.io.File
import org.bitcoins.chain.blockchain.{Blockchain, ChainHandler}
import org.bitcoins.chain.models.{BlockHeaderDAO, BlockHeaderDb, BlockHeaderDbHelper}
import org.bitcoins.core.protocol.blockchain.{Block, RegTestNetChainParams}
import org.bitcoins.wallet.Wallet
import org.bitcoins.wallet.api.InitializeWalletSuccess
import scodec.bits.ByteVector
import akka.actor.ActorSystem
import org.bitcoins.chain.api.ChainApi
import com.typesafe.config.ConfigFactory
import org.bitcoins.chain.db.ChainDbManagement
import org.bitcoins.chain.db.ChainDbConfig
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.chain.blockchain.sync.ChainSync
import org.bitcoins.core.util.BitcoinSLogger
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.currency._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.number._
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.client.v17.BitcoindV17RpcClient
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.util.RpcUtil
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.wallet.db.WalletDbManagement
import org.bitcoins.wallet.db.WalletDbConfig
import org.bitcoins.wallet.config.WalletAppConfig
import org.bitcoins.zmq.ZMQSubscriber
import org.slf4j.LoggerFactory
import scala.collection.JavaConverters._
import scala.concurrent._
import scala.concurrent.duration.DurationInt
import scala.util._
/**
* This is for example purposes only!
* This shows how to peer a bitcoin-s wallet
* with a bitcoind instance that is relaying
* information about what is happening on the blockchain
* to the bitcoin-s wallet.
*
* This is useful if you want more flexible signing
* procedures in the JVM ecosystem and more
* granular control over your utxos with
* popular databases like postgres, sqlite etc
*/
//you can run this script with the following command
//$ sbt "doc/run doc/src/main/scala/org/bitcoins/doc/wallet/create-wallet.sc"
val logger = LoggerFactory.getLogger("org.bitcoins.doc.wallet.CreateWallet")
val time = System.currentTimeMillis()
//boiler plate config
implicit val system = ActorSystem(s"wallet-scala-sheet-${time}")
import system.dispatcher
val chainDbConfig = ChainDbConfig.RegTestDbConfig
val chainAppConfig = ChainAppConfig(chainDbConfig)
implicit val chainParams = chainAppConfig.chain
val walletDbConfig = WalletDbConfig.RegTestDbConfig
val walletAppConfig = WalletAppConfig(walletDbConfig)
val datadir = new File(s"/tmp/bitcoin-${time}/")
val bitcoinConf = new File(datadir.getAbsolutePath + "/bitcoin.conf")
logger.info(s"bitcoin.conf location=${bitcoinConf.getAbsolutePath}")
datadir.mkdirs()
bitcoinConf.createNewFile()
val config = BitcoindRpcTestUtil.standardConfig
val _ = BitcoindRpcTestUtil.writeConfigToFile(config,datadir)
//construct bitcoind
val instance = BitcoindInstance.fromConfig(config = config, datadir)
val bitcoind = new BitcoindRpcClient(instance = instance)
//start bitcoind, this may take a little while
//generate 101 blocks so we have money in our wallet
val bitcoindF = bitcoind.start().map(_ => bitcoind)
//create a native chain handler for bitcoin-s
val blockHeaderDAO: BlockHeaderDAO = BlockHeaderDAO(appConfig = chainAppConfig)
val genesisHeader = BlockHeaderDbHelper.fromBlockHeader(
height = 0,
bh = chainAppConfig.chain.genesisBlock.blockHeader)
val blockHeaderTableF = {
//drop regtest table if it exists
val dropTableF = ChainDbManagement.dropHeaderTable(chainDbConfig)
//recreate the table
val createdTableF = dropTableF.flatMap(_ => ChainDbManagement.createHeaderTable(chainDbConfig))
createdTableF
}
val createdGenHeaderF = blockHeaderTableF.flatMap(_ => blockHeaderDAO.create(genesisHeader))
val chainF = createdGenHeaderF.map(h => Vector(h))
val blockchainF = chainF.map(chain => Blockchain(chain))
val chainHandlerF = blockchainF.map(blockchain => ChainHandler(blockHeaderDAO, chainAppConfig))
val chainApi101BlocksF = sync(chainHandlerF, 101)
val bitcoinsLogF = chainApi101BlocksF.flatMap { chainApi =>
chainApi.getBlockCount.map(count => logger.info(s"bitcoin-s blockcount=${count}"))
}
val walletF = bitcoinsLogF.flatMap { _ =>
//create tables
val dropTablesF = WalletDbManagement.dropAll(walletDbConfig)
val createTablesF = dropTablesF.flatMap(_ => WalletDbManagement.createAll(walletDbConfig))
createTablesF.flatMap { _ =>
Wallet.initialize(walletAppConfig)
.collect{ case success: InitializeWalletSuccess => success.wallet }
}
}
val bitcoinsAddrF = walletF.flatMap(_.getNewAddress())
//send money to our wallet with bitcoind
val amt = Bitcoins.one
val transactionOutputIndexF: Future[(Transaction,Int)] = for {
bitcoind <- bitcoindF
bitcoinsAddr <- bitcoinsAddrF
txid <- bitcoind.sendToAddress(bitcoinsAddr, amt)
tx <- bitcoind.getRawTransactionRaw(txid)
} yield {
logger.info(s"Sending ${amt} to address ${bitcoinsAddr.value}")
val Some((output,index)) = tx.outputs.zipWithIndex.find { case (output,index) =>
output.scriptPubKey == bitcoinsAddr.scriptPubKey
}
(tx,index)
}
//add the utxo that was just created by bitcoind to our wallet
val addUtxoF = for {
wallet <- walletF
(tx,index) <- transactionOutputIndexF
addUtxo <- wallet.addUtxo(tx,UInt32(index))
} yield {
logger.info(s"Add utxo result=${addUtxo}")
addUtxo
}
//bury the utxo with enough proof of work to make it confirmed
val chainApi6BlocksF = for {
addUtxo <- addUtxoF
(tx,_) <- transactionOutputIndexF
chainApi <- sync(chainApi101BlocksF,6)
} yield {
logger.info(s"txid=${tx.txId.flip.hex}")
}
//check balance & clean everything up
chainApi6BlocksF.onComplete { chainApi =>
val balanceF = walletF.flatMap(_.getBalance)
balanceF.onComplete(balance => logger.info(s"bitcoin-s walllet balance=${balance}"))
balanceF.flatMap(_ => cleanup())
}
/** Syncs the give number of blocks to our chain */
def sync(chainHandlerF: Future[ChainApi], numBlocks: Int)(implicit ec: ExecutionContext): Future[ChainApi] = {
//we need a way to connect bitcoin-s to our running bitcoind, we are going to do this via rpc for now
//we need to implement the 'getBestBlockHashFunc' and 'getBlockHeaderFunc' functions
//to be able to sync our internal bitcoin-s chain with our external bitcoind chain
val getBestBlockHashFunc = { () =>
bitcoindF.flatMap(_.getBestBlockHash)
}
val getBlockHeaderFunc = { hash: DoubleSha256DigestBE =>
bitcoindF.flatMap(_.getBlockHeader(hash).map(_.blockHeader))
}
//now that we have bitcoind setup correctly and have rpc linked to
//the bitcoin-s chain project, let's generate some blocks so
//we have money to spend in our bitcoind wallet!
//we need to generate 101 blocks to give us 50 btc to spend
val genBlocksF = chainHandlerF.flatMap { _ =>
bitcoindF.flatMap(_.generate(numBlocks))
}
//now we need to sync those blocks into bitcoin-s
val chainSyncF = genBlocksF.flatMap { _ =>
chainHandlerF.flatMap { ch =>
ChainSync.sync(
ch.asInstanceOf[ChainHandler],
getBlockHeaderFunc,
getBestBlockHashFunc)
}
}
chainSyncF
}
def cleanup(): Future[Unit] = {
logger.info("Beginning clean up of create wallet script")
val bitcoindStopF = {
bitcoindF.flatMap { bitcoind =>
val stopF = bitcoind.stop()
stopF
}
}
datadir.delete()
logger.debug("cleaning up chain, wallet, and system")
val chainCleanupF = ChainDbManagement.dropAll(chainDbConfig)
val walletCleanupF = WalletDbManagement.dropAll(walletDbConfig)
val doneWithCleanupF = for {
_ <- bitcoindStopF
_ <- chainCleanupF
_ <- walletCleanupF
_ <- system.terminate()
} yield {
logger.info(s"Done cleaning up")
}
doneWithCleanupF
}

View file

@ -0,0 +1,50 @@
---
id: configuration
title: Application configuration
---
Bitcoin-S uses [HOCON](https://github.com/lightbend/config/blob/master/HOCON.md)
to configure various parts of the application the library offers. HOCON is a
superset of JSON, that is, all valid JSON is valid HOCON.
All configuration for Bitcoin-S is under the `bitcoin-s` key.
If you have a file `application.conf` anywhere on your classpath when using
bitcoin-s, the values there take precedence over the ones found in our
`reference.conf`. We also look for the file `bitcoin-s.conf` in the current
Bitcoin-S data directory.
The resolved configuration gets parsed by
[`AppConfig`](../../db-commons/src/main/scala/org/bitcoins/db/AppConfig.scala).
`AppConfig` is an abstract class that's implemented by corresponding case
classes in the `wallet`, `chain` and `node` projects. Here's some examples of how to
construct a wallet configuration:
```scala mdoc:compile-only
import org.bitcoins.wallet.config.WalletAppConfig
import com.typesafe.config.ConfigFactory
import java.nio.file.Paths
import scala.util.Properties
// reads $HOME/.bitcoin-s/
val defaultConfig = WalletAppConfig.fromDefaultDatadir()
// reads a custom data directory
val customDirectory = Paths.get(Properties.userHome, "custom-bitcoin-s-directory")
val configFromCustomDatadir = WalletAppConfig(customDirectory)
// reads a custom data directory and overrides the network to be testnet3
val customOverride = ConfigFactory.parseString("bitcoin-s.network = testnet3")
val configFromCustomDirAndOverride = WalletAppConfig(customDirectory, customOverride)
```
You can pass as many `com.typesafe.config.Config`s as you'd like. If any
keys appear multiple times the last one encountered takes precedence.
## Internal configuration
Database connections are also configured by using HOCON. This is done in
[`db.conf`](../../db-commons/src/main/resources/db.conf). The options
exposed here are **not** intended to
be used by users of Bitcoin-S, and are internal only.

84
docs/chain/sync-chain.md Normal file
View file

@ -0,0 +1,84 @@
---
id: sync-chain
title: Syncing block headers with Bitcoin-S
---
Using the `chain` module of Bitcoin-S it's possible to
sync and verify block headers from the Bitcoin blockchain. In this document
we demonstrate how to do this, while persisting it to disk. We should be
able to read this chain on subsequent runs, assuming we are connected
to the same `bitcoind` instance.
```scala mdoc:compile-only
import akka.actor.ActorSystem
import org.bitcoins.chain.blockchain._
import org.bitcoins.chain.blockchain.sync._
import org.bitcoins.chain.models._
import org.bitcoins.rpc.client.common._
import org.bitcoins.testkit.chain._
import scala.concurrent._
implicit val system = ActorSystem()
implicit val exectionContext = system.dispatcher
// We are assuming that a `bitcoind` regtest node is running the background.
// You can see our `bitcoind` guides to see how to connect
// to a local or remote `bitcoind` node.
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.rpc.client.common.BitcoindRpcClient
val bitcoindInstance = BitcoindInstance.fromDatadir()
val rpcCli = new BitcoindRpcClient(bitcoindInstance)
// Next, we need to create a way to monitor the chain:
val getBestBlockHash = ChainTestUtil.bestBlockHashFnRpc(Future.successful(rpcCli))
val getBlockHeader = ChainTestUtil.getBlockHeaderFnRpc(Future.successful(rpcCli))
// set a data directory
import java.nio.file.Files
val datadir = Files.createTempDirectory("bitcoin-s-test")
// set the currenet network to regtest
import com.typesafe.config.ConfigFactory
val config = ConfigFactory.parseString {
"""
| bitcoin-s {
| network = regtest
| }
|""".stripMargin
}
import org.bitcoins.chain.config.ChainAppConfig
implicit val chainConfig = ChainAppConfig(datadir, config)
// Initialize the needed database tables if they don't exist:
val chainProjectInitF = chainConfig.initialize()
val blockHeaderDAO = BlockHeaderDAO()
// Now, do the actual syncing:
val chainHandlerF = ChainHandler.fromDatabase(blockHeaderDAO)
val syncedChainApiF = for {
_ <- chainProjectInitF
handler <- chainHandlerF
synced <- ChainSync.sync(handler, getBlockHeader, getBestBlockHash)
} yield synced
val syncResultF = syncedChainApiF.flatMap { chainApi =>
chainApi.getBlockCount.map(count => println(s"chain api blockcount=${count}"))
rpcCli.getBlockCount.map(count => println(s"bitcoind blockcount=${count}"))
}
syncResultF.onComplete { case result =>
println(s"Sync result=${result}")
system.terminate()
}
```

View file

@ -49,6 +49,21 @@ file you should edit would be `$HOME/.bitcoin-s/bitcoin-s.conf`.
You can place configuration files in the data directory that tests are being run in,
but you can also edit [`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/resources/reference.conf).
## Logging when working on Bitcoin-S tests
When working on various parts of Bitcoin-S the need to log what's going on arises
pretty quickly. There's two way of doing this:
1. Using the way described in the section above, "Working on Bitcoin-S applications".
You could either use traits (like `HTTPLogger` or `P2PLogger`) that exposes a
field `logger`, or acquire the logger directly through the traits companion
object.
2. Use the standard `BitcoinSLogger`, which is also available as both a trait and
a companion object with a field you can access (`BitcoinSLogger.logger`). Note
that by default all logging from this logger is turned off in tests, to make
output less noisy. You can tune this by changing the level found in
`core-test/src/test/resources/logback-test.xml`.
## Developer productivity
### Bloop

View file

@ -99,7 +99,6 @@ handling could look:
```scala mdoc:compile-only
import org.bitcoins.rpc.client.common._
import org.bitcoins.rpc.BitcoindException
import org.bitcoins.rpc.BitcoindWalletException
import org.bitcoins.core.crypto._
import org.bitcoins.core.protocol._

View file

@ -0,0 +1,121 @@
---
id: create-wallet
title: Creating a Bitcoin-S wallet
---
This guide shows how to create a Bitcoin-S wallet and then
peer it with a `bitcoind` instance that relays
information about what is happening on the blockchain
through the P2P network.
This is useful if you want more flexible signing procedures in
the JVM ecosystem and more granular control over your
UTXOs with popular database like Postgres, SQLite, etc.
This code snippet you have a running `bitcoind` instance, locally
on regtest.
```scala mdoc:compile-only
import akka.actor.ActorSystem
implicit val system = ActorSystem()
import system.dispatcher
import com.typesafe.config.ConfigFactory
val config = ConfigFactory.parseString {
"""
| bitcoin-s {
| network = regtest
| }
""".stripMargin
}
import java.nio.file.Files
val datadir = Files.createTempDirectory("bitcoin-s-wallet")
import org.bitcoins.wallet.config.WalletAppConfig
implicit val walletConfig = WalletAppConfig(datadir, config)
// we also need to store chain state for syncing purposes
import org.bitcoins.chain.config.ChainAppConfig
implicit val chainConfig = ChainAppConfig(datadir, config)
// when this future completes, we have
// created the necessary directories and
// databases for managing both chain state
// and wallet state
import scala.concurrent._
val configF: Future[Unit] = for {
_ <- walletConfig.initialize()
_ <- chainConfig.initialize()
} yield ()
import org.bitcoins.rpc.config.BitcoindInstance
val bitcoindInstance = BitcoindInstance.fromDatadir()
import org.bitcoins.rpc.client.common.BitcoindRpcClient
val bitcoind = new BitcoindRpcClient(bitcoindInstance)
// when this future completes, we have
// synced our chain handler to our bitcoind
// peer
import org.bitcoins.chain.api.ChainApi
val syncF: Future[ChainApi] = configF.flatMap { _ =>
val getBestBlockHashFunc = { () =>
bitcoind.getBestBlockHash
}
import org.bitcoins.core.crypto.DoubleSha256DigestBE
val getBlockHeaderFunc = { hash: DoubleSha256DigestBE =>
bitcoind.getBlockHeader(hash).map(_.blockHeader)
}
import org.bitcoins.chain.models.BlockHeaderDAO
import org.bitcoins.chain.blockchain.ChainHandler
val blockHeaderDAO = BlockHeaderDAO()
val chainHandler = ChainHandler(
blockHeaderDAO,
blockchains = Vector.empty)
import org.bitcoins.chain.blockchain.sync.ChainSync
ChainSync.sync(chainHandler, getBlockHeaderFunc, getBestBlockHashFunc)
}
// once this future completes, we have a initialized
// wallet
import org.bitcoins.wallet.api.LockedWalletApi
import org.bitcoins.wallet.api.InitializeWalletSuccess
import org.bitcoins.wallet.Wallet
val walletF: Future[LockedWalletApi] = configF.flatMap { _ =>
Wallet.initialize().collect {
case InitializeWalletSuccess(wallet) => wallet
}
}
// when this future completes, ww have sent a transaction
// from bitcoind to the Bitcoin-S wallet
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.currency._
val transactionF: Future[Transaction] = for {
wallet <- walletF
address <- wallet.getNewAddress()
txid <- bitcoind.sendToAddress(address, 3.bitcoin)
transaction <- bitcoind.getRawTransaction(txid)
} yield transaction.hex
// when this future completes, we have processed
// the transaction from bitcoind, and we have
// queried our balance for the current balance
val balanceF: Future[CurrencyUnit] = for {
wallet <- walletF
tx <- transactionF
_ <- wallet.processTransaction(tx, confirmations = 0)
balance <- wallet.getBalance
} yield balance
balanceF.foreach { balance =>
println(s"Bitcoin-S wallet balance: $balance")
}
```

View file

@ -243,7 +243,7 @@ trait EclairApi {
* Documented by not implemented in Eclair
*/
def sendToRoute(
route: TraversableOnce[NodeId],
route: scala.collection.immutable.Seq[NodeId],
amountMsat: MilliSatoshis,
paymentHash: Sha256Digest,
finalCltvExpiry: Long): Future[PaymentId]

View file

@ -22,7 +22,7 @@ import org.bitcoins.core.protocol.ln.{
ShortChannelId
}
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.util.{BitcoinSUtil, FutureUtil}
import org.bitcoins.core.util.BitcoinSUtil
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.eclair.rpc.api.EclairApi
import org.bitcoins.eclair.rpc.config.EclairInstance
@ -371,7 +371,7 @@ class EclairRpcClient(val instance: EclairInstance)(
//register callback that publishes a payment to our actor system's
//event stream,
receivedInfoF.map {
receivedInfoF.foreach {
case None =>
if (attempts.incrementAndGet() >= maxAttempts) {
// too many tries to get info about a payment
@ -480,13 +480,13 @@ class EclairRpcClient(val instance: EclairInstance)(
}
def sendToRoute(
route: TraversableOnce[NodeId],
route: scala.collection.immutable.Seq[NodeId],
amountMsat: MilliSatoshis,
paymentHash: Sha256Digest,
finalCltvExpiry: Long): Future[PaymentId] = {
eclairCall[PaymentId](
"sendtoroute",
"route" -> route.mkString(","),
"route" -> route.iterator.mkString(","),
"amountMsat" -> amountMsat.toBigDecimal.toString,
"paymentHash" -> paymentHash.hex,
"finalCltvExpiry" -> finalCltvExpiry.toString

View file

@ -1,6 +1,7 @@
val scala2_11 = "2.11.12"
val scala2_12 = "2.12.8"
val scala2_12 = "2.12.9"
val scala2_13 = "2.13.0"
scalaVersion in ThisBuild := scala2_12
scalaVersion in ThisBuild := scala2_13
crossScalaVersions in ThisBuild := List(scala2_12, scala2_11)
crossScalaVersions in ThisBuild := List(scala2_13, scala2_12, scala2_11)

View file

@ -1,7 +0,0 @@
package org.bitcoins.node.models
trait ColumnMappers {
import org.bitcoins.db.DbCommonsColumnMappers._
}
object ColumnMappers extends ColumnMappers

View file

@ -106,8 +106,9 @@ case class P2PClientActor(
handleCommand(cmd, peerOpt = None)
case connected: Tcp.Connected =>
Await.result(handleEvent(connected, unalignedBytes = ByteVector.empty),
timeout)
val _ = Await.result(
handleEvent(connected, unalignedBytes = ByteVector.empty),
timeout)
case msg: NetworkMessage =>
self.forward(msg.payload)
case payload: NetworkPayload =>

View file

@ -8,7 +8,7 @@ object Deps {
val scalacheck = "1.14.0"
val scalaTest = "3.0.8"
val slf4j = "1.7.28"
val spray = "1.3.4"
val spray = "1.3.5"
val zeromq = "0.5.1"
val akkav = "10.1.9"
val akkaStreamv = "2.5.25"
@ -17,9 +17,11 @@ object Deps {
val junitV = "0.11"
val nativeLoaderV = "2.3.4"
val typesafeConfigV = "1.3.4"
val ammoniteV = "1.6.7"
val asyncV = "0.9.7"
// async dropped Scala 2.11 in 0.10.0
val asyncOldScalaV = "0.9.7"
val asyncNewScalaV = "0.10.0"
val postgresV = "9.4.1210"
val akkaActorV = akkaStreamv
val slickV = "3.3.2"
@ -27,9 +29,21 @@ object Deps {
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
val oldMicroPickleV = "0.7.4"
val oldMicroJsonV = oldMicroPickleV
val newMicroPickleV = "0.7.5"
val newMicroJsonV = newMicroPickleV
// akka-http-upickle is not yet published
// to Maven central. There's a PR for adding
// suport, https://github.com/hseeberger/akka-http-json/pull/314.
// Until that's merged, you'll have to pull down
// that PR, do `sbt publishLocal` and replace the
// value here with whatever is in there. This
// obviously has to be changed before this is
// merged.
val sourcecodeV = "0.1.7"
// CLI deps
@ -38,6 +52,7 @@ object Deps {
}
object Compile {
val bouncycastle = "org.bouncycastle" % "bcprov-jdk15on" % V.bouncyCastle withSources () withJavadoc ()
val scodec = "org.scodec" %% "scodec-bits" % V.scodecV withSources () withJavadoc ()
val slf4j = "org.slf4j" % "slf4j-api" % V.slf4j % "provided" withSources () withJavadoc ()
@ -53,24 +68,26 @@ object Deps {
//for loading secp256k1 natively
val nativeLoader = "org.scijava" % "native-lib-loader" % V.nativeLoaderV withSources () withJavadoc ()
val ammonite = "com.lihaoyi" %% "ammonite" % V.ammoniteV cross CrossVersion.full
//node deps
val slick = "com.typesafe.slick" %% "slick" % V.slickV withSources () withJavadoc ()
val slickHikari = "com.typesafe.slick" %% "slick-hikaricp" % V.slickV
val sqlite = "org.xerial" % "sqlite-jdbc" % V.sqliteV
val postgres = "org.postgresql" % "postgresql" % V.postgresV
val uJson = "com.lihaoyi" %% "ujson" % V.uJsonV
// serializing to and from JSON
val uPickle = "com.lihaoyi" %% "upickle" % V.uPickleV
// zero dep JSON library. Have to use different versiont to juggle
// Scala 2.11/12/13
val oldMicroJson = "com.lihaoyi" %% "ujson" % V.oldMicroJsonV
val newMicroJson = "com.lihaoyi" %% "ujson" % V.newMicroJsonV
// serializing to and from JSON Have to use different versiont to juggle
// Scala 2.11/12/13
val oldMicroPickle = "com.lihaoyi" %% "upickle" % V.oldMicroPickleV
val newMicroPickle = "com.lihaoyi" %% "upickle" % V.newMicroPickleV
// get access to reflection data at compile-time
val sourcecode = "com.lihaoyi" %% "sourcecode" % V.sourcecodeV
// 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
@ -82,7 +99,8 @@ object Deps {
}
object Test {
val async = "org.scala-lang.modules" %% "scala-async" % V.asyncV % "test" withSources () withJavadoc ()
val oldAsync = "org.scala-lang.modules" %% "scala-async" % V.asyncOldScalaV % "test" withSources () withJavadoc ()
val newAsync = "org.scala-lang.modules" %% "scala-async" % V.asyncNewScalaV % "test" withSources () withJavadoc ()
val junitInterface = "com.novocode" % "junit-interface" % V.junitV % "test" withSources () withJavadoc ()
val logback = Compile.logback % "test"
val scalacheck = Compile.scalacheck % "test"
@ -136,13 +154,13 @@ object Deps {
Compile.typesafeConfig
)
val bitcoindRpcTest = List(
def bitcoindRpcTest(scalaVersion: String) = List(
Test.akkaHttp,
Test.akkaStream,
Test.logback,
Test.scalaTest,
Test.scalacheck,
Test.async
if (scalaVersion.startsWith("2.11")) Test.oldAsync else Test.newAsync
)
val bench = List(
@ -158,19 +176,22 @@ object Deps {
Compile.slickHikari
)
val cli = List(
def cli(scalaVersion: String) = List(
Compile.sttp,
Compile.uPickle,
if (scalaVersion.startsWith("2.11")) Compile.oldMicroPickle
else Compile.newMicroPickle,
Compile.logback,
Compile.scopt
)
val picklers = List(
Compile.uPickle
def picklers(scalaVersion: String) = List(
if (scalaVersion.startsWith("2.11")) Compile.oldMicroPickle
else Compile.newMicroPickle
)
val server = List(
Compile.akkaHttpUpickle,
Compile.uPickle,
def server(scalaVersion: String) = List(
if (scalaVersion.startsWith("2.11")) Compile.oldMicroPickle
else Compile.newMicroPickle,
Compile.logback,
Compile.akkaHttp
)
@ -210,13 +231,9 @@ object Deps {
Test.akkaTestkit
)
val scripts = List(
Compile.ammonite,
Compile.logback
)
val wallet = List(
Compile.uJson,
def wallet(scalaVersion: String) = List(
if (scalaVersion.startsWith("2.11")) Compile.oldMicroJson
else Compile.newMicroJson,
Compile.logback
)

View file

@ -1,16 +0,0 @@
## Ammonite scripts
This project contain [Ammonite](https://ammonite.io) scripts that demonstrate
functionality of `bitcoin-s`.
#### Running them with sbt:
```bash
$ sbt "scripts/run path/to/script.sc" # this is very slow, not recommended
```
#### Running them with the [Bloop CLI](https://scalacenter.github.io/bloop/):
```bash
$ bloop run scripts --args path/to/script.sc # much faster than through sbt
```

View file

@ -1,43 +0,0 @@
package org.bitcoins.doc
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import scala.util.Properties
object amm extends App {
/** Gets all files ending with .sc in dir or subdirs */
def getScripts(dir: Path): Seq[Path] = {
import scala.collection.JavaConverters._
Files
.walk(dir)
.iterator()
.asScala
.filter(Files.isRegularFile(_))
.filter(_.toString.endsWith(".sc"))
.toList
}
if (args.isEmpty || args.headOption.forall(_.isEmpty)) {
import System.err.{println => printerr}
printerr("No script name provided!")
printerr()
val cwd = Paths.get(Properties.userDir)
val scripts = getScripts(cwd)
if (scripts.nonEmpty) {
printerr("Available scripts:")
scripts.foreach { script =>
printerr(s" ${cwd.relativize(script)}")
}
} else {
printerr("No .sc scripts found!")
}
sys.exit(1)
} else {
ammonite.Main.main(args)
}
}

View file

@ -1,50 +0,0 @@
import akka.actor.ActorSystem
import org.bitcoins.core.config._
import org.bitcoins.rpc.config._
import org.bitcoins.rpc.client.common._
import java.net.URI
//this script shows you have to connect to a remote bitcoind
//instance via an ssh tunnel with bitcoin-s
//first we need to create the ssh tunnel
//$ ssh -L 8332:localhost:8332 my-cool-user@my-cool-website.com
//note: the port number '8332' is for mainnet, if you want to
//conect to a testnet rpc client you will need to do '18332'
//now we have a secure connection between our remote bitcoind
//node that is running on 'my-cool-website.com' under the
//username 'my-cool-user'
//it should be noted, that the steps above can be skipped and you
//can connect to a running local bitcoind instance as well
val username = "FILL_ME_IN" //this username comes from 'rpcuser' in your bitcoin.conf file
val password = "FILL_ME_IN" //this password comes from your 'rpcpassword' in your bitcoin.conf file
val rpcPort = 8332 //this is default port for mainnet, 18332 for testnet/regtest
val authCredentials = BitcoindAuthCredentials(
username = username,
password = password,
rpcPort = rpcPort
)
val bitcoindInstance = {
BitcoindInstance (
network = MainNet,
uri = new URI(s"http://localhost:${authCredentials.rpcPort + 1}"),
rpcUri = new URI(s"http://localhost:${authCredentials.rpcPort}"),
authCredentials = authCredentials
)
}
implicit val system = ActorSystem(s"connnect-bitcoind-ssh-${System.currentTimeMillis()}")
implicit val ec = system.dispatcher
val rpcCli = new BitcoindRpcClient(bitcoindInstance)
rpcCli.getBalance.onComplete { case balance =>
println(s"Wallet balance=${balance}")
system.terminate()
}

View file

@ -19,7 +19,8 @@ abstract class BloomFilterGenerator {
} yield BloomFilter(size, falsePositiveRate, tweak, flags)
/** Loads a generic bloom filter with the given byte vectors and returns it */
def bloomFilter(byteVectors: Seq[ByteVector]): Gen[BloomFilter] =
def bloomFilter(
byteVectors: scala.collection.Seq[ByteVector]): Gen[BloomFilter] =
for {
filter <- bloomFilter
} yield filter.insertByteVectors(byteVectors)

View file

@ -43,8 +43,11 @@ abstract class MerkleGenerator {
* false negatives.
* @return
*/
def merkleBlockCreatedWithBloomFilter: Gen[
(MerkleBlock, Block, Seq[DoubleSha256Digest], BloomFilter)] =
def merkleBlockCreatedWithBloomFilter: Gen[(
MerkleBlock,
Block,
scala.collection.Seq[DoubleSha256Digest],
BloomFilter)] =
for {
block <- BlockchainElementsGenerator.block
//choose some random txs in the block to put in the bloom filter

View file

@ -42,7 +42,7 @@ import org.bitcoins.rpc.config.BitcoindAuthCredentials
*
*/
trait EclairRpcTestUtil extends BitcoinSLogger {
import collection.JavaConverters._
import org.bitcoins.core.compat.JavaConverters._
def randomDirName: String =
0.until(5).map(_ => scala.util.Random.alphanumeric.head).mkString

View file

@ -466,7 +466,7 @@ trait BitcoindRpcTestUtil extends BitcoinSLogger {
_ <- start1F
_ <- start2F
} yield {
clientAccum += (client1, client2)
clientAccum ++= List(client1, client2)
(client1, client2)
}
}

View file

@ -1,7 +1,7 @@
package org.bitcoins.testkit.util
import org.scalactic.anyvals.PosInt
import org.scalatest.prop.PropertyChecks
import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks
import org.scalatest.{FlatSpec, MustMatchers}
import org.slf4j.{Logger, LoggerFactory}
@ -9,7 +9,7 @@ import org.slf4j.{Logger, LoggerFactory}
abstract class BitcoinSUnitTest
extends FlatSpec
with MustMatchers
with PropertyChecks {
with ScalaCheckPropertyChecks {
protected lazy val logger: Logger = LoggerFactory.getLogger(getClass)

View file

@ -12,7 +12,7 @@ import java.nio.file.Files
import akka.compat.Future
import akka.compat.Future
import scala.concurrent.Future
import scala.collection.JavaConverters._
import org.bitcoins.core.compat.JavaConverters._
import java.nio.file.Path
import org.scalatest.BeforeAndAfterEach
import java.nio.file.Paths

View file

@ -1,7 +1,7 @@
package org.bitcoins.wallet
import org.bitcoins.core.compat._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Try, Success, Failure}
/**
* @define liftBiasedFut Given a [[scala.Either Either]] that contains a
@ -16,22 +16,25 @@ object EitherUtil {
* where the right hand side of an either is asynchronous.
*/
def flattenFutureE[L, R](
either: Either[L, Future[Either[L, R]]]
): Future[Either[L, R]] = {
either: CompatEither[L, Future[CompatEither[L, R]]]
): Future[CompatEither[L, R]] = {
def ifLeft(left: L): Future[Either[L, R]] = Future.successful(Left(left))
def ifRight(rightF: Future[Either[L, R]]): Future[Either[L, R]] = rightF
def ifLeft(left: L): Future[CompatEither[L, R]] =
Future.successful(CompatLeft(left))
def ifRight(
rightF: Future[CompatEither[L, R]]): Future[CompatEither[L, R]] =
rightF
either.fold(ifLeft, ifRight)
}
/** $liftBiasedFut */
def liftRightBiasedFutureE[L, R](
either: Either[L, Future[R]]
)(implicit ec: ExecutionContext): Future[Either[L, R]] =
either: CompatEither[L, Future[R]]
)(implicit ec: ExecutionContext): Future[CompatEither[L, R]] =
either match {
case Right(fut) => fut.map(Right(_))
case Left(l) => Future.successful(Left(l))
case CompatRight(fut) => fut.map(elem => CompatRight(elem))
case CompatLeft(l) => Future.successful(CompatLeft(l))
}
/** $liftBiasedFut */
@ -43,46 +46,4 @@ object EitherUtil {
case Right(l) => Future.successful(Right(l))
}
object EitherOps {
import scala.language.implicitConversions
implicit def either2EnhancedEither[A, B](
either: Either[A, B]
): EnchancedEither[A, B] = EnchancedEither(either)
implicit def enchancedEither2Either[A, B](
enhanced: EnchancedEither[A, B]): Either[A, B] = enhanced.underlying
}
/** The methods here are copied directly from the 2.12 stdlib */
case class EnchancedEither[A, B](
private[EitherUtil] val underlying: Either[A, B]) {
/** The given function is applied if this is a `Right`.
*
* {{{
* Right(12).map(x => "flower") // Result: Right("flower")
* Left(12).map(x => "flower") // Result: Left(12)
* }}}
*/
def map[B1](f: B => B1): EnchancedEither[A, B1] = underlying match {
case Right(b) => EnchancedEither(Right(f(b)))
case _ => EnchancedEither(this.asInstanceOf[Either[A, B1]])
}
/** Binds the given function across `Right`.
*
* @param f The function to bind across `Right`.
*/
def flatMap[A1 >: A, B1](f: B => Either[A1, B1]): EnchancedEither[A1, B1] =
underlying match {
case Right(b) => EnchancedEither(f(b))
case _ => EnchancedEither(underlying.asInstanceOf[Either[A1, B1]])
}
def toTry(implicit ev: A <:< Throwable): Try[B] = underlying match {
case Right(b) => Success(b)
case Left(a) => Failure(a)
}
}
}

View file

@ -1,6 +1,7 @@
package org.bitcoins.wallet
import org.bitcoins.core.crypto._
import org.bitcoins.core.compat.CompatEither
import scodec.bits.ByteVector
import scala.util.{Failure, Success, Try}
@ -8,9 +9,9 @@ import scala.util.{Failure, Success, Try}
case class EncryptedMnemonic(value: AesEncryptedData, salt: AesSalt) {
def toMnemonic(password: AesPassword): Try[MnemonicCode] = {
import EitherUtil.EitherOps._
val key = password.toKey(salt)
AesCrypt.decrypt(value, key).toTry.flatMap { decrypted =>
val either = AesCrypt.decrypt(value, key)
CompatEither(either).toTry.flatMap { decrypted =>
decrypted.decodeUtf8 match {
case Left(_) =>
// when failing to decode this to a UTF-8 string

View file

@ -1,5 +1,6 @@
package org.bitcoins.wallet
import org.bitcoins.core.compat._
import org.bitcoins.core.config.BitcoinNetwork
import org.bitcoins.core.crypto._
import org.bitcoins.core.currency._
@ -161,26 +162,28 @@ object Wallet extends CreateWalletApi with KeyHandlingLogger {
override def initializeWithEntropy(entropy: BitVector)(
implicit config: WalletAppConfig,
ec: ExecutionContext): Future[InitializeWalletResult] = {
import EitherUtil.EitherOps._
logger.info(s"Initializing wallet on chain ${config.network}")
val mnemonicT = Try(MnemonicCode.fromEntropy(entropy))
val mnemonicE: Either[InitializeWalletError, MnemonicCode] =
val mnemonicE: CompatEither[InitializeWalletError, MnemonicCode] =
mnemonicT match {
case Success(mnemonic) =>
logger.trace(s"Created mnemonic from entropy")
Right(mnemonic)
CompatEither(Right(mnemonic))
case Failure(err) =>
logger.error(s"Could not create mnemonic from entropy! $err")
Left(InitializeWalletError.BadEntropy)
CompatEither(Left(InitializeWalletError.BadEntropy))
}
val encryptedMnemonicE: Either[InitializeWalletError, EncryptedMnemonic] =
val encryptedMnemonicE: CompatEither[
InitializeWalletError,
EncryptedMnemonic] =
mnemonicE.map { EncryptedMnemonicHelper.encrypt(_, badPassphrase) }
val biasedFinalEither: Either[InitializeWalletError, Future[WalletImpl]] =
val biasedFinalEither: CompatEither[
InitializeWalletError,
Future[WalletImpl]] =
for {
mnemonic <- mnemonicE
encrypted <- encryptedMnemonicE
@ -216,14 +219,14 @@ object Wallet extends CreateWalletApi with KeyHandlingLogger {
} yield wallet
}
val finalEither: Future[Either[InitializeWalletError, WalletImpl]] =
val finalEither: Future[CompatEither[InitializeWalletError, WalletImpl]] =
EitherUtil.liftRightBiasedFutureE(biasedFinalEither)
finalEither.map {
case Right(wallet) =>
case CompatRight(wallet) =>
logger.debug(s"Successfully initialized wallet")
InitializeWalletSuccess(wallet)
case Left(err) => err
case CompatLeft(err) => err
}
}

View file

@ -1,6 +1,8 @@
package org.bitcoins.wallet
import scala.collection.JavaConverters._
import org.bitcoins.core.compat.JavaConverters._
import org.bitcoins.core.compat._
import org.bitcoins.core.crypto.AesPassword
import java.nio.file.Files
import org.bitcoins.core.crypto.MnemonicCode
@ -68,9 +70,9 @@ object WalletStorage extends KeyHandlingLogger {
val foundMnemonicOpt: Option[EncryptedMnemonic] =
readEncryptedMnemonicFromDisk() match {
case Left(_) =>
case CompatLeft(_) =>
None
case Right(mnemonic) => Some(mnemonic)
case CompatRight(mnemonic) => Some(mnemonic)
}
foundMnemonicOpt match {
@ -104,7 +106,7 @@ object WalletStorage extends KeyHandlingLogger {
* performing no decryption
*/
private def readEncryptedMnemonicFromDisk()(
implicit config: WalletAppConfig): Either[
implicit config: WalletAppConfig): CompatEither[
ReadMnemonicError,
EncryptedMnemonic] = {
@ -112,7 +114,7 @@ object WalletStorage extends KeyHandlingLogger {
config.datadir.resolve(ENCRYPTED_SEED_FILE_NAME)
}
val jsonE: Either[ReadMnemonicError, ujson.Value] = {
val jsonE: CompatEither[ReadMnemonicError, ujson.Value] = {
if (Files.isRegularFile(path)) {
val rawJson = Files.readAllLines(path).asScala.mkString("\n")
logger.debug(s"Read raw encrypted mnemonic from $path")
@ -121,24 +123,23 @@ object WalletStorage extends KeyHandlingLogger {
ujson.read(rawJson)
} match {
case Failure(ujson.ParseException(clue, _, _, _)) =>
Left(ReadMnemonicError.JsonParsingError(clue))
CompatLeft(ReadMnemonicError.JsonParsingError(clue))
case Failure(exception) => throw exception
case Success(value) =>
logger.debug(s"Parsed $path into valid json")
Right(value)
CompatRight(value)
}
} else {
logger.error(s"Encrypted mnemonic not found at $path")
Left(ReadMnemonicError.NotFoundError)
CompatLeft(ReadMnemonicError.NotFoundError)
}
}
import EitherUtil.EitherOps._
import MnemonicJsonKeys._
import ReadMnemonicError._
val readJsonTupleEither: Either[
val readJsonTupleEither: CompatEither[
ReadMnemonicError,
(String, String, String)] = jsonE.flatMap { json =>
logger.trace(s"Read encrypted mnemonic JSON: $json")
@ -148,15 +149,15 @@ object WalletStorage extends KeyHandlingLogger {
val rawSaltString = json(SALT).str
(ivString, cipherTextString, rawSaltString)
} match {
case Success(value) => Right(value)
case Success(value) => CompatRight(value)
case Failure(value: ujson.Value.InvalidData) =>
logger.error(s"Error when parsing JSON file $path: ${value.msg}")
Left(JsonParsingError(value.msg))
CompatLeft(JsonParsingError(value.msg))
case Failure(exception) => throw exception
}
}
val encryptedEither: Either[ReadMnemonicError, EncryptedMnemonic] =
val encryptedEither: CompatEither[ReadMnemonicError, EncryptedMnemonic] =
readJsonTupleEither.flatMap {
case (rawIv, rawCipherText, rawSalt) =>
val encryptedOpt = for {
@ -167,10 +168,12 @@ object WalletStorage extends KeyHandlingLogger {
logger.debug(s"Parsed contents of $path into an EncryptedMnemonic")
EncryptedMnemonic(AesEncryptedData(cipherText, iv), salt)
}
encryptedOpt
.map(Right(_))
.getOrElse(
Left(JsonParsingError("JSON contents was not hex strings")))
val toRight: Option[
CompatRight[ReadMnemonicError, EncryptedMnemonic]] = encryptedOpt
.map(CompatRight(_))
toRight.getOrElse(
CompatLeft(JsonParsingError("JSON contents was not hex strings")))
}
encryptedEither
}
@ -185,22 +188,21 @@ object WalletStorage extends KeyHandlingLogger {
val encryptedEither = readEncryptedMnemonicFromDisk()
import EitherUtil.EitherOps._
val decryptedEither: Either[ReadMnemonicError, MnemonicCode] =
val decryptedEither: CompatEither[ReadMnemonicError, MnemonicCode] =
encryptedEither.flatMap { encrypted =>
encrypted.toMnemonic(passphrase) match {
case Failure(exc) =>
logger.error(s"Error when decrypting $encrypted: $exc")
Left(ReadMnemonicError.DecryptionError)
CompatLeft(ReadMnemonicError.DecryptionError)
case Success(value) =>
logger.debug(s"Decrypted $encrypted successfully")
Right(value)
CompatRight(value)
}
}
decryptedEither match {
case Left(value) => value
case Right(value) => ReadMnemonicSuccess(value)
case CompatLeft(value) => value
case CompatRight(value) => ReadMnemonicSuccess(value)
}
}
}

View file

@ -1,5 +1,6 @@
package org.bitcoins.wallet.internal
import org.bitcoins.core.compat._
import org.bitcoins.wallet.LockedWallet
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
@ -42,14 +43,14 @@ private[wallet] trait UtxoHandling extends KeyHandlingLogger {
* it in our address table
*/
private def findAddress(
spk: ScriptPubKey): Future[Either[AddUtxoError, AddressDb]] =
spk: ScriptPubKey): Future[CompatEither[AddUtxoError, AddressDb]] =
BitcoinAddress.fromScriptPubKey(spk, networkParameters) match {
case Success(address) =>
addressDAO.findAddress(address).map {
case Some(addrDb) => Right(addrDb)
case None => Left(AddUtxoError.AddressNotFound)
case Some(addrDb) => CompatRight(addrDb)
case None => CompatLeft(AddUtxoError.AddressNotFound)
}
case Failure(_) => Future.successful(Left(AddUtxoError.BadSPK))
case Failure(_) => Future.successful(CompatLeft(AddUtxoError.BadSPK))
}
/** Constructs a DB level representation of the given UTXO, and persist it to disk */
@ -103,7 +104,6 @@ private[wallet] trait UtxoHandling extends KeyHandlingLogger {
confirmations: Int,
spent: Boolean): Future[AddUtxoResult] = {
import AddUtxoError._
import EitherUtil.EitherOps._
logger.info(s"Adding UTXO to wallet: ${transaction.txId.hex}:${vout.toInt}")
@ -127,12 +127,12 @@ private[wallet] trait UtxoHandling extends KeyHandlingLogger {
// second check: do we have an address associated with the provided
// output in our DB?
val addressDbEitherF: Future[Either[AddUtxoError, AddressDb]] =
val addressDbEitherF: Future[CompatEither[AddUtxoError, AddressDb]] =
findAddress(output.scriptPubKey)
// insert the UTXO into the DB
addressDbEitherF.flatMap { addressDbE =>
val biasedE: Either[AddUtxoError, Future[SpendingInfoDb]] = for {
val biasedE: CompatEither[AddUtxoError, Future[SpendingInfoDb]] = for {
addressDb <- addressDbE
} yield
writeUtxo(txid = transaction.txIdBE,
@ -144,8 +144,8 @@ private[wallet] trait UtxoHandling extends KeyHandlingLogger {
EitherUtil.liftRightBiasedFutureE(biasedE)
} map {
case Right(utxo) => AddUtxoSuccess(utxo)
case Left(e) => e
case CompatRight(utxo) => AddUtxoSuccess(utxo)
case CompatLeft(e) => e
}
}
}

View file

@ -12,7 +12,10 @@
"rpc/rpc-eclair",
"rpc/rpc-bitcoind"
],
"Chain": ["chain/sync-chain"],
"Wallet": ["wallet/create-wallet"],
"Contributing": ["contributing", "contributing-website"],
"Applications": ["applications/configuration"],
"Security": ["security"]
}
}