mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
7.1 KiB
7.1 KiB
id | title |
---|---|
chain-query-api | Chain Query API |
import org.apache.pekko.actor.ActorSystem
import org.bitcoins.core.api.callback.OnBlockReceived
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:
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.
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[(DoubleSha256DigestBE, 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[(DoubleSha256DigestBE, 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)
// Then to trigger one of the events we can run
wallet.chainQueryApi.getFiltersBetweenHeights(100, 150)