Compute confirmations dynamically (#938)

* Compute confirmations dynamically
This commit is contained in:
rorp 2019-12-14 12:06:22 -08:00 committed by GitHub
parent 96aae0ca4f
commit 2c53a39fd1
25 changed files with 506 additions and 421 deletions

View File

@ -25,7 +25,7 @@ import org.scalatest.{Matchers, WordSpec}
import ujson.Value.InvalidData
import ujson.{Arr, Null, Num, Str}
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.Future
class RoutesSpec
extends WordSpec
@ -52,9 +52,8 @@ class RoutesSpec
"The server" should {
"return the block count" in {
(mockChainApi
.getBlockCount(_: ExecutionContext))
.expects(*)
(mockChainApi.getBlockCount: () => Future[Int])
.expects()
.returning(Future.successful(1234567890))
val route =
@ -67,9 +66,8 @@ class RoutesSpec
}
"return the best block hash" in {
(mockChainApi
.getBestBlockHash(_: ExecutionContext))
.expects(*)
(mockChainApi.getBestBlockHash: () => Future[DoubleSha256DigestBE])
.expects()
.returning(Future.successful(DoubleSha256DigestBE.empty))
val route =

View File

@ -5,7 +5,8 @@ import java.nio.file.Files
import akka.actor.ActorSystem
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.core.api.{ChainQueryApi, NodeApi}
import org.bitcoins.core.api.ChainQueryApi
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.node.config.NodeAppConfig
import org.bitcoins.node.models.Peer
import org.bitcoins.node.networking.peer.DataMessageHandler
@ -115,7 +116,7 @@ object Main extends App {
wallet: UnlockedWalletApi): Future[NodeCallbacks] = {
import DataMessageHandler._
lazy val onTx: OnTxReceived = { tx =>
wallet.processTransaction(tx, confirmations = 0)
wallet.processTransaction(tx, blockHash = None)
()
}
lazy val onCompactFilter: OnCompactFilterReceived = {
@ -123,7 +124,7 @@ object Main extends App {
wallet.processCompactFilter(blockHash, blockFilter)
}
lazy val onBlock: OnBlockReceived = { block =>
wallet.processBlock(block, 0)
wallet.processBlock(block)
()
}
if (nodeConf.isSPVEnabled) {

View File

@ -14,9 +14,10 @@ import org.bitcoins.core.crypto.{
ECPrivateKey
}
import org.bitcoins.core.gcs.{BlockFilter, FilterHeader, FilterType}
import org.bitcoins.core.number.{Int32, UInt32}
import org.bitcoins.core.p2p.CompactFilterMessage
import org.bitcoins.core.protocol.BitcoinAddress
import org.bitcoins.core.protocol.blockchain.BlockHeader
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
import org.bitcoins.core.util.CryptoUtil
import org.bitcoins.testkit.BitcoinSTestAppConfig
import org.bitcoins.testkit.chain.fixture.ChainFixtureTag
@ -51,7 +52,9 @@ class ChainHandlerTest extends ChainUnitTest {
source.close()
import org.bitcoins.rpc.serializers.JsonReaders.BlockHeaderReads
val headersResult = Json.parse(arrStr).validate[Vector[BlockHeader]].get
val headersResult: Vector[BlockHeader] =
Json.parse(arrStr).validate[Vector[BlockHeader]].get
override val defaultTag: ChainFixtureTag = ChainFixtureTag.GenisisChainHandler
@ -268,30 +271,14 @@ class ChainHandlerTest extends ChainUnitTest {
it must "get the highest filter header" in { chainHandler: ChainHandler =>
{
val firstFilterHeader = FilterHeader(
filterHash =
DoubleSha256Digest.fromBytes(ECPrivateKey.freshPrivateKey.bytes),
prevHeaderHash = DoubleSha256Digest.empty)
for {
empty <- chainHandler.getFilterHeadersAtHeight(0)
block <- chainHandler.getHeadersAtHeight(0)
_ <- chainHandler.processFilterHeader(firstFilterHeader,
block.head.hashBE)
count <- chainHandler.getFilterHeaderCount
first <- chainHandler.getFilterHeader(block.head.hashBE)
vec <- chainHandler.getFilterHeadersAtHeight(count)
genesisFilterHeader <- chainHandler.getFilterHeadersAtHeight(count)
} yield {
assert(empty.isEmpty)
assert(first.nonEmpty)
assert(vec.nonEmpty)
assert(Vector(first.get) == vec)
assert(first.get.hashBE == firstFilterHeader.hash.flip)
assert(first.get.filterHashBE == firstFilterHeader.filterHash.flip)
assert(genesisFilterHeader.size == 1)
assert(
first.get.previousFilterHeaderBE == firstFilterHeader.prevHeaderHash.flip)
assert(first.get.blockHashBE == block.head.hashBE)
assert(first.get.height == 0)
assert(first.get.filterHeader == firstFilterHeader)
genesisFilterHeader.contains(ChainUnitTest.genesisFilterHeaderDb))
assert(count == 0)
}
}
}
@ -313,42 +300,36 @@ class ChainHandlerTest extends ChainUnitTest {
it must "get the highest filter" in { chainHandler: ChainHandler =>
{
for {
empty <- chainHandler.getFilterCount
blockHashBE <- chainHandler.getHeadersAtHeight(0).map(_.head.hashBE)
golombFilter = BlockFilter.fromHex("017fa880", blockHashBE.flip)
firstFilter = CompactFilterMessage(blockHash = blockHashBE.flip,
filter = golombFilter)
firstFilterHeader = FilterHeader(filterHash = golombFilter.hash,
prevHeaderHash =
DoubleSha256Digest.empty)
newChainHandler <- chainHandler.processFilterHeader(firstFilterHeader,
blockHashBE)
_ <- chainHandler.processFilter(firstFilter)
count <- newChainHandler.getFilterCount
first <- newChainHandler.getFiltersAtHeight(count).map(_.headOption)
count <- chainHandler.getFilterCount
genesisFilter <- chainHandler.getFiltersAtHeight(count)
} yield {
assert(empty == 0)
assert(first.nonEmpty)
assert(first.get.hashBE == golombFilter.hash.flip)
assert(first.get.height == 0)
assert(first.get.blockHashBE == blockHashBE)
assert(first.get.filterType == FilterType.Basic)
assert(first.get.golombFilter == golombFilter)
assert(count == 0)
assert(genesisFilter.contains(ChainUnitTest.genesisFilterDb))
}
}
}
it must "NOT create an unknown filter" in { chainHandler: ChainHandler =>
{
val blockHeader =
BlockHeader(
version = Int32(1),
previousBlockHash = ChainUnitTest.genesisHeaderDb.hashBE.flip,
merkleRootHash = DoubleSha256Digest.empty,
time = UInt32(1231006505),
nBits = UInt32(545259519),
nonce = UInt32(2083236893)
)
val unknownHashF = for {
blockHashBE <- chainHandler.getHeadersAtHeight(0).map(_.head.hashBE)
_ <- chainHandler.processHeader(blockHeader)
blockHashBE <- chainHandler.getHeadersAtHeight(1).map(_.head.hashBE)
golombFilter = BlockFilter.fromHex("017fa880", blockHashBE.flip)
firstFilter = CompactFilterMessage(blockHash = blockHashBE.flip,
filter = golombFilter)
firstFilterHeader = FilterHeader(
filterHash =
DoubleSha256Digest.fromBytes(ECPrivateKey.freshPrivateKey.bytes),
prevHeaderHash = DoubleSha256Digest.empty)
prevHeaderHash = ChainUnitTest.genesisFilterHeaderDb.hashBE.flip)
newChainHandler <- chainHandler.processFilterHeader(firstFilterHeader,
blockHashBE)
process <- newChainHandler.processFilter(firstFilter)
@ -400,7 +381,7 @@ class ChainHandlerTest extends ChainUnitTest {
it must "generate a range for a block filter query" in {
chainHandler: ChainHandler =>
for {
bestBlock <- chainHandler.getBestBlockHeader
bestBlock <- chainHandler.getBestBlockHeader()
bestBlockHashBE = bestBlock.hashBE
rangeOpt <- chainHandler.nextHeaderBatchRange(
DoubleSha256DigestBE.empty,
@ -412,6 +393,47 @@ class ChainHandlerTest extends ChainUnitTest {
}
}
it must "generate a range for a block filter header query" in {
chainHandler: ChainHandler =>
for {
bestBlock <- chainHandler.getBestBlockHeader()
bestBlockHashBE = bestBlock.hashBE
rangeOpt <- chainHandler.nextFilterHeaderBatchRange(
DoubleSha256DigestBE.empty,
1)
} yield {
assert(rangeOpt.nonEmpty)
assert(rangeOpt.get._1 == 0)
assert(rangeOpt.get._2 == bestBlockHashBE.flip)
}
}
it must "return the number of confirmations" in {
chainHandler: ChainHandler =>
for {
bestBlockHashBE <- chainHandler.getBestBlockHash()
confirmations <- chainHandler.getNumberOfConfirmations(bestBlockHashBE)
} yield {
assert(confirmations == Some(1))
}
}
it must "return the height by block stamp" in { chainHandler: ChainHandler =>
for {
bestBlock <- chainHandler.getBestBlockHeader()
stamp1 = BlockStamp.BlockHash(bestBlock.hashBE)
stamp2 = BlockStamp.BlockHeight(bestBlock.height)
stamp3 = BlockStamp.BlockTime(bestBlock.time)
height1 <- chainHandler.getHeightByBlockStamp(stamp1)
height2 <- chainHandler.getHeightByBlockStamp(stamp2)
// TODO implement BlockTime
// height3 <- chainHandler.getHeightByBlockStamp(stamp3)
} yield {
assert(height1 == height2)
// assert(height1 == height3)
}
}
it must "match block filters" in { chainHandler: ChainHandler =>
import scodec.bits._
@ -462,7 +484,7 @@ class ChainHandlerTest extends ChainUnitTest {
BitcoinAddress("n1RH2x3b3ah4TGQtgrmNAHfmad9wr8U2QY").get.scriptPubKey),
startOpt = None,
endOpt = None
)
)(system.dispatcher)
} yield {
assert(Vector(created.blockHashBE) == matched)
}
@ -524,7 +546,7 @@ class ChainHandlerTest extends ChainUnitTest {
private def buildChainHandlerCompetingHeaders(
chainHandler: ChainHandler): Future[ReorgFixture] = {
for {
oldBestTip <- chainHandler.getBestBlockHeader
oldBestTip <- chainHandler.getBestBlockHeader()
(newHeaderB, newHeaderC) = buildCompetingHeaders(oldBestTip)
newChainApi <- chainHandler.processHeaders(Vector(newHeaderB, newHeaderC))
newHeaderDbB <- newChainApi.getHeader(newHeaderB.hashBE)

View File

@ -25,8 +25,7 @@ trait ChainApi extends ChainQueryApi {
* @param header
* @return
*/
def processHeader(header: BlockHeader)(
implicit ec: ExecutionContext): Future[ChainApi] = {
def processHeader(header: BlockHeader): Future[ChainApi] = {
processHeaders(Vector(header))
}
@ -36,37 +35,22 @@ trait ChainApi extends ChainQueryApi {
* @param headers
* @return
*/
def processHeaders(headers: Vector[BlockHeader])(
implicit ec: ExecutionContext): Future[ChainApi]
def processHeaders(headers: Vector[BlockHeader]): Future[ChainApi]
/** Gets a [[org.bitcoins.chain.models.BlockHeaderDb]] from the chain's database */
def getHeader(hash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[BlockHeaderDb]]
def getHeader(hash: DoubleSha256DigestBE): Future[Option[BlockHeaderDb]]
/** Gets all [[org.bitcoins.chain.models.BlockHeaderDb]]s at a given height */
def getHeadersAtHeight(height: Int)(
implicit ec: ExecutionContext): Future[Vector[BlockHeaderDb]]
def getHeadersAtHeight(height: Int): Future[Vector[BlockHeaderDb]]
/** Gets the number of blocks in the database */
def getBlockCount(implicit ec: ExecutionContext): Future[Int]
def getBlockCount(): Future[Int]
/** Gets the hash of the block that is what we consider "best" */
override def getBestBlockHash(
implicit ec: ExecutionContext): Future[DoubleSha256DigestBE]
// /** Gets the hash of the block that is what we consider "best" */
// override def getBestBlockHash: Future[DoubleSha256DigestBE]
/** Gets the best block header we have */
def getBestBlockHeader(
implicit ec: ExecutionContext): Future[BlockHeaderDb] = {
for {
hash <- getBestBlockHash
headerOpt <- getHeader(hash)
} yield headerOpt match {
case None =>
throw new RuntimeException(
s"We found best hash=${hash.hex} but could not retrieve the full header!!!")
case Some(header) => header
}
}
def getBestBlockHeader(): Future[BlockHeaderDb]
/**
* Adds a compact filter header into the filter header chain and returns a new [[ChainApi chain api]]
@ -74,8 +58,7 @@ trait ChainApi extends ChainQueryApi {
*/
def processFilterHeader(
filterHeader: FilterHeader,
blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[ChainApi] = {
blockHash: DoubleSha256DigestBE): Future[ChainApi] = {
processFilterHeaders(Vector(filterHeader), blockHash)
}
@ -85,44 +68,40 @@ trait ChainApi extends ChainQueryApi {
*/
def processFilterHeaders(
filterHeaders: Vector[FilterHeader],
stopHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[ChainApi]
stopHash: DoubleSha256DigestBE): Future[ChainApi]
/**
* Generates a block range in form of (startHeight, stopHash) by the given stop hash.
*/
def nextHeaderBatchRange(stopHash: DoubleSha256DigestBE, batchSize: Int)(
implicit ec: ExecutionContext): Future[Option[(Int, DoubleSha256Digest)]]
def nextHeaderBatchRange(
stopHash: DoubleSha256DigestBE,
batchSize: Int): Future[Option[(Int, DoubleSha256Digest)]]
/**
* Generates a filter header range in form of (startHeight, stopHash) by the given stop hash.
*/
def nextFilterHeaderBatchRange(
stopHash: DoubleSha256DigestBE,
batchSize: Int)(
implicit ec: ExecutionContext): Future[Option[(Int, DoubleSha256Digest)]]
batchSize: Int): Future[Option[(Int, DoubleSha256Digest)]]
/**
* Adds a compact filter into the filter database.
*/
def processFilter(message: CompactFilterMessage)(
implicit ec: ExecutionContext): Future[ChainApi] =
def processFilter(message: CompactFilterMessage): Future[ChainApi] =
processFilters(Vector(message))
/**
* Process all of the given compact filters and returns a new [[ChainApi chain api]]
* that contains these headers.
*/
def processFilters(message: Vector[CompactFilterMessage])(
implicit ec: ExecutionContext): Future[ChainApi]
def processFilters(message: Vector[CompactFilterMessage]): Future[ChainApi]
/**
* Adds a compact filter header check point into the list of check points.
*/
def processCheckpoint(
filterHeaderHash: DoubleSha256DigestBE,
blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[ChainApi] = {
blockHash: DoubleSha256DigestBE): Future[ChainApi] = {
processCheckpoints(Vector(filterHeaderHash), blockHash)
}
@ -131,38 +110,35 @@ trait ChainApi extends ChainQueryApi {
*/
def processCheckpoints(
checkpoints: Vector[DoubleSha256DigestBE],
blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[ChainApi]
blockHash: DoubleSha256DigestBE): Future[ChainApi]
/** Gets the number of compact filter headers in the database */
def getFilterHeaderCount(implicit ec: ExecutionContext): Future[Int]
def getFilterHeaderCount: Future[Int]
/**
* Looks up a compact filter header by its height.
*/
def getFilterHeadersAtHeight(height: Int)(
implicit ec: ExecutionContext): Future[Vector[CompactFilterHeaderDb]]
def getFilterHeadersAtHeight(
height: Int): Future[Vector[CompactFilterHeaderDb]]
/**
* Looks up a compact filter header by its hash.
*/
def getFilterHeader(blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[CompactFilterHeaderDb]]
def getFilterHeader(
blockHash: DoubleSha256DigestBE): Future[Option[CompactFilterHeaderDb]]
/**
* Looks up a compact filter by its hash.
*/
def getFilter(hash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[CompactFilterDb]]
def getFilter(hash: DoubleSha256DigestBE): Future[Option[CompactFilterDb]]
/** Gets the number of compact filters in the database */
def getFilterCount(implicit ec: ExecutionContext): Future[Int]
def getFilterCount: Future[Int]
/**
* Looks up a compact filter by its height.
*/
def getFiltersAtHeight(height: Int)(
implicit ec: ExecutionContext): Future[Vector[CompactFilterDb]]
def getFiltersAtHeight(height: Int): Future[Vector[CompactFilterDb]]
/**
* Iterates over the block filters in order to find filters that match to the given addresses
@ -182,15 +158,8 @@ trait ChainApi extends ChainQueryApi {
endOpt: Option[BlockStamp],
batchSize: Int,
parallelismLevel: Int)(
implicit ec: ExecutionContext): Future[Vector[DoubleSha256DigestBE]]
ec: ExecutionContext): Future[Vector[DoubleSha256DigestBE]]
/** Returns the block height of the given block stamp */
def getHeightByBlockStamp(blockStamp: BlockStamp)(
implicit ec: ExecutionContext): Future[Int]
/** @inheritdoc */
override def getBlockHeight(blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[Int]] =
getHeader(blockHash).map(_.map(_.height))
def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int]
}

View File

@ -33,12 +33,13 @@ case class ChainHandler(
filterDAO: CompactFilterDAO,
blockchains: Vector[Blockchain],
blockFilterCheckpoints: Map[DoubleSha256DigestBE, DoubleSha256DigestBE])(
implicit val chainConfig: ChainAppConfig)
implicit val chainConfig: ChainAppConfig,
executionContext: ExecutionContext)
extends ChainApi
with ChainVerificationLogger {
/** @inheritdoc */
override def getBlockCount(implicit ec: ExecutionContext): Future[Int] = {
override def getBlockCount(): Future[Int] = {
logger.debug(s"Querying for block count")
blockHeaderDAO.maxHeight.map { height =>
logger.debug(s"getBlockCount result: count=$height")
@ -46,9 +47,21 @@ case class ChainHandler(
}
}
override def getBestBlockHeader(): Future[BlockHeaderDb] = {
for {
hash <- getBestBlockHash()
headerOpt <- getHeader(hash)
} yield headerOpt match {
case None =>
throw new RuntimeException(
s"We found best hash=${hash.hex} but could not retrieve the full header!!!")
case Some(header) => header
}
}
/** @inheritdoc */
override def getHeader(hash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[BlockHeaderDb]] = {
override def getHeader(
hash: DoubleSha256DigestBE): Future[Option[BlockHeaderDb]] = {
blockHeaderDAO.findByHash(hash).map { header =>
logger.debug(s"Looking for header by hash=$hash")
val resultStr = header
@ -60,8 +73,8 @@ case class ChainHandler(
}
/** @inheritdoc */
override def processHeaders(headers: Vector[BlockHeader])(
implicit ec: ExecutionContext): Future[ChainApi] = {
override def processHeaders(
headers: Vector[BlockHeader]): Future[ChainApi] = {
if (headers.isEmpty) {
Future.successful(this)
} else {
@ -92,8 +105,7 @@ case class ChainHandler(
/**
* @inheritdoc
*/
override def getBestBlockHash(
implicit ec: ExecutionContext): Future[DoubleSha256DigestBE] = {
override def getBestBlockHash(): Future[DoubleSha256DigestBE] = {
logger.debug(s"Querying for best block hash")
//naive implementation, this is looking for the tip with the _most_ proof of work
//this does _not_ mean that it is on the chain that has the most work
@ -121,8 +133,7 @@ case class ChainHandler(
/** @inheritdoc */
override def nextHeaderBatchRange(
prevStopHash: DoubleSha256DigestBE,
batchSize: Int)(implicit ec: ExecutionContext): Future[
Option[(Int, DoubleSha256Digest)]] = {
batchSize: Int): Future[Option[(Int, DoubleSha256Digest)]] = {
val startHeightF = if (prevStopHash == DoubleSha256DigestBE.empty) {
Future.successful(0)
} else {
@ -151,8 +162,7 @@ case class ChainHandler(
/** @inheritdoc */
override def nextFilterHeaderBatchRange(
prevStopHash: DoubleSha256DigestBE,
batchSize: Int)(implicit ec: ExecutionContext): Future[
Option[(Int, DoubleSha256Digest)]] = {
batchSize: Int): Future[Option[(Int, DoubleSha256Digest)]] = {
val startHeightF = if (prevStopHash == DoubleSha256DigestBE.empty) {
Future.successful(0)
} else {
@ -182,8 +192,7 @@ case class ChainHandler(
/** @inheritdoc */
override def processFilterHeaders(
filterHeaders: Vector[FilterHeader],
stopHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[ChainApi] = {
stopHash: DoubleSha256DigestBE): Future[ChainApi] = {
val filterHeadersToCreateF = for {
blockHeaders <- blockHeaderDAO
@ -223,8 +232,8 @@ case class ChainHandler(
}
/** @inheritdoc */
override def processFilters(messages: Vector[CompactFilterMessage])(
implicit ec: ExecutionContext): Future[ChainApi] = {
override def processFilters(
messages: Vector[CompactFilterMessage]): Future[ChainApi] = {
logger.debug(s"processFilters: messages=${messages}")
val filterHeadersF = filterHeaderDAO
@ -291,8 +300,7 @@ case class ChainHandler(
/** @inheritdoc */
override def processCheckpoints(
checkpoints: Vector[DoubleSha256DigestBE],
blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[ChainApi] = {
blockHash: DoubleSha256DigestBE): Future[ChainApi] = {
val blockHeadersF: Future[Seq[BlockHeaderDb]] = Future
.traverse(checkpoints.indices.toVector) { i =>
@ -317,19 +325,17 @@ case class ChainHandler(
}
/** @inheritdoc */
override def getFilter(blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[CompactFilterDb]] = {
override def getFilter(
blockHash: DoubleSha256DigestBE): Future[Option[CompactFilterDb]] = {
filterDAO.findByBlockHash(blockHash)
}
/** @inheritdoc */
override def getHeadersAtHeight(height: Int)(
implicit ec: ExecutionContext): Future[Vector[BlockHeaderDb]] =
override def getHeadersAtHeight(height: Int): Future[Vector[BlockHeaderDb]] =
blockHeaderDAO.getAtHeight(height)
/** @inheritdoc */
override def getFilterHeaderCount(
implicit ec: ExecutionContext): Future[Int] = {
override def getFilterHeaderCount: Future[Int] = {
logger.debug(s"Querying for filter header count")
filterHeaderDAO.maxHeight.map { height =>
logger.debug(s"getFilterHeaderCount result: count=$height")
@ -338,17 +344,17 @@ case class ChainHandler(
}
/** @inheritdoc */
override def getFilterHeadersAtHeight(height: Int)(
implicit ec: ExecutionContext): Future[Vector[CompactFilterHeaderDb]] =
override def getFilterHeadersAtHeight(
height: Int): Future[Vector[CompactFilterHeaderDb]] =
filterHeaderDAO.getAtHeight(height)
/** @inheritdoc */
override def getFilterHeader(blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[CompactFilterHeaderDb]] =
override def getFilterHeader(
blockHash: DoubleSha256DigestBE): Future[Option[CompactFilterHeaderDb]] =
filterHeaderDAO.findByBlockHash(blockHash)
/** @inheritdoc */
override def getFilterCount(implicit ec: ExecutionContext): Future[Int] = {
override def getFilterCount: Future[Int] = {
logger.debug(s"Querying for filter count")
filterDAO.maxHeight.map { height =>
logger.debug(s"getFilterCount result: count=$height")
@ -357,8 +363,8 @@ case class ChainHandler(
}
/** @inheritdoc */
override def getFiltersAtHeight(height: Int)(
implicit ec: ExecutionContext): Future[Vector[CompactFilterDb]] =
override def getFiltersAtHeight(
height: Int): Future[Vector[CompactFilterDb]] =
filterDAO.getAtHeight(height)
/** Implements [[ChainApi.getMatchingBlocks()]].
@ -384,7 +390,7 @@ case class ChainHandler(
endOpt: Option[BlockStamp] = None,
batchSize: Int = chainConfig.filterBatchSize,
parallelismLevel: Int = Runtime.getRuntime.availableProcessors())(
implicit ec: ExecutionContext): Future[Vector[DoubleSha256DigestBE]] = {
ec: ExecutionContext): Future[Vector[DoubleSha256DigestBE]] = {
require(batchSize > 0, "batch size must be greater than zero")
require(parallelismLevel > 0, "parallelism level must be greater than zero")
@ -485,8 +491,7 @@ case class ChainHandler(
}
/** @inheritdoc */
override def getHeightByBlockStamp(blockStamp: BlockStamp)(
implicit ec: ExecutionContext): Future[Int] =
override def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int] =
blockStamp match {
case blockHeight: BlockStamp.BlockHeight =>
Future.successful(blockHeight.height)
@ -501,6 +506,25 @@ case class ChainHandler(
Future.failed(new RuntimeException(s"Not implemented: $blockTime"))
}
/** @inheritdoc */
override def getBlockHeight(
blockHash: DoubleSha256DigestBE): Future[Option[Int]] =
getHeader(blockHash).map(_.map(_.height))
/** @inheritdoc */
override def getNumberOfConfirmations(
blockHash: DoubleSha256DigestBE): Future[Option[Int]] = {
getBlockHeight(blockHash).flatMap {
case None => FutureUtil.none
case Some(blockHeight) =>
for {
tipHash <- getBestBlockHash()
tipHeightOpt <- getBlockHeight(tipHash)
} yield {
tipHeightOpt.map(tipHeight => tipHeight - blockHeight + 1)
}
}
}
}
object ChainHandler {
@ -530,7 +554,8 @@ object ChainHandler {
filterHeaderDAO: CompactFilterHeaderDAO,
filterDAO: CompactFilterDAO,
blockchains: Blockchain)(
implicit chainConfig: ChainAppConfig): ChainHandler = {
implicit ec: ExecutionContext,
chainConfig: ChainAppConfig): ChainHandler = {
new ChainHandler(blockHeaderDAO = blockHeaderDAO,
filterHeaderDAO = filterHeaderDAO,
filterDAO = filterDAO,

View File

@ -1,8 +1,9 @@
package org.bitcoins.core.api
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.util.FutureUtil
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.Future
/**
* This trait provides methods to query various types of blockchain data.
@ -10,12 +11,14 @@ import scala.concurrent.{ExecutionContext, Future}
trait ChainQueryApi {
/** Gets the height of the given block */
def getBlockHeight(blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[Int]]
def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]]
/** Gets the hash of the block that is what we consider "best" */
def getBestBlockHash(
implicit ec: ExecutionContext): Future[DoubleSha256DigestBE]
def getBestBlockHash(): Future[DoubleSha256DigestBE]
/** Gets number of confirmations for the given block hash*/
def getNumberOfConfirmations(
blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]]
}
@ -24,13 +27,17 @@ object ChainQueryApi {
object NoOp extends ChainQueryApi {
/** Gets the height of the given block */
override def getBlockHeight(blockHash: DoubleSha256DigestBE)(
implicit ec: ExecutionContext): Future[Option[Int]] =
Future.successful(None)
override def getBlockHeight(
blockHash: DoubleSha256DigestBE): Future[Option[Int]] =
FutureUtil.none
/** Gets the hash of the block that is what we consider "best" */
override def getBestBlockHash(
implicit ec: ExecutionContext): Future[DoubleSha256DigestBE] =
override def getBestBlockHash(): Future[DoubleSha256DigestBE] =
Future.successful(DoubleSha256DigestBE.empty)
/** Gets number of confirmations for the given block hash. It returns None of no block found */
override def getNumberOfConfirmations(
blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]] =
FutureUtil.none
}
}

View File

@ -1,7 +1,6 @@
package org.bitcoins.core.util
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import scala.concurrent.{ExecutionContext, Future}
object FutureUtil {
@ -24,6 +23,8 @@ object FutureUtil {
val unit: Future[Unit] = Future.successful(())
def none[T]: Future[Option[T]] = Future.successful(Option.empty[T])
/**
* Folds over the given elements sequentially in a non-blocking async way
* @param init the initialized value for the accumulator

View File

@ -110,22 +110,23 @@ val walletF: Future[LockedWalletApi] = configF.flatMap { _ =>
// 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.crypto._
import org.bitcoins.core.protocol.transaction._
import org.bitcoins.core.currency._
val transactionF: Future[Transaction] = for {
val transactionF: Future[(Transaction, Option[DoubleSha256DigestBE])] = for {
wallet <- walletF
address <- wallet.getNewAddress()
txid <- bitcoind.sendToAddress(address, 3.bitcoin)
transaction <- bitcoind.getRawTransaction(txid)
} yield transaction.hex
} yield (transaction.hex, transaction.blockhash)
// 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)
(tx, blockhash) <- transactionF
_ <- wallet.processTransaction(tx, blockhash)
balance <- wallet.getBalance
} yield balance

View File

@ -53,7 +53,7 @@ class NeutrinoNodeWithWalletTest extends NodeUnitTest {
val onBlock: DataMessageHandler.OnBlockReceived = { block =>
for {
wallet <- walletF
_ <- wallet.processBlock(block, confirmations = 6)
_ <- wallet.processBlock(block)
} yield ()
}
val onCompactFilter: OnCompactFilterReceived = { (blockHash, blockFilter) =>
@ -98,14 +98,16 @@ class NeutrinoNodeWithWalletTest extends NodeUnitTest {
expectedConfirmedAmount = 0.sats,
expectedUnconfirmedAmount = BitcoinSWalletTest.initialFunds - TestAmount - TestFees,
expectedUtxos = 1,
expectedAddresses = 2)
expectedAddresses = 2
)
}
val condition2 = { () =>
condition(
expectedConfirmedAmount = TestAmount,
expectedUnconfirmedAmount = BitcoinSWalletTest.initialFunds - TestAmount - TestFees,
expectedUtxos = 2,
expectedAddresses = 3)
expectedAddresses = 3
)
}
for {

View File

@ -46,7 +46,7 @@ class SpvNodeWithWalletTest extends NodeUnitTest {
if (expectedTxId == tx.txId) {
for {
prevBalance <- wallet.getUnconfirmedBalance()
_ <- wallet.processTransaction(tx, confirmations = 0)
_ <- wallet.processTransaction(tx, None)
balance <- wallet.getUnconfirmedBalance()
} yield {
val result = balance == prevBalance + amountFromBitcoind

View File

@ -33,7 +33,7 @@ case class NeutrinoNode(
val res = for {
node <- super.start()
chainApi <- chainApiFromDb()
bestHash <- chainApi.getBestBlockHash
bestHash <- chainApi.getBestBlockHash()
peerMsgSender <- peerMsgSenderF
_ <- peerMsgSender.sendGetCompactFilterCheckPointMessage(
stopHash = bestHash.flip)

View File

@ -8,8 +8,8 @@ import org.bitcoins.chain.models.{
CompactFilterDAO,
CompactFilterHeaderDAO
}
import org.bitcoins.core.api.NodeApi
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.api.{ChainQueryApi, NodeApi}
import org.bitcoins.core.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.core.p2p.{NetworkPayload, TypeIdentifier}
import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.protocol.script.ScriptPubKey
@ -36,7 +36,7 @@ import scala.util.{Failure, Success}
/**
This a base trait for various kinds of nodes. It contains house keeping methods required for all nodes.
*/
trait Node extends NodeApi with P2PLogger {
trait Node extends NodeApi with ChainQueryApi with P2PLogger {
implicit def system: ActorSystem
@ -180,7 +180,7 @@ trait Node extends NodeApi with P2PLogger {
def sync(): Future[Unit] = {
for {
chainApi <- chainApiFromDb()
hash <- chainApi.getBestBlockHash
hash <- chainApi.getBestBlockHash()
header <- chainApi
.getHeader(hash)
.map(_.get) // .get is safe since this is an internal call
@ -223,4 +223,18 @@ trait Node extends NodeApi with P2PLogger {
} yield ()
}
/** Gets the height of the given block */
override def getBlockHeight(
blockHash: DoubleSha256DigestBE): Future[Option[Int]] =
chainApiFromDb().flatMap(_.getBlockHeight(blockHash))
/** Gets the hash of the block that is what we consider "best" */
override def getBestBlockHash(): Future[DoubleSha256DigestBE] =
chainApiFromDb().flatMap(_.getBestBlockHash())
/** Gets number of confirmations for the given block hash*/
def getNumberOfConfirmations(
blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]] =
chainApiFromDb().flatMap(_.getNumberOfConfirmations(blockHashOpt))
}

View File

@ -1,8 +1,9 @@
package org.bitcoins.testkit.chain
import org.bitcoins.chain.models.{BlockHeaderDb, BlockHeaderDbHelper}
import org.bitcoins.chain.models._
import org.bitcoins.core.crypto
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.core.gcs.{BlockFilter, FilterHeader, GolombFilter}
import org.bitcoins.core.protocol.blockchain.{
BlockHeader,
MainNetChainParams,
@ -20,6 +21,20 @@ sealed abstract class ChainTestUtil {
lazy val regTestGenesisHeaderDb: BlockHeaderDb = {
BlockHeaderDbHelper.fromBlockHeader(height = 0, bh = regTestHeader)
}
lazy val regTestGenesisHeaderCompactFilter: GolombFilter =
BlockFilter.apply(regTestChainParams.genesisBlock, Vector.empty)
lazy val regTestGenesisHeaderCompactFilterDb: CompactFilterDb =
CompactFilterDbHelper.fromGolombFilter(regTestGenesisHeaderCompactFilter,
regTestHeader.hashBE,
0)
lazy val regTestGenesisHeaderCompactFilterHeader: FilterHeader = FilterHeader(
regTestGenesisHeaderCompactFilter.hash,
DoubleSha256Digest.empty)
lazy val regTestGenesisHeaderCompactFilterHeaderDb: CompactFilterHeaderDb =
CompactFilterHeaderDbHelper.fromFilterHeader(
regTestGenesisHeaderCompactFilterHeader,
regTestHeader.hashBE,
0)
lazy val mainnetChainParam: MainNetChainParams.type = MainNetChainParams

View File

@ -12,10 +12,10 @@ import org.bitcoins.chain.models._
import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader}
import org.bitcoins.db.AppConfig
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.testkit.{chain, BitcoinSTestAppConfig}
import org.bitcoins.testkit.chain.fixture._
import org.bitcoins.testkit.fixtures.BitcoinSFixture
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.testkit.{chain, BitcoinSTestAppConfig}
import org.bitcoins.zmq.ZMQSubscriber
import org.scalatest._
import play.api.libs.json.{JsError, JsSuccess, Json}
@ -289,6 +289,12 @@ object ChainUnitTest extends ChainVerificationLogger {
val genesisHeaderDb: BlockHeaderDb = ChainTestUtil.regTestGenesisHeaderDb
val genesisFilterDb: CompactFilterDb =
ChainTestUtil.regTestGenesisHeaderCompactFilterDb
val genesisFilterHeaderDb: CompactFilterHeaderDb =
ChainTestUtil.regTestGenesisHeaderCompactFilterHeaderDb
def createChainHandler()(
implicit ec: ExecutionContext,
appConfig: ChainAppConfig): Future[ChainHandler] = {
@ -449,6 +455,9 @@ object ChainUnitTest extends ChainVerificationLogger {
for {
chainHandler <- chainHandlerF
genHeader <- chainHandler.blockHeaderDAO.create(genesisHeaderDb)
genFilterHeader <- chainHandler.filterHeaderDAO.create(
genesisFilterHeaderDb)
genFilter <- chainHandler.filterDAO.create(genesisFilterDb)
} yield genHeader
}

View File

@ -5,7 +5,9 @@ import java.net.InetSocketAddress
import akka.actor.ActorSystem
import org.bitcoins.chain.api.ChainApi
import org.bitcoins.chain.config.ChainAppConfig
import org.bitcoins.core.api.ChainQueryApi
import org.bitcoins.core.config.NetworkParameters
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.db.AppConfig
import org.bitcoins.node._
import org.bitcoins.node.config.NodeAppConfig
@ -252,10 +254,9 @@ object NodeUnitTest extends P2PLogger {
for {
bitcoind <- BitcoinSFixture.createBitcoindWithFunds(versionOpt)
node <- createSpvNode(bitcoind, callbacks)
chain <- node.chainApiFromDb()
fundedWallet <- BitcoinSWalletTest.fundedWalletAndBitcoind(bitcoind,
node,
chain)
node)
} yield {
SpvNodeFundedWalletBitcoind(node = node,
wallet = fundedWallet.wallet,
@ -274,10 +275,9 @@ object NodeUnitTest extends P2PLogger {
for {
bitcoind <- BitcoinSFixture.createBitcoindWithFunds(versionOpt)
node <- createNeutrinoNode(bitcoind, callbacks)
chain <- node.chainApiFromDb()
fundedWallet <- BitcoinSWalletTest.fundedWalletAndBitcoind(bitcoind,
node,
chain)
node)
} yield {
NeutrinoNodeFundedWalletBitcoind(node = node,
wallet = fundedWallet.wallet,

View File

@ -3,7 +3,7 @@ package org.bitcoins.testkit.wallet
import akka.actor.ActorSystem
import com.typesafe.config.{Config, ConfigFactory}
import org.bitcoins.core.api.{ChainQueryApi, NodeApi}
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.currency._
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.db.AppConfig
@ -227,12 +227,10 @@ object BitcoinSWalletTest extends WalletLogger {
val WalletWithBitcoind(wallet, bitcoind) = pair
for {
addr <- wallet.getNewAddress()
tx <- bitcoind
.sendToAddress(addr, initialFunds)
.flatMap(bitcoind.getRawTransaction(_))
txId <- bitcoind.sendToAddress(addr, initialFunds)
_ <- bitcoind.getNewAddress.flatMap(bitcoind.generateToAddress(6, _))
_ <- wallet.processTransaction(tx.hex, 6)
tx <- bitcoind.getRawTransaction(txId)
_ <- wallet.processTransaction(tx.hex, tx.blockhash)
balance <- wallet.getBalance()
} yield {

View File

@ -1,33 +1,31 @@
package org.bitcoins.testkit.wallet
import org.bitcoins.testkit.Implicits._
import org.bitcoins.core.config.RegTest
import org.bitcoins.core.crypto._
import org.bitcoins.core.currency._
import org.bitcoins.core.hd._
import org.bitcoins.core.protocol.Bech32Address
import org.bitcoins.core.protocol.blockchain.{
ChainParams,
RegTestNetChainParams
}
import org.bitcoins.testkit.core.gen.CryptoGenerators
import org.bitcoins.wallet.models.AccountDb
import org.bitcoins.core.hd._
import org.bitcoins.core.protocol.script.ScriptWitness
import org.bitcoins.core.protocol.script.P2WPKHWitnessV0
import org.bitcoins.wallet.models.LegacySpendingInfo
import org.bitcoins.core.protocol.transaction.TransactionOutPoint
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.wallet.models.SegwitV0SpendingInfo
import org.bitcoins.testkit.core.gen.NumberGenerator
import org.scalacheck.Gen
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.testkit.fixtures.WalletDAOs
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
import org.bitcoins.wallet.models.SegWitAddressDb
import org.bitcoins.core.protocol.Bech32Address
import org.bitcoins.core.protocol.script.P2WPKHWitnessSPKV0
import org.bitcoins.core.protocol.script.{
P2WPKHWitnessSPKV0,
P2WPKHWitnessV0,
ScriptPubKey,
ScriptWitness
}
import org.bitcoins.core.protocol.transaction.{
TransactionOutPoint,
TransactionOutput
}
import org.bitcoins.core.util.CryptoUtil
import org.bitcoins.wallet.models.AddressDb
import org.bitcoins.testkit.Implicits._
import org.bitcoins.testkit.core.gen.{CryptoGenerators, NumberGenerator}
import org.bitcoins.testkit.fixtures.WalletDAOs
import org.bitcoins.wallet.models._
import scala.concurrent.{ExecutionContext, Future}
object WalletTestUtil {
@ -78,9 +76,8 @@ object WalletTestUtil {
private def randomTXID = CryptoGenerators.doubleSha256Digest.sampleSome.flip
private def randomVout = NumberGenerator.uInt32s.sampleSome
/** Between 0 and 10 confirmations */
private def randomConfs: Int = Gen.choose(0, 10).sampleSome
private def randomBlockHash =
CryptoGenerators.doubleSha256Digest.sampleSome.flip
private def randomSpent: Boolean = math.random > 0.5
@ -90,13 +87,15 @@ object WalletTestUtil {
TransactionOutput(1.bitcoin, spk)
val scriptWitness = randomScriptWitness
val privkeyPath = WalletTestUtil.sampleSegwitPath
SegwitV0SpendingInfo(confirmations = randomConfs,
spent = randomSpent,
txid = randomTXID,
outPoint = outpoint,
output = output,
privKeyPath = privkeyPath,
scriptWitness = scriptWitness)
SegwitV0SpendingInfo(
spent = randomSpent,
txid = randomTXID,
outPoint = outpoint,
output = output,
privKeyPath = privkeyPath,
scriptWitness = scriptWitness,
blockHash = Some(randomBlockHash)
)
}
def sampleLegacyUTXO(spk: ScriptPubKey): LegacySpendingInfo = {
@ -105,12 +104,12 @@ object WalletTestUtil {
val output =
TransactionOutput(1.bitcoin, spk)
val privKeyPath = WalletTestUtil.sampleLegacyPath
LegacySpendingInfo(confirmations = randomConfs,
spent = randomSpent,
LegacySpendingInfo(spent = randomSpent,
txid = randomTXID,
outPoint = outpoint,
output = output,
privKeyPath = privKeyPath)
privKeyPath = privKeyPath,
blockHash = Some(randomBlockHash))
}
/** Given an account returns a sample address */

View File

@ -1,17 +1,15 @@
package org.bitcoins.wallet
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.testkit.Implicits._
import org.scalatest.FutureOutcome
import org.bitcoins.core.crypto.{DoubleSha256DigestBE, ECPrivateKey}
import org.bitcoins.core.currency._
import scala.concurrent.Future
import org.scalatest.compatible.Assertion
import org.bitcoins.wallet.api.UnlockedWalletApi
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.testkit.Implicits._
import org.bitcoins.testkit.core.gen.TransactionGenerators
import org.bitcoins.core.protocol.script.ScriptPubKey
import scala.annotation.tailrec
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.wallet.api.UnlockedWalletApi
import org.scalatest.FutureOutcome
import org.scalatest.compatible.Assertion
import scala.concurrent.Future
class ProcessTransactionTest extends BitcoinSWalletTest {
override type FixtureParam = UnlockedWalletApi
@ -47,24 +45,26 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
tx = TransactionGenerators
.transactionTo(address.scriptPubKey)
.sampleSome
blockHash = DoubleSha256DigestBE.fromBytes(
ECPrivateKey.freshPrivateKey.bytes)
_ <- wallet.processTransaction(tx, confirmations = 0)
_ <- wallet.processTransaction(tx, None)
oldConfirmed <- wallet.getConfirmedBalance()
oldUnconfirmed <- wallet.getUnconfirmedBalance()
// repeating the action should not make a difference
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(tx, confirmations = 0)
wallet.processTransaction(tx, None)
}
_ <- wallet.processTransaction(tx, confirmations = 3)
_ <- wallet.processTransaction(tx, Some(blockHash))
newConfirmed <- wallet.getConfirmedBalance()
newUnconfirmed <- wallet.getUnconfirmedBalance()
utxosPostAdd <- wallet.listUtxos()
// repeating the action should not make a difference
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(tx, confirmations = 3)
wallet.processTransaction(tx, Some(blockHash))
}
} yield {
val ourOutputs =
@ -81,7 +81,7 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
val unrelated = TransactionGenerators.transaction.sampleSome
for {
_ <- checkUtxosAndBalance(wallet) {
wallet.processTransaction(unrelated, confirmations = 4)
wallet.processTransaction(unrelated, None)
}
balance <- wallet.getBalance()

View File

@ -1,16 +1,11 @@
package org.bitcoins.wallet
import org.bitcoins.core.currency._
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
import org.bitcoins.wallet.api.{AddUtxoError, AddUtxoSuccess, WalletApi}
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.scalatest.FutureOutcome
import scala.concurrent.Future
import org.bitcoins.core.hd.HDChainType
import org.bitcoins.core.wallet.fee.SatoshisPerByte
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
import org.bitcoins.testkit.wallet.BitcoinSWalletTest.WalletWithBitcoind
import org.scalatest.FutureOutcome
class WalletIntegrationTest extends BitcoinSWalletTest {
@ -50,10 +45,8 @@ class WalletIntegrationTest extends BitcoinSWalletTest {
for {
addr <- wallet.getNewAddress()
tx <- bitcoind
.sendToAddress(addr, valueFromBitcoind)
.flatMap(bitcoind.getRawTransactionRaw(_))
txId <- bitcoind.sendToAddress(addr, valueFromBitcoind)
tx <- bitcoind.getRawTransactionRaw(txId)
// before processing TX, wallet should be completely empty
_ <- wallet.listUtxos().map(utxos => assert(utxos.isEmpty))
@ -63,7 +56,7 @@ class WalletIntegrationTest extends BitcoinSWalletTest {
.map(unconfirmed => assert(unconfirmed == 0.bitcoin))
// after this, tx is unconfirmed in wallet
_ <- wallet.processTransaction(tx, confirmations = 0)
_ <- wallet.processTransaction(tx, None)
// we should now have one UTXO in the wallet
// it should not be confirmed
@ -79,8 +72,11 @@ class WalletIntegrationTest extends BitcoinSWalletTest {
.getUnconfirmedBalance()
.map(unconfirmed => assert(unconfirmed == valueFromBitcoind))
_ <- bitcoind.getNewAddress.flatMap(bitcoind.generateToAddress(6, _))
rawTx <- bitcoind.getRawTransaction(txId)
// after this, tx should be confirmed
_ <- wallet.processTransaction(tx, confirmations = 6)
_ <- wallet.processTransaction(tx, rawTx.blockhash)
_ <- wallet
.listUtxos()
.map { utxos =>

View File

@ -1,17 +1,18 @@
package org.bitcoins.wallet.api
import org.bitcoins.core.currency._
import org.bitcoins.core.number.Int64
import org.bitcoins.core.protocol.script.ScriptPubKey
import org.bitcoins.core.protocol.transaction.TransactionOutput
import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerByte}
import org.bitcoins.testkit.core.gen.{TransactionGenerators, WitnessGenerators}
import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletTestUtil}
import org.scalatest.FutureOutcome
import org.bitcoins.wallet.models.SpendingInfoDb
import org.bitcoins.wallet.models.SegwitV0SpendingInfo
import org.bitcoins.testkit.Implicits._
import org.bitcoins.testkit.core.gen.CryptoGenerators
import org.bitcoins.testkit.core.gen.{
CryptoGenerators,
TransactionGenerators,
WitnessGenerators
}
import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletTestUtil}
import org.bitcoins.wallet.models.{SegwitV0SpendingInfo, SpendingInfoDb}
import org.scalatest.FutureOutcome
class CoinSelectorTest extends BitcoinSWalletTest {
case class CoinSelectionFixture(
@ -30,34 +31,34 @@ class CoinSelectorTest extends BitcoinSWalletTest {
val feeRate = SatoshisPerByte(CurrencyUnits.zero)
val utxo1 = SegwitV0SpendingInfo(
confirmations = 0,
txid = CryptoGenerators.doubleSha256Digest.sampleSome.flip,
spent = false,
id = Some(1),
outPoint = TransactionGenerators.outPoint.sampleSome,
output = TransactionOutput(10.sats, ScriptPubKey.empty),
privKeyPath = WalletTestUtil.sampleSegwitPath,
scriptWitness = WitnessGenerators.scriptWitness.sampleSome
scriptWitness = WitnessGenerators.scriptWitness.sampleSome,
blockHash = None
)
val utxo2 = SegwitV0SpendingInfo(
confirmations = 0,
txid = CryptoGenerators.doubleSha256Digest.sampleSome.flip,
spent = false,
id = Some(2),
outPoint = TransactionGenerators.outPoint.sampleSome,
output = TransactionOutput(90.sats, ScriptPubKey.empty),
privKeyPath = WalletTestUtil.sampleSegwitPath,
scriptWitness = WitnessGenerators.scriptWitness.sampleSome
scriptWitness = WitnessGenerators.scriptWitness.sampleSome,
blockHash = None
)
val utxo3 = SegwitV0SpendingInfo(
confirmations = 0,
txid = CryptoGenerators.doubleSha256Digest.sampleSome.flip,
spent = false,
id = Some(3),
outPoint = TransactionGenerators.outPoint.sampleSome,
output = TransactionOutput(20.sats, ScriptPubKey.empty),
privKeyPath = WalletTestUtil.sampleSegwitPath,
scriptWitness = WitnessGenerators.scriptWitness.sampleSome
scriptWitness = WitnessGenerators.scriptWitness.sampleSome,
blockHash = None
)
test(CoinSelectionFixture(output, feeRate, utxo1, utxo2, utxo3))

View File

@ -84,7 +84,7 @@ sealed abstract class Wallet extends LockedWallet with UnlockedWalletApi {
signed <- txBuilder.sign
ourOuts <- findOurOuts(signed)
// TODO internal
_ <- processOurTransaction(signed, confirmations = 0)
_ <- processOurTransaction(signed, blockHashOpt = None)
} yield {
logger.debug(
s"Signed transaction=${signed.txIdBE.hex} with outputs=${signed.outputs.length}, inputs=${signed.inputs.length}")

View File

@ -51,17 +51,17 @@ trait LockedWalletApi extends WalletApi {
/**
* Processes the given transaction, updating our DB state if it's relevant to us.
* @param transaction The transaction we're processing
* @param confirmations How many confirmations the TX has
* @param blockHash Containing block hash
*/
def processTransaction(
transaction: Transaction,
confirmations: Int): Future[LockedWalletApi]
blockHash: Option[DoubleSha256DigestBE]): Future[LockedWalletApi]
/**
* Processes the give block, updating our DB state if it's relevant to us.
* @param block The block we're processing
*/
def processBlock(block: Block, confirmations: Int): Future[LockedWalletApi]
def processBlock(block: Block): Future[LockedWalletApi]
def processCompactFilter(
blockHash: DoubleSha256Digest,

View File

@ -1,5 +1,6 @@
package org.bitcoins.wallet.internal
import org.bitcoins.core.crypto.DoubleSha256DigestBE
import org.bitcoins.core.number.UInt32
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
@ -23,29 +24,26 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
/** @inheritdoc */
override def processTransaction(
transaction: Transaction,
confirmations: Int): Future[LockedWallet] = {
logger.info(
s"Processing transaction=${transaction.txIdBE} with confirmations=$confirmations")
processTransactionImpl(transaction, confirmations).map {
case ProcessTxResult(incoming, outgoing) =>
logger.info(
s"Finished processing of transaction=${transaction.txIdBE}. Relevant incomingTXOs=${incoming.length}, outgoingTXOs=${outgoing.length}")
this
blockHashOpt: Option[DoubleSha256DigestBE]
): Future[LockedWallet] = {
for {
result <- processTransactionImpl(transaction, blockHashOpt)
} yield {
logger.info(
s"Finished processing of transaction=${transaction.txIdBE}. Relevant incomingTXOs=${result.updatedIncoming.length}, outgoingTXOs=${result.updatedOutgoing.length}")
this
}
}
/** @inheritdoc */
override def processBlock(
block: Block,
confirmations: Int): Future[LockedWallet] = {
logger.info(
s"Processing block=${block.blockHeader.hash.flip} with confirmations=$confirmations")
override def processBlock(block: Block): Future[LockedWallet] = {
logger.info(s"Processing block=${block.blockHeader.hash.flip}")
val res = block.transactions.foldLeft(Future.successful(this)) {
(acc, transaction) =>
for {
_ <- acc
newWallet <- processTransaction(transaction, confirmations)
newWallet <- processTransaction(transaction,
Some(block.blockHeader.hash.flip))
} yield {
newWallet
}
@ -74,10 +72,10 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
*/
private[wallet] def processOurTransaction(
transaction: Transaction,
confirmations: Int): Future[ProcessTxResult] = {
blockHashOpt: Option[DoubleSha256DigestBE]): Future[ProcessTxResult] = {
logger.info(
s"Processing TX from our wallet, transaction=${transaction.txIdBE} with confirmations=$confirmations")
processTransactionImpl(transaction, confirmations).map { result =>
s"Processing TX from our wallet, transaction=${transaction.txIdBE} with blockHash=$blockHashOpt")
processTransactionImpl(transaction, blockHashOpt).map { result =>
val txid = transaction.txIdBE
val changeOutputs = result.updatedIncoming.length
val spentOutputs = result.updatedOutgoing.length
@ -98,49 +96,61 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
*/
private def processTransactionImpl(
transaction: Transaction,
confirmations: Int): Future[ProcessTxResult] = {
blockHashOpt: Option[DoubleSha256DigestBE]): Future[ProcessTxResult] = {
val incomingTxoFut: Future[Vector[SpendingInfoDb]] =
spendingInfoDAO
.findTx(transaction)
.flatMap {
// no existing elements found
case Vector() =>
processNewIncomingTx(transaction, confirmations).map(_.toVector)
logger.info(
s"Processing transaction=${transaction.txIdBE} with blockHash=$blockHashOpt")
for {
aggregate <- {
case txos: Vector[SpendingInfoDb] =>
val txoProcessingFutures =
txos
.map(processExistingIncomingTxo(transaction, confirmations, _))
val incomingTxoFut: Future[Vector[SpendingInfoDb]] =
spendingInfoDAO
.findTx(transaction)
.flatMap {
// no existing elements found
case Vector() =>
processNewIncomingTx(transaction, blockHashOpt)
.map(_.toVector)
Future
.sequence(txoProcessingFutures)
case txos: Vector[SpendingInfoDb] =>
val txoProcessingFutures =
txos
.map(
processExistingIncomingTxo(transaction, blockHashOpt, _))
Future
.sequence(txoProcessingFutures)
}
val outgoingTxFut: Future[Vector[SpendingInfoDb]] = {
for {
outputsBeingSpent <- spendingInfoDAO.findOutputsBeingSpent(
transaction)
processed <- FutureUtil.sequentially(outputsBeingSpent)(
markAsSpentIfUnspent)
} yield processed.flatten.toVector
}
val outgoingTxFut: Future[Vector[SpendingInfoDb]] = {
for {
outputsBeingSpent <- spendingInfoDAO.findOutputsBeingSpent(transaction)
processed <- FutureUtil.sequentially(outputsBeingSpent)(
markAsSpentIfUnspent)
} yield processed.flatten.toVector
val aggregateFut =
for {
incoming <- incomingTxoFut
outgoing <- outgoingTxFut
} yield {
ProcessTxResult(incoming.toList, outgoing.toList)
}
}
aggregateFut.failed.foreach { err =>
val msg = s"Error when processing transaction=${transaction.txIdBE}"
logger.error(msg, err)
}
val aggregateFut =
for {
incoming <- incomingTxoFut
outgoing <- outgoingTxFut
} yield {
ProcessTxResult(incoming.toList, outgoing.toList)
aggregateFut
}
aggregateFut.failed.foreach { err =>
val msg = s"Error when processing transaction=${transaction.txIdBE}"
logger.error(msg, err)
} yield {
aggregate
}
aggregateFut
}
/** If the given UTXO is marked as unspent, updates
@ -149,7 +159,7 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
private val markAsSpentIfUnspent: SpendingInfoDb => Future[
Option[SpendingInfoDb]] = { out =>
if (out.spent) {
Future.successful(None)
FutureUtil.none
} else {
val updatedF =
spendingInfoDAO.update(out.copyWithSpent(spent = true))
@ -171,11 +181,8 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
transaction: Transaction,
index: Int,
spent: Boolean,
confirmations: Int): Future[SpendingInfoDb] =
addUtxo(transaction,
UInt32(index),
spent = spent,
confirmations = confirmations)
blockHash: Option[DoubleSha256DigestBE]): Future[SpendingInfoDb] =
addUtxo(transaction, UInt32(index), spent = spent, blockHash = blockHash)
.flatMap {
case AddUtxoSuccess(utxo) => Future.successful(utxo)
case err: AddUtxoError =>
@ -192,48 +199,61 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
*/
private def processExistingIncomingTxo(
transaction: Transaction,
confirmations: Int,
blockHashOpt: Option[DoubleSha256DigestBE],
foundTxo: SpendingInfoDb): Future[SpendingInfoDb] = {
if (foundTxo.confirmations < confirmations) {
// TODO The assumption here is that double-spends never occur. That's not
// the case. This must be fixed when double-spend logic is implemented.
logger.debug(
s"Increasing confirmation count of txo=${transaction.txIdBE}, old=${foundTxo.confirmations} new=${confirmations}")
val updateF =
spendingInfoDAO.update(
foundTxo.copyWithConfirmations(confirmations = confirmations))
updateF.foreach(tx =>
logger.debug(
s"Updated confirmation count=${tx.confirmations} of output=${foundTxo}"))
updateF.failed.foreach(err =>
logger.error(
s"Failed to update confirmation count of transaction=${transaction.txIdBE}",
err))
updateF
} else if (foundTxo.confirmations > confirmations) {
val msg =
List(
s"Incoming transaction=${transaction.txIdBE} has fewer confirmations=$confirmations",
s"than what we already have registered=${foundTxo.confirmations}! I don't know how",
s"to handle this."
if (foundTxo.txid != transaction.txIdBE) {
val errMsg =
Seq(
s"Found TXO has txid=${foundTxo.txid}, tx we were given has txid=${transaction.txIdBE}.",
"This is either a reorg or a double spent, which is not implemented yet"
).mkString(" ")
logger.warn(msg)
Future.failed(new RuntimeException(msg))
logger.error(errMsg)
Future.failed(new RuntimeException(errMsg))
} else {
if (foundTxo.txid == transaction.txIdBE) {
logger.debug(
s"Skipping further processing of transaction=${transaction.txIdBE}, already processed.")
Future.successful(foundTxo)
} else {
val errMsg =
Seq(
s"Found TXO has txid=${foundTxo.txid}, tx we were given has txid=${transaction.txIdBE}.",
"This is either a reorg or a double spent, which is not implemented yet"
).mkString(" ")
logger.error(errMsg)
Future.failed(new RuntimeException(errMsg))
(foundTxo.blockHash, blockHashOpt) match {
case (None, Some(blockHash)) =>
logger.debug(
s"Updating block_hash of txo=${transaction.txIdBE}, new block hash=${blockHash}")
val updateF =
spendingInfoDAO.update(
foundTxo
.copyWithBlockHash(blockHash = blockHash))
updateF.foreach(tx =>
logger.debug(
s"Updated block_hash of txo=${tx.txid} new block hash=${blockHash}"))
updateF.failed.foreach(err =>
logger.error(
s"Failed to update confirmation count of transaction=${transaction.txIdBE}",
err))
updateF
case (Some(oldBlockHash), Some(newBlockHash)) =>
if (oldBlockHash == newBlockHash) {
logger.debug(
s"Skipping further processing of transaction=${transaction.txIdBE}, already processed.")
Future.successful(foundTxo)
} else {
val errMsg =
Seq(
s"Found TXO has block hash=${oldBlockHash}, tx we were given has block hash=${newBlockHash}.",
"This is either a reorg or a double spent, which is not implemented yet"
).mkString(" ")
logger.error(errMsg)
Future.failed(new RuntimeException(errMsg))
}
case (Some(blockHash), None) =>
val msg =
List(
s"Incoming transaction=${transaction.txIdBE} already has block hash=$blockHash! assigned",
s" I don't know how to handle this."
).mkString(" ")
logger.warn(msg)
Future.failed(new RuntimeException(msg))
case (None, None) =>
logger.debug(
s"Skipping further processing of transaction=${transaction.txIdBE}, already processed.")
Future.successful(foundTxo)
}
}
}
@ -245,7 +265,7 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
*/
private def processNewIncomingTx(
transaction: Transaction,
confirmations: Int): Future[Seq[SpendingInfoDb]] = {
blockHashOpt: Option[DoubleSha256DigestBE]): Future[Seq[SpendingInfoDb]] = {
addressDAO.findAll().flatMap { addrs =>
val relevantOutsWithIdx: Seq[OutputWithIndex] = {
val withIndex =
@ -281,9 +301,9 @@ private[wallet] trait TransactionProcessing extends WalletLogger {
out =>
processUtxo(transaction,
out.index,
confirmations = confirmations,
// TODO is this correct?
spent = false))
spent = false,
blockHash = blockHashOpt))
}
addUTXOsFut

View File

@ -48,30 +48,30 @@ private[wallet] trait UtxoHandling extends WalletLogger {
/** Constructs a DB level representation of the given UTXO, and persist it to disk */
private def writeUtxo(
txid: DoubleSha256DigestBE,
confirmations: Int,
spent: Boolean,
output: TransactionOutput,
outPoint: TransactionOutPoint,
addressDb: AddressDb): Future[SpendingInfoDb] = {
addressDb: AddressDb,
blockHash: Option[DoubleSha256DigestBE]): Future[SpendingInfoDb] = {
val utxo: SpendingInfoDb = addressDb match {
case segwitAddr: SegWitAddressDb =>
SegwitV0SpendingInfo(
confirmations = confirmations,
spent = spent,
txid = txid,
outPoint = outPoint,
output = output,
privKeyPath = segwitAddr.path,
scriptWitness = segwitAddr.witnessScript
scriptWitness = segwitAddr.witnessScript,
blockHash = blockHash
)
case LegacyAddressDb(path, _, _, _, _) =>
LegacySpendingInfo(confirmations = confirmations,
spent = spent,
LegacySpendingInfo(spent = spent,
txid = txid,
outPoint = outPoint,
output = output,
privKeyPath = path)
privKeyPath = path,
blockHash = blockHash)
case nested: NestedSegWitAddressDb =>
throw new IllegalArgumentException(
s"Bad utxo $nested. Note: nested segwit is not implemented")
@ -93,8 +93,8 @@ private[wallet] trait UtxoHandling extends WalletLogger {
protected def addUtxo(
transaction: Transaction,
vout: UInt32,
confirmations: Int,
spent: Boolean): Future[AddUtxoResult] = {
spent: Boolean,
blockHash: Option[DoubleSha256DigestBE]): Future[AddUtxoResult] = {
import AddUtxoError._
logger.info(s"Adding UTXO to wallet: ${transaction.txId.hex}:${vout.toInt}")
@ -127,11 +127,11 @@ private[wallet] trait UtxoHandling extends WalletLogger {
val biasedE: CompatEither[AddUtxoError, Future[SpendingInfoDb]] = for {
addressDb <- addressDbE
} yield writeUtxo(txid = transaction.txIdBE,
confirmations = confirmations,
spent = spent,
output,
outPoint,
addressDb)
addressDb,
blockHash)
EitherUtil.liftRightBiasedFutureE(biasedE)
} map {

View File

@ -1,7 +1,8 @@
package org.bitcoins.wallet.models
import org.bitcoins.core.crypto.Sign
import org.bitcoins.core.crypto.{BIP39Seed, DoubleSha256DigestBE, Sign}
import org.bitcoins.core.currency.CurrencyUnit
import org.bitcoins.core.hd.{HDPath, LegacyHDPath, SegWitHDPath}
import org.bitcoins.core.protocol.script.{ScriptPubKey, ScriptWitness}
import org.bitcoins.core.protocol.transaction.{
TransactionOutPoint,
@ -12,11 +13,6 @@ import org.bitcoins.core.wallet.utxo.{BitcoinUTXOSpendingInfo, ConditionalPath}
import org.bitcoins.db.{DbRowAutoInc, TableAutoInc}
import slick.jdbc.SQLiteProfile.api._
import slick.lifted.ProvenShape
import org.bitcoins.core.hd.HDPath
import org.bitcoins.core.hd.SegWitHDPath
import org.bitcoins.core.crypto.BIP39Seed
import org.bitcoins.core.hd.LegacyHDPath
import org.bitcoins.core.crypto.DoubleSha256DigestBE
/**
* DB representation of a native V0
@ -29,8 +25,8 @@ case class SegwitV0SpendingInfo(
scriptWitness: ScriptWitness,
txid: DoubleSha256DigestBE,
spent: Boolean,
confirmations: Int,
id: Option[Long] = None
id: Option[Long] = None,
blockHash: Option[DoubleSha256DigestBE]
) extends SpendingInfoDb {
override val redeemScriptOpt: Option[ScriptPubKey] = None
override val scriptWitnessOpt: Option[ScriptWitness] = Some(scriptWitness)
@ -38,13 +34,16 @@ case class SegwitV0SpendingInfo(
override type PathType = SegWitHDPath
override type SpendingInfoType = SegwitV0SpendingInfo
def copyWithSpent(spent: Boolean): SegwitV0SpendingInfo = copy(spent = spent)
def copyWithConfirmations(confirmations: Int): SegwitV0SpendingInfo =
copy(confirmations = confirmations)
override def copyWithSpent(spent: Boolean): SegwitV0SpendingInfo =
copy(spent = spent)
override def copyWithId(id: Long): SegwitV0SpendingInfo =
copy(id = Some(id))
/** Updates the `blockHash` field */
override def copyWithBlockHash(
blockHash: DoubleSha256DigestBE): SegwitV0SpendingInfo =
copy(blockHash = Some(blockHash))
}
/**
@ -54,9 +53,9 @@ case class LegacySpendingInfo(
outPoint: TransactionOutPoint,
output: TransactionOutput,
privKeyPath: LegacyHDPath,
confirmations: Int,
spent: Boolean,
txid: DoubleSha256DigestBE,
blockHash: Option[DoubleSha256DigestBE],
id: Option[Long] = None
) extends SpendingInfoDb {
override val redeemScriptOpt: Option[ScriptPubKey] = None
@ -68,10 +67,12 @@ case class LegacySpendingInfo(
override def copyWithId(id: Long): LegacySpendingInfo =
copy(id = Some(id))
def copyWithSpent(spent: Boolean): LegacySpendingInfo = copy(spent = spent)
override def copyWithSpent(spent: Boolean): LegacySpendingInfo =
copy(spent = spent)
def copyWithConfirmations(confirmations: Int): LegacySpendingInfo =
copy(confirmations = confirmations)
override def copyWithBlockHash(
blockHash: DoubleSha256DigestBE): LegacySpendingInfo =
copy(blockHash = Some(blockHash))
}
// TODO add case for nested segwit
@ -101,18 +102,15 @@ sealed trait SpendingInfoDb extends DbRowAutoInc[SpendingInfoDb] {
val hashType: HashType = HashType.sigHashAll
/** How many confirmations this output has */
// MOVE ME
require(confirmations >= 0,
s"Confirmations cannot be negative! Got: $confirmations")
def confirmations: Int
/** Whether or not this TXO is spent from our wallet */
def spent: Boolean
/** The TXID of the transaction this output was received in */
def txid: DoubleSha256DigestBE
/** The hash of the block in which the transaction was included */
def blockHash: Option[DoubleSha256DigestBE]
/** Converts the UTXO to the canonical `txid:vout` format */
def toHumanReadableString: String =
s"${outPoint.txId.flip.hex}:${outPoint.vout.toInt}"
@ -120,8 +118,8 @@ sealed trait SpendingInfoDb extends DbRowAutoInc[SpendingInfoDb] {
/** Updates the `spent` field */
def copyWithSpent(spent: Boolean): SpendingInfoType
/** Updates the `confirmations` field */
def copyWithConfirmations(confirmations: Int): SpendingInfoType
/** Updates the `blockHash` field */
def copyWithBlockHash(blockHash: DoubleSha256DigestBE): SpendingInfoType
/** Converts a non-sensitive DB representation of a UTXO into
* a signable (and sensitive) real-world UTXO
@ -165,8 +163,6 @@ case class SpendingInfoTable(tag: Tag)
def txid: Rep[DoubleSha256DigestBE] = column("txid")
def confirmations: Rep[Int] = column("confirmations")
def spent: Rep[Boolean] = column("spent")
def scriptPubKey: Rep[ScriptPubKey] = column("script_pub_key")
@ -180,6 +176,8 @@ case class SpendingInfoTable(tag: Tag)
def scriptWitnessOpt: Rep[Option[ScriptWitness]] = column("script_witness")
def blockHash: Rep[Option[DoubleSha256DigestBE]] = column("block_hash")
/** All UTXOs must have a SPK in the wallet that gets spent to */
def fk_scriptPubKey = {
val addressTable = TableQuery[AddressTable]
@ -196,9 +194,9 @@ case class SpendingInfoTable(tag: Tag)
HDPath,
Option[ScriptPubKey], // ReedemScript
Option[ScriptWitness],
Int, // confirmations
Boolean, // spent
DoubleSha256DigestBE // TXID
DoubleSha256DigestBE, // TXID
Option[DoubleSha256DigestBE] // block hash
)
private val fromTuple: UTXOTuple => SpendingInfoDb = {
@ -209,18 +207,18 @@ case class SpendingInfoTable(tag: Tag)
path: SegWitHDPath,
None, // ReedemScript
Some(scriptWitness),
confirmations,
spent,
txid) =>
txid,
blockHash) =>
SegwitV0SpendingInfo(
outPoint = outpoint,
output = TransactionOutput(value, spk),
privKeyPath = path,
scriptWitness = scriptWitness,
id = id,
confirmations = confirmations,
spent = spent,
txid = txid
txid = txid,
blockHash = blockHash
)
case (id,
@ -230,20 +228,29 @@ case class SpendingInfoTable(tag: Tag)
path: LegacyHDPath,
None, // RedeemScript
None, // ScriptWitness
confirmations,
spent,
txid) =>
txid,
blockHash) =>
LegacySpendingInfo(outPoint = outpoint,
output = TransactionOutput(value, spk),
privKeyPath = path,
id = id,
confirmations = confirmations,
spent = spent,
txid = txid)
case (id, outpoint, spk, value, path, spkOpt, swOpt, confs, spent, txid) =>
txid = txid,
blockHash = blockHash)
case (id,
outpoint,
spk,
value,
path,
spkOpt,
swOpt,
spent,
txid,
blockHash) =>
throw new IllegalArgumentException(
"Could not construct UtxoSpendingInfoDb from bad tuple:"
+ s" ($id, $outpoint, $spk, $value, $path, $spkOpt, $swOpt, $confs, $spent, $txid)."
+ s" ($id, $outpoint, $spk, $value, $path, $spkOpt, $swOpt, $spent, $txid, $blockHash)."
+ " Note: Nested Segwit is not implemented")
}
@ -258,9 +265,9 @@ case class SpendingInfoTable(tag: Tag)
utxo.privKeyPath,
utxo.redeemScriptOpt,
utxo.scriptWitnessOpt,
utxo.confirmations,
utxo.spent,
utxo.txid))
utxo.txid,
utxo.blockHash))
def * : ProvenShape[SpendingInfoDb] =
(id.?,
@ -270,7 +277,7 @@ case class SpendingInfoTable(tag: Tag)
privKeyPath,
redeemScriptOpt,
scriptWitnessOpt,
confirmations,
spent,
txid) <> (fromTuple, toTuple)
txid,
blockHash) <> (fromTuple, toTuple)
}