mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 09:52:09 +01:00
6.5 KiB
6.5 KiB
id | title | original_id |
---|---|---|
version-v0.4-chain-query-api | Chain Query API | chain-query-api |
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]]
}
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.getSpvTestConfig().walletConf
// let's use a helper method to get a v19 bitcoind
// and a ChainApi
val bitcoind = BitcoindV19RpcClient(BitcoindInstance.fromConfigFile())
val nodeApi = BitcoinSWalletTest.MockNodeApi
// Create our key manager
val keyManagerE = BIP39KeyManager.initialize(kmParams = walletConf.kmParams,
bip39PasswordOpt = None)
val keyManager = keyManagerE match {
case Right(keyManager) => keyManager
case Left(err) =>
throw new RuntimeException(s"Cannot initialize key manager err=$err")
}
// 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)
}
}
// Finally, we can initialize our wallet with our own node api
val wallet =
Wallet(keyManager = keyManager, nodeApi = nodeApi, chainQueryApi = chainApi, feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one), creationTime = Instant.now)
// Then to trigger one of the events we can run
wallet.chainQueryApi.getFiltersBetweenHeights(100, 150)