mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-26 15:42:49 +01:00
* Remove support for v19 of bitcoind * Remove downloading v19 bitcoind inside of bitcoind-rpc.sbt * Clean up docs * Move unit tests in BitcoindV19RpcClientTest into other test files so we don't remove them, these rpcs are supported in future versions of bitcoind
195 lines
7.1 KiB
Markdown
195 lines
7.1 KiB
Markdown
---
|
|
id: chain-query-api
|
|
title: Chain Query API
|
|
---
|
|
|
|
```scala mdoc:invisible
|
|
import akka.actor.ActorSystem
|
|
import org.bitcoins.core.api.chain.ChainQueryApi
|
|
import org.bitcoins.core.api.chain.ChainQueryApi.FilterResponse
|
|
import org.bitcoins.crypto._
|
|
import org.bitcoins.core.gcs.{FilterType, GolombFilter}
|
|
import org.bitcoins.core.protocol.BlockStamp
|
|
import org.bitcoins.core.protocol.blockchain.Block
|
|
import org.bitcoins.core.protocol.transaction.Transaction
|
|
import org.bitcoins.core.wallet.fee._
|
|
import org.bitcoins.feeprovider._
|
|
import org.bitcoins.node._
|
|
import org.bitcoins.rpc.client.common.BitcoindRpcClient
|
|
import org.bitcoins.rpc.config._
|
|
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
|
org.bitcoins.testkit.chain.MockChainQueryApi
|
|
import org.bitcoins.testkit.node.MockNodeApi
|
|
import org.bitcoins.wallet.Wallet
|
|
import org.bitcoins.wallet.config.WalletAppConfig
|
|
|
|
import scala.concurrent.{ExecutionContextExecutor, Future}
|
|
```
|
|
|
|
### ChainQueryAPI
|
|
|
|
The ChainQueryApi is how the wallet project stays aware of the current best chain.
|
|
This allows the wallet for example to calculate the number of confirmations for a transaction,
|
|
get the current chain tip, or even retrieve block filters for a given set of blocks.
|
|
|
|
Since this is an API it can be hooked up to the `chain` module of bitcoin-s but it can also be linked to
|
|
any other implementation of your choosing. This allows you to use the bitcoin-s wallet in any schema that you
|
|
want.
|
|
|
|
The functions that the ChainQueryApi supports are:
|
|
|
|
```scala mdoc:compile-only
|
|
trait ChainQueryApi {
|
|
|
|
/** Gets the height of the given block */
|
|
def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]]
|
|
|
|
/** Gets the hash of the block that is what we consider "best" */
|
|
def getBestBlockHash(): Future[DoubleSha256DigestBE]
|
|
|
|
/** Gets number of confirmations for the given block hash*/
|
|
def getNumberOfConfirmations(
|
|
blockHashOpt: DoubleSha256DigestBE): Future[Option[Int]]
|
|
|
|
/** Gets the number of compact filters in the database */
|
|
def getFilterCount: Future[Int]
|
|
|
|
/** Returns the block height of the given block stamp */
|
|
def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int]
|
|
|
|
def getFiltersBetweenHeights(
|
|
startHeight: Int,
|
|
endHeight: Int): Future[Vector[FilterResponse]]
|
|
|
|
def getMedianTimePast(): Future[Long]
|
|
}
|
|
```
|
|
|
|
## Chain query with bitcoind
|
|
|
|
As an example, we will show you how to use the `ChainQueryApi` and bitcoind to query chain data.
|
|
|
|
```scala mdoc:compile-only
|
|
implicit val system: ActorSystem = ActorSystem(s"node-api-example")
|
|
implicit val ec: ExecutionContextExecutor = system.dispatcher
|
|
implicit val walletConf: WalletAppConfig =
|
|
BitcoinSTestAppConfig.getNeutrinoTestConfig().walletConf
|
|
|
|
val instance = BitcoindInstanceLocal.fromConfigFile(BitcoindConfig.DEFAULT_CONF_FILE)
|
|
val bitcoind = BitcoindRpcClient(instance)
|
|
val nodeApi = MockNodeApi.mock
|
|
|
|
// This function can be used to create a callback for when our chain api receives a transaction, block, or
|
|
// a block filter, the returned NodeCallbacks will contain the necessary items to initialize the callbacks
|
|
def createCallbacks(
|
|
processTransaction: Transaction => Future[Unit],
|
|
processCompactFilters: (Vector[(DoubleSha256Digest, GolombFilter)]) => Future[Unit],
|
|
processBlock: Block => Future[Unit]): NodeCallbacks = {
|
|
lazy val onTx: OnTxReceived = { tx =>
|
|
processTransaction(tx)
|
|
}
|
|
lazy val onCompactFilters: OnCompactFiltersReceived = {
|
|
blockFilters =>
|
|
processCompactFilters(blockFilters)
|
|
}
|
|
lazy val onBlock: OnBlockReceived = { block =>
|
|
processBlock(block)
|
|
}
|
|
NodeCallbacks(onTxReceived = Vector(onTx),
|
|
onBlockReceived = Vector(onBlock),
|
|
onCompactFiltersReceived = Vector(onCompactFilters))
|
|
}
|
|
|
|
// Here is a super simple example of a callback, this could be replaced with anything, from
|
|
// relaying the block on the network, finding relevant wallet transactions, verifying the block,
|
|
// or writing it to disk
|
|
val exampleProcessTx = (tx: Transaction) =>
|
|
Future.successful(println(s"Received tx: ${tx.txIdBE}"))
|
|
|
|
val exampleProcessBlock = (block: Block) =>
|
|
Future.successful(println(s"Received block: ${block.blockHeader.hashBE}"))
|
|
|
|
val exampleProcessFilters =
|
|
(filters: Vector[(DoubleSha256Digest, GolombFilter)]) =>
|
|
Future.successful(println(s"Received filter: ${filters.head._1.flip.hex} ${filters.head._2.hash.flip.hex}"))
|
|
|
|
val exampleCallbacks =
|
|
createCallbacks(exampleProcessTx, exampleProcessFilters, exampleProcessBlock)
|
|
|
|
// Here is where we are defining our actual chain api, Ideally this could be it's own class
|
|
// but for the examples sake we will keep it small.
|
|
val chainApi = new ChainQueryApi {
|
|
|
|
override def epochSecondToBlockHeight(time: Long): Future[Int] =
|
|
Future.successful(0)
|
|
|
|
/** Gets the height of the given block */
|
|
override def getBlockHeight(
|
|
blockHash: DoubleSha256DigestBE): Future[Option[Int]] = {
|
|
bitcoind.getBlock(blockHash).map(block => Some(block.height))
|
|
}
|
|
|
|
/** Gets the hash of the block that is what we consider "best" */
|
|
override def getBestBlockHash(): Future[DoubleSha256DigestBE] = {
|
|
bitcoind.getBestBlockHash
|
|
}
|
|
|
|
/** Gets number of confirmations for the given block hash */
|
|
override def getNumberOfConfirmations(
|
|
blockHash: DoubleSha256DigestBE): Future[Option[Int]] = {
|
|
for {
|
|
tip <- bitcoind.getBlockCount
|
|
block <- bitcoind.getBlock(blockHash)
|
|
} yield {
|
|
Some(tip - block.height + 1)
|
|
}
|
|
}
|
|
|
|
/** Gets the number of compact filters in the database */
|
|
override def getFilterCount(): Future[Int] = {
|
|
// since bitcoind should have the filter for
|
|
// every block we can just return the block height
|
|
bitcoind.getBlockCount
|
|
}
|
|
|
|
/** Returns the block height of the given block stamp */
|
|
override def getHeightByBlockStamp(blockStamp: BlockStamp): Future[Int] = {
|
|
blockStamp match {
|
|
case blockHeight: BlockStamp.BlockHeight =>
|
|
Future.successful(blockHeight.height)
|
|
case blockHash: BlockStamp.BlockHash =>
|
|
getBlockHeight(blockHash.hash).map(_.get)
|
|
case blockTime: BlockStamp.BlockTime =>
|
|
Future.failed(new RuntimeException(s"Not implemented: $blockTime"))
|
|
}
|
|
}
|
|
|
|
override def getFiltersBetweenHeights(
|
|
startHeight: Int,
|
|
endHeight: Int): Future[Vector[FilterResponse]] = {
|
|
val filterFs = startHeight
|
|
.until(endHeight)
|
|
.map { height =>
|
|
for {
|
|
hash <- bitcoind.getBlockHash(height)
|
|
filter <- bitcoind.getBlockFilter(hash, FilterType.Basic)
|
|
} yield {
|
|
FilterResponse(filter.filter, hash, height)
|
|
}
|
|
}
|
|
.toVector
|
|
|
|
Future.sequence(filterFs)
|
|
}
|
|
|
|
override def getMedianTimePast(): Future[Long] = bitcoind.getMedianTimePast()
|
|
}
|
|
|
|
// Finally, we can initialize our wallet with our own node api
|
|
val wallet =
|
|
Wallet(nodeApi = nodeApi, chainQueryApi = chainApi, feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one))
|
|
|
|
// Then to trigger one of the events we can run
|
|
wallet.chainQueryApi.getFiltersBetweenHeights(100, 150)
|
|
|
|
```
|