mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2024-11-19 01:40:55 +01:00
Bump versions to 1.9.9 (#5598)
This commit is contained in:
parent
922d42af11
commit
83ffea0214
48
README.md
48
README.md
@ -1,5 +1,5 @@
|
||||
![Bitcoin-S logo](website/static/img/bitcoin-s-dark-logo.png)
|
||||
[![Build Status](https://github.com/bitcoin-s/bitcoin-s/workflows/Release/badge.svg)](https://github.com/bitcoin-s/bitcoin-s/actions) [![Coverage Status](https://coveralls.io/repos/github/bitcoin-s/bitcoin-s/badge.svg?branch=master)](https://coveralls.io/github/bitcoin-s/bitcoin-s?branch=master) [![Maven Central](https://img.shields.io/badge/Maven%20Central-1.9.8-brightgreen.svg)](https://mvnrepository.com/artifact/org.bitcoin-s) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/bitcoin-s-core)
|
||||
[![Build Status](https://github.com/bitcoin-s/bitcoin-s/workflows/Release/badge.svg)](https://github.com/bitcoin-s/bitcoin-s/actions) [![Coverage Status](https://coveralls.io/repos/github/bitcoin-s/bitcoin-s/badge.svg?branch=master)](https://coveralls.io/github/bitcoin-s/bitcoin-s?branch=master) [![Maven Central](https://img.shields.io/badge/Maven%20Central-1.9.9-brightgreen.svg)](https://mvnrepository.com/artifact/org.bitcoin-s) [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/bitcoin-s-core)
|
||||
|
||||
Feature-rich toolkit for making Bitcoin and Lightning applications on the JVM.
|
||||
|
||||
@ -43,52 +43,52 @@ This link is intended for setting up development of bitcoin-s. If you want to ju
|
||||
|
||||
### Adding bitcoin-s to your library
|
||||
|
||||
The latest release of bitcoin-s is `1.9.8`, here is how you can use the dependencies in your projects:
|
||||
The latest release of bitcoin-s is `1.9.9`, here is how you can use the dependencies in your projects:
|
||||
|
||||
```
|
||||
libraryDependencies += "org.bitcoin-s" % "bitcoin-s-secp256k1jni" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" % "bitcoin-s-secp256k1jni" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-core" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-core" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-crypto" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-crypto" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-chain" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-chain" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-oracle" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-oracle" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-oracle-explorer-client" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-oracle-explorer-client" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-app-commons" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-app-commons" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-db-commons" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-db-commons" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-fee-provider" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-fee-provider" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-bitcoind-rpc" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-bitcoind-rpc" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-eclair-rpc" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-eclair-rpc" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-lnd-rpc" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-lnd-rpc" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-key-manager" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-key-manager" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-node" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-node" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-node" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-node" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-wallet" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-wallet" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-wallet" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-wallet" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit-core" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit-core" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-zmq" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-zmq" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-tor" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-tor" % "1.9.9"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-cli" % "1.9.8"
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-cli" % "1.9.9"
|
||||
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
package org.bitcoins.node.constant
|
||||
|
||||
case object NodeConstants {
|
||||
val userAgent = "/bitcoin-s:1.9.8/"
|
||||
val userAgent = "/bitcoin-s:1.9.9/"
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import scala.util.Properties
|
||||
|
||||
object CommonSettings {
|
||||
|
||||
val previousStableVersion: String = "1.9.8"
|
||||
val previousStableVersion: String = "1.9.9"
|
||||
|
||||
private def isCI = {
|
||||
Properties
|
||||
|
78
website/versioned_docs/version-1.9.9/chain/chain.md
Normal file
78
website/versioned_docs/version-1.9.9/chain/chain.md
Normal file
@ -0,0 +1,78 @@
|
||||
---
|
||||
title: Blockchain Verification
|
||||
id: version-1.9.9-chain
|
||||
original_id: chain
|
||||
---
|
||||
|
||||
Bitcoin-S comes bundled with a rudimentary blockchain verification
|
||||
module. This module is currently only released as a library, and not as a binary.
|
||||
This is because it (nor the documentation) is not deemed production
|
||||
ready. Use at your own risk, and without too much money depending on it.
|
||||
|
||||
## Syncing and verifying block headers
|
||||
|
||||
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
|
||||
implicit val ec: ExecutionContext = ExecutionContext.global
|
||||
implicit val system: ActorSystem = ActorSystem("System")
|
||||
// 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.
|
||||
|
||||
val bitcoindInstance = BitcoindInstanceLocal.fromDatadir()
|
||||
val rpcCli = BitcoindRpcClient(bitcoindInstance)
|
||||
|
||||
// Next, we need to create a way to monitor the chain:
|
||||
|
||||
val getBestBlockHash = SyncUtil.getBestBlockHashFunc(rpcCli)
|
||||
|
||||
val getBlockHeader = SyncUtil.getBlockHeaderFunc(rpcCli)
|
||||
|
||||
// set a data directory
|
||||
val datadir = Files.createTempDirectory("bitcoin-s-test")
|
||||
|
||||
// set the current network to regtest
|
||||
import com.typesafe.config.ConfigFactory
|
||||
val config = ConfigFactory.parseString {
|
||||
"""
|
||||
| bitcoin-s {
|
||||
| network = regtest
|
||||
| }
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
implicit val chainConfig: ChainAppConfig = ChainAppConfig(datadir, Vector(config))
|
||||
|
||||
// Initialize the needed database tables if they don't exist:
|
||||
val chainProjectInitF = chainConfig.start()
|
||||
val blockHeaderDAO = BlockHeaderDAO()
|
||||
val compactFilterHeaderDAO = CompactFilterHeaderDAO()
|
||||
val compactFilterDAO = CompactFilterDAO()
|
||||
val stateDAO = ChainStateDescriptorDAO()
|
||||
|
||||
|
||||
//initialize the chain handler from the database
|
||||
val chainHandler = ChainHandler.fromDatabase(blockHeaderDAO, compactFilterHeaderDAO, compactFilterDAO, stateDAO)
|
||||
|
||||
// Now, do the actual syncing:
|
||||
val syncedChainApiF = for {
|
||||
_ <- chainProjectInitF
|
||||
synced <- ChainSync.sync(chainHandler, 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}")
|
||||
}
|
||||
```
|
115
website/versioned_docs/version-1.9.9/chain/filter-sync.md
Normal file
115
website/versioned_docs/version-1.9.9/chain/filter-sync.md
Normal file
@ -0,0 +1,115 @@
|
||||
---
|
||||
title: Syncing Blockfilters
|
||||
id: version-1.9.9-filter-sync
|
||||
original_id: filter-sync
|
||||
---
|
||||
|
||||
The `chain` module has the ability to store [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) block filters locally. Generally these filters are useful
|
||||
for doing wallet rescans. The idea is you can generate a list of script pubkeys you are interested in and see if
|
||||
the block filter matches the scriptPubKey.
|
||||
|
||||
As we demonstrated in [the chain docs](chain.md) with block headers, you can sync block filters from an external data source
|
||||
as well. We are going to use bitcoind as an example of an external data source to sync filters against. It is important
|
||||
that the bitcoind version you are using is >= `v19` as the [`getblockfilter`](https://github.com/bitcoin/bitcoin/blob/master/doc/release-notes/release-notes-0.19.0.1.md#new-rpcs)
|
||||
rpc is implemented there. You need to make sure bitcoind is started with the `-blockfilterindex` flag. This makes it
|
||||
so we can query filters.
|
||||
|
||||
> It is important to remember that you need fully synced block headers before you can sync filter headers and filters. Please see [the chain docs](chain.md) for syncing block headers.
|
||||
|
||||
#### Abstract idea of syncing filters.
|
||||
|
||||
Our internal infrastructure depends on one function to be implemented to be able to sync filters.
|
||||
|
||||
|
||||
```scala
|
||||
val getFilterFunc: BlockHeader => Future[FilterWithHeaderHash] = ???
|
||||
```
|
||||
|
||||
With `getFilterFunc` given a `BlockHeader` we can find it's associated `GolombFilter` -- which is our internal repesentation
|
||||
of a BIP157 block filter.
|
||||
|
||||
The basic idea for `FilterSync.syncFilters()` is to look at our current best block header inside of our `ChainApi.getBestBlockHeader()`
|
||||
and then check what our best block filter's block hash is with `ChainApi.getBestFilterHeader()`. If the blockfilter returned from our internal
|
||||
data store is NOT associated with our best block header, we attempt to sync our filter headers to catch up to our best block header.
|
||||
|
||||
### Syncing block filters against bitcoind
|
||||
|
||||
We are going to implement `getFilterFunc` with bitcoind and then sync a few filter headers.
|
||||
|
||||
```scala
|
||||
implicit val system: ActorSystem = ActorSystem(s"filter-sync-example")
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
implicit val chainAppConfig: ChainAppConfig = BitcoinSTestAppConfig.getNeutrinoTestConfig().chainConf
|
||||
|
||||
val instance = BitcoindInstanceLocal.fromConfigFile(BitcoindConfig.DEFAULT_CONF_FILE)
|
||||
val bitcoind = BitcoindRpcClient(instance)
|
||||
val bitcoindWithChainApiF: Future[BitcoindBaseVersionChainHandlerViaRpc] = {
|
||||
ChainUnitTest.createChainApiWithBitcoindRpc(bitcoind)
|
||||
}
|
||||
val bitcoindF = bitcoindWithChainApiF.map(_.bitcoindRpc)
|
||||
val chainApiF = bitcoindWithChainApiF.map(_.chainHandler)
|
||||
|
||||
val filterType = FilterType.Basic
|
||||
val addressF = bitcoindF.flatMap(_.getNewAddress)
|
||||
|
||||
//this is the function that we are going to use to sync
|
||||
//our internal filters against. We use this function to query
|
||||
//for each block filter associated with a blockheader
|
||||
val getFilterFunc: BlockHeader => Future[FilterWithHeaderHash] = { blockHeader =>
|
||||
val prevFilterResultF =
|
||||
bitcoindF.flatMap(_.getBlockFilter(blockHeader.hashBE, filterType))
|
||||
prevFilterResultF.map { filterResult =>
|
||||
FilterWithHeaderHash(filterResult.filter, filterResult.header)
|
||||
}
|
||||
}
|
||||
|
||||
//ok enough setup, let's generate a block that we need to sync the filter for in bitcoind
|
||||
val block1F = for {
|
||||
bitcoind <- bitcoindF
|
||||
address <- addressF
|
||||
hashes <- bitcoind.generateToAddress(1,address)
|
||||
} yield hashes
|
||||
|
||||
//to be able to sync filters, we need to make sure our block headers are synced first
|
||||
//so let's sync our block headers to our internal chainstate
|
||||
val chainApiSyncedHeadersF = for {
|
||||
bitcoind <- bitcoindF
|
||||
handler <- chainApiF
|
||||
getBestBlockHash = SyncUtil.getBestBlockHashFunc(bitcoind)
|
||||
getBlockHeader = SyncUtil.getBlockHeaderFunc(bitcoind)
|
||||
syncedChainApiHeaders <- ChainSync.sync(handler, getBlockHeader, getBestBlockHash)
|
||||
} yield syncedChainApiHeaders
|
||||
|
||||
//now that we have synced our 1 block header, we can now sync the 1 block filter
|
||||
//associated with that header.
|
||||
val chainApiSyncedFiltersF = for {
|
||||
syncedHeadersChainApi <- chainApiSyncedHeadersF
|
||||
syncedFilters <- FilterSync.syncFilters(syncedHeadersChainApi,getFilterFunc)
|
||||
} yield syncedFilters
|
||||
|
||||
//now we should have synced our one filter, let's make sure we have it
|
||||
val resultF = for {
|
||||
chainApi <- chainApiSyncedFiltersF
|
||||
filterHeaderCount <- chainApi.getFilterHeaderCount()
|
||||
filterCount <- chainApi.getFilterCount()
|
||||
} yield {
|
||||
println(s"filterHeaderCount=$filterHeaderCount filterCount=$filterCount")
|
||||
}
|
||||
|
||||
//cleanup
|
||||
resultF.onComplete { _ =>
|
||||
for {
|
||||
c <- bitcoindWithChainApiF
|
||||
_ <- ChainUnitTest.destroyBitcoindChainApiViaRpc(c)
|
||||
_ <- system.terminate()
|
||||
} yield ()
|
||||
}
|
||||
```
|
||||
|
||||
Yay! Now we have synced block filters from an external data source. If you want to repeatedly sync you can just call
|
||||
|
||||
`FilterSync.syncFilters(syncedFiltersChainApi,getFilterFunc)` every time you would like to sync.
|
||||
|
||||
Again, you need to ensure
|
||||
your headers are synced before you can sync filters, so make sure that you are calling `ChainSync.sync()` before syncing
|
||||
filters.
|
541
website/versioned_docs/version-1.9.9/config/configuration.md
Normal file
541
website/versioned_docs/version-1.9.9/config/configuration.md
Normal file
@ -0,0 +1,541 @@
|
||||
---
|
||||
title: Application Configuration
|
||||
id: version-1.9.9-configuration
|
||||
original_id: 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`](/api/org/bitcoins/db/AppConfig).
|
||||
`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
|
||||
import org.apache.pekko.actor.ActorSystem
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import java.nio.file.Paths
|
||||
import scala.util.Properties
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
implicit val system: ActorSystem = ActorSystem("configuration-example")
|
||||
// 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, Vector.empty)
|
||||
|
||||
// reads a custom data directory and overrides the network to be testnet3
|
||||
val customOverride = ConfigFactory.parseString("bitcoin-s.network = testnet3")
|
||||
val configFromCustomDirAndOverride = WalletAppConfig(customDirectory, Vector(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.
|
||||
|
||||
## Command Line Options
|
||||
|
||||
There are a few command line options available that take precedence over configuration file.
|
||||
|
||||
- `--datadir <directory>`
|
||||
|
||||
`datadir` sets the data directory instead of using the default `$HOME/.bitcoin-s`
|
||||
|
||||
- `--rpcbind <ip>`
|
||||
|
||||
`rpcbind` sets the interface the rpc server binds to instead of using the default `127.0.0.1`
|
||||
|
||||
- `--rpcport <port>`
|
||||
|
||||
`rpcport` sets the port the rpc server binds to instead of using the default `9999`
|
||||
|
||||
- `--force-recalc-chainwork`
|
||||
|
||||
`force-recalc-chainwork` will force a recalculation of the entire chain's chain work, this can be useful if there is
|
||||
an incompatible migration or if it got out of sync.
|
||||
|
||||
- `-Dlogback.configurationFile=/path/to/config.xml`
|
||||
|
||||
You can set a custom logback configuration. If you need help creating a custom logback file you can
|
||||
read [the logback configuration documentation](http://logback.qos.ch/manual/configuration.html).
|
||||
|
||||
## Internal configuration
|
||||
|
||||
Database connections are also configured by using HOCON. This is done
|
||||
in [`reference.conf`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/resources/reference.conf)
|
||||
inside the `db-commons` module. The options exposed here are **not** intended to be used by users of Bitcoin-S, and are
|
||||
internal only.
|
||||
|
||||
## Database Migrations
|
||||
|
||||
All of our modules that require databases now have database migrations. The tool we use for these migrations is
|
||||
called [flyway](https://flywaydb.org/). To find your projects migraitons, you need to look inside of the
|
||||
`[project-name]/src/main/resources/[database-name]/migration/`. For example, the chain projects migrations live under
|
||||
the path `chain/src/main/resources/chaindb/migration/V1__chain_db_baseline.sql`.
|
||||
|
||||
Migrations can be executed by calling
|
||||
the [`DbManagement.migrate()`](https://github.com/bitcoin-s/bitcoin-s/blob/e387d075b0ff2e0a0fec15788fcb48e4ddc4d9d5/db-commons/src/main/scala/org/bitcoins/db/DbManagement.scala#L92)
|
||||
method. Migrations are applied by default on server startup, via
|
||||
the [`AppConfig.start()`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/scala/org/bitcoins/db/AppConfig.scala#L49)
|
||||
method.
|
||||
|
||||
These migrations are setup so that project's databases and migrations are independent of each other. Therefore if you
|
||||
want to use the `bitcoin-s-chain` project, but not the `bitcoin-s-wallet` project, wallet migrations are not applied. It
|
||||
should be noted if you are using a module as a library, you are responsible for configuring the database via
|
||||
[slick's configuration](https://scala-slick.org/doc/3.3.1/database.html#using-typesafe-config) and calling
|
||||
[`AppConfig.start()`](https://github.com/bitcoin-s/bitcoin-s/blob/master/db-commons/src/main/scala/org/bitcoins/db/AppConfig.scala#L49)
|
||||
to ensure the entire module is initialized correctly.
|
||||
|
||||
## Example Configuration File
|
||||
|
||||
```$xslt
|
||||
bitcoin-s {
|
||||
# the network your bitcoin-s node is running on
|
||||
network = "testnet3" # regtest, testnet3, mainnet, signet
|
||||
|
||||
# specify what backend you are using with bitcoin-s
|
||||
# by default we do neutrino, but you can also connect
|
||||
# bitcoind with the configuration settings in bitcoin-s.bitcoind-rpc
|
||||
node.mode = neutrino # neutrino, bitcoind
|
||||
|
||||
# configurations for connecting to bitcoind
|
||||
bitcoind-rpc {
|
||||
# bitcoind rpc username
|
||||
rpcuser = user
|
||||
# bitcoind rpc password
|
||||
# If your password contains the characters '$','{', '}', '[', ']', ':', '=', ',', '+', '#', '`', '^', '?', '!', '@', '*', '&', whitespace
|
||||
# or the string "//", enclose it in double quotes
|
||||
# rpcpassword = "password=" if the original password is password=, rpcpassword = "passwo//rd" if the original password is passwo//rd etc.
|
||||
# If it contains '\' or '"', escape it with '\'
|
||||
# rpcpassword = "pass\\word" if the original password is pass\word, rpcpassword = "pass\"word" if the original password is pass"word
|
||||
rpcpassword = password
|
||||
|
||||
# Binary location of bitcoind
|
||||
binary = ${HOME}/.bitcoin-s/binaries/bitcoind/bitcoin-0.20.1/bin/bitcoind
|
||||
# bitcoind datadir
|
||||
datadir = ${HOME}/.bitcoin
|
||||
# bitcoind network host
|
||||
connect = localhost
|
||||
# bitcoind p2p port
|
||||
port = 8333
|
||||
# bitcoind rpc host
|
||||
rpcconnect = localhost
|
||||
# bitcoind rpc port
|
||||
rpcport = 8332
|
||||
# bitcoind zmq raw tx
|
||||
zmqpubrawtx = "tcp://127.0.0.1:28332"
|
||||
# bitcoind zmq raw block
|
||||
zmqpubrawblock = "tcp://127.0.0.1:28333"
|
||||
# bitcoind zmq hash tx
|
||||
zmqpubhashtx = "tcp://127.0.0.1:28330"
|
||||
# bitcoind zmq raw block
|
||||
zmqpubhashblock = "tcp://127.0.0.1:28331"
|
||||
|
||||
#If you have a bitcoind instance that is running remotely on another machine, you should set it to true
|
||||
isRemote = false
|
||||
}
|
||||
|
||||
# settings if you are using a neutrino node in bitcoin-s
|
||||
node {
|
||||
|
||||
# a list of peer addresses in form "hostname:portnumber"
|
||||
# Port number is optional, the default value is 8333 for mainnet,
|
||||
# 18333 for testnet and 18444 for regtest.
|
||||
peers = [""]
|
||||
|
||||
# try to connect to peers from dns seeds, database, addr messages etc
|
||||
enable-peer-discovery = true
|
||||
|
||||
# number of persistent peer connections to maintain for node use
|
||||
maxConnectedPeers = 2
|
||||
|
||||
# time interval for trying next set of peers in peer discovery
|
||||
try-peers-interval = 12 hour
|
||||
|
||||
# the delay until we start attempting to connect to peers
|
||||
try-peers-start-delay = 1 second
|
||||
|
||||
# wait time for queries like getheaders etc before switching to another
|
||||
query-wait-time = 120s
|
||||
|
||||
hikari-logging = true
|
||||
hikari-logging-interval = 10 minute
|
||||
|
||||
# whether to have p2p peers relay us unconfirmed txs
|
||||
relay = false
|
||||
|
||||
# how often we run health checks for our peers
|
||||
health-check-interval = 1 minutes
|
||||
|
||||
# if the peer does not send us a message within this duration
|
||||
# we disconnect it for inactivity
|
||||
peer-timeout = 20 minute
|
||||
|
||||
# how long we wait until we attempt to re-connect to a peer we have
|
||||
# in our database that we have connected to previously
|
||||
connection-attempt-cool-down-period = 5 minutes
|
||||
}
|
||||
|
||||
proxy {
|
||||
# You can configure SOCKS5 proxy to use Tor for outgoing connections
|
||||
enabled = false
|
||||
socks5 = "127.0.0.1:9050"
|
||||
}
|
||||
|
||||
# tor settings
|
||||
tor {
|
||||
# You can enable Tor for incoming connections
|
||||
enabled = false
|
||||
control = "127.0.0.1:9051"
|
||||
|
||||
# Tor daemon can be provided by the node operator.
|
||||
# If this parameter set to true, bitcoin-s will connect the provided Tor daemon.
|
||||
# Otherwise bitcoin-s will start its own pre-packaged daemon.
|
||||
provided = false
|
||||
|
||||
# This parameter allows to use random port numbers for pre-packaged Tor daemon,
|
||||
# which is useful if another Tor daemon instance already bound SOCKS5 and control ports.
|
||||
# In this case bitcoin-s.tor.control and bitcoin-s.proxy.socks5
|
||||
# addresses will be automatically changed to "localhost:<random port>"
|
||||
use-random-ports = true
|
||||
|
||||
# The password used to arrive at the HashedControlPassword for the control port.
|
||||
# If provided, the HASHEDPASSWORD authentication method will be used instead of
|
||||
# the SAFECOOKIE one.
|
||||
# password = securePassword
|
||||
|
||||
# The path to the private key of the onion service being created
|
||||
# privateKeyPath = /path/to/priv/key
|
||||
|
||||
# Optonal Tor targets. If empty all hidden serices will be created at localhost.
|
||||
targets = []
|
||||
}
|
||||
|
||||
# settings for the chain module
|
||||
chain {
|
||||
neutrino {
|
||||
filter-header-batch-size.default = 2000
|
||||
filter-header-batch-size.regtest = 10
|
||||
# You can set a network specific filter-header-batch-size
|
||||
# by adding a trailing `.networkId` (main, test, regtest)
|
||||
# It is recommended to keep the main and test batch size high
|
||||
# to keep the sync time fast, however, for regtest it should be small
|
||||
# so it does not exceed the chain size.
|
||||
|
||||
filter-batch-size = 1000
|
||||
}
|
||||
|
||||
hikari-logging = true
|
||||
hikari-logging-interval = 10 minute
|
||||
|
||||
websocket {
|
||||
# don't emit block processed events over the websocket
|
||||
# until IBD is complete. This is an optimization for the
|
||||
# the UI so it doesn't have to handle hundreds of thousands of
|
||||
# events while IBD is going on.
|
||||
block-processed-ibd = false
|
||||
}
|
||||
}
|
||||
|
||||
# settings for wallet module
|
||||
wallet {
|
||||
# You can have multiple wallets by setting a different
|
||||
# wallet name for each of them. They will each have
|
||||
# their own unique seed and database or schema,
|
||||
# depending on the database driver.
|
||||
# The wallet name can contain letters, numbers, and underscores '_'.
|
||||
# walletName = MyWallet0
|
||||
|
||||
defaultAccountType = segwit # legacy, segwit, nested-segwit
|
||||
|
||||
bloomFalsePositiveRate = 0.0001 # percentage
|
||||
|
||||
# the number of consecutive addresses that we do not
|
||||
# discover funds in before we mark as rescan as exhausted
|
||||
# this is needed because we can never truely tell how many addresses
|
||||
# the wallet has used when executing a rescan from a seed
|
||||
addressGapLimit = 100
|
||||
|
||||
# the number of addresses that get generated everytime
|
||||
# we need to rescan. If a match occurs within the addressGapLimit
|
||||
# we generate another discoveryBatchSize addresses and then rescan again
|
||||
discoveryBatchSize = 100
|
||||
|
||||
requiredConfirmations = 6
|
||||
|
||||
# Expected average fee rate over the long term
|
||||
# in satoshis per virtual byte
|
||||
longTermFeeRate = 10
|
||||
|
||||
# How big the address queue size is before we throw an exception
|
||||
# because of an overflow
|
||||
addressQueueSize = 10
|
||||
|
||||
# How long we attempt to generate an address for
|
||||
# before we timeout
|
||||
addressQueueTimeout = 5 seconds
|
||||
|
||||
# Allow external payout and change addresses in DLCs
|
||||
# By default all DLC addresses are generated by the wallet itself
|
||||
allowExternalDLCAddresses = false
|
||||
|
||||
# How often the wallet will rebroadcast unconfirmed transactions
|
||||
rebroadcastFrequency = 4 hours
|
||||
|
||||
hikari-logging = true
|
||||
hikari-logging-interval = 10 minute
|
||||
}
|
||||
|
||||
keymanager {
|
||||
# You can optionally set a BIP 39 password
|
||||
# bip39password = "changeMe"
|
||||
|
||||
# Password that your seed is encrypted with
|
||||
# aesPassword = changeMe
|
||||
|
||||
# At least 16 bytes of entropy encoded in hex
|
||||
# This will be used as the seed for any
|
||||
# project that is dependent on the keymanager
|
||||
# entropy = ""
|
||||
}
|
||||
|
||||
# Bitcoin-S provides manny different fee providers
|
||||
# You can configure your server to use any of them
|
||||
# Below is some examples of different options
|
||||
fee-provider {
|
||||
# name = mempoolspace # Uses mempool.space's api
|
||||
# The target is optional for mempool.space
|
||||
# It refers to the expected number of blocks until confirmation
|
||||
# target = 6
|
||||
|
||||
# name = bitcoinerlive # Uses bitcoiner.live's api
|
||||
# The target is optional for Bitcoiner Live
|
||||
# It refers to the expected number of blocks until confirmation
|
||||
# target = 6
|
||||
|
||||
# name = bitgo # Uses BitGo's api
|
||||
# The target is optional for BitGo
|
||||
# It refers to the expected number of blocks until confirmation
|
||||
# target = 6
|
||||
|
||||
# name = constant # A constant fee rate in sats/vbyte
|
||||
# target = 1 # Will always use 1 sat/vbyte
|
||||
}
|
||||
|
||||
dlcnode {
|
||||
# The address we are listening on for incoming connections for DLCs
|
||||
# Binding to 0.0.0.0 makes us listen to all incoming connections
|
||||
# Consider using 127.0.0.1 listen address if Tor is enabled.
|
||||
# listen = "0.0.0.0:2862"
|
||||
|
||||
# The address our peers use to connect to our node.
|
||||
# By default it's the same as the listen address,
|
||||
# or if Tor is enabled, the hidden service's onion address.
|
||||
# You can specify a port number like this "192.168.0.1:12345",
|
||||
# The default port number is the same as in the listen adrress
|
||||
# external-ip = "192.168.0.1"
|
||||
}
|
||||
|
||||
server {
|
||||
# The port we bind our rpc server on
|
||||
rpcport = 9999
|
||||
|
||||
# The ip address we bind our server too
|
||||
rpcbind = "127.0.0.1"
|
||||
|
||||
# The port we bind our websocket server on
|
||||
wsport = 19999
|
||||
|
||||
# The ip address we bind the websocket server too
|
||||
wsbind = "127.0.0.1"
|
||||
|
||||
# The basic auth password. It must me must be non empty.
|
||||
password = topsecret
|
||||
}
|
||||
|
||||
oracle {
|
||||
# The port we bind our rpc server on
|
||||
rpcport = 9998
|
||||
|
||||
# The ip address we bind our server too
|
||||
rpcbind = "127.0.0.1"
|
||||
|
||||
# The basic auth password. It must me must be non empty.
|
||||
password = supersecret
|
||||
|
||||
hikari-logging = true
|
||||
hikari-logging-interval = 10 minute
|
||||
|
||||
db {
|
||||
path = ${bitcoin-s.datadir}/oracle/
|
||||
}
|
||||
}
|
||||
|
||||
dbDefault = {
|
||||
dataSourceClass = slick.jdbc.DatabaseUrlDataSource
|
||||
profile = "slick.jdbc.SQLiteProfile$"
|
||||
|
||||
db {
|
||||
# for information on parameters available here see
|
||||
# https://scala-slick.org/doc/3.3.1/api/index.html#slick.jdbc.JdbcBackend$DatabaseFactoryDef@forConfig(String,Config,Driver,ClassLoader):Database
|
||||
path = ${bitcoin-s.datadir}/${bitcoin-s.network}/
|
||||
driver = org.sqlite.JDBC
|
||||
user = ""
|
||||
password = ""
|
||||
host = localhost
|
||||
port = 5432
|
||||
|
||||
# this needs to be set to 1 for SQLITE as it does not support concurrent database operations
|
||||
# see: https://github.com/bitcoin-s/bitcoin-s/pull/1840
|
||||
numThreads = 1
|
||||
queueSize=5000
|
||||
connectionPool = "HikariCP"
|
||||
registerMbeans = true
|
||||
}
|
||||
hikari-logging = false
|
||||
hikari-logging-interval = 10 minute
|
||||
}
|
||||
|
||||
testkit {
|
||||
pg {
|
||||
#enabled postgres backend database for all test cases
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pekko {
|
||||
loglevel = "OFF"
|
||||
stdout-loglevel = "OFF"
|
||||
http {
|
||||
client {
|
||||
# The time after which an idle connection will be automatically closed.
|
||||
# Set to `infinite` to completely disable idle connection timeouts.
|
||||
|
||||
# some requests potentially take a long time, like generate and prune
|
||||
idle-timeout = 5 minutes
|
||||
}
|
||||
|
||||
server {
|
||||
# The amount of time until a request times out on the server
|
||||
# If you have a large payload this may need to be bumped
|
||||
# https://doc.akka.io/docs/akka-http/current/common/timeouts.html#request-timeout
|
||||
request-timeout = 10s
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
actor {
|
||||
debug {
|
||||
# enable DEBUG logging of all AutoReceiveMessages (Kill, PoisonPill etc.)
|
||||
autoreceive= off
|
||||
# enable function of LoggingReceive, which is to log any received message at
|
||||
# DEBUG level
|
||||
receive = on
|
||||
# enable DEBUG logging of unhandled messages
|
||||
unhandled = off
|
||||
|
||||
# enable DEBUG logging of actor lifecycle changes
|
||||
lifecycle = off
|
||||
|
||||
event-stream=off
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Database configuration
|
||||
|
||||
By default, bitcoin-s uses Sqlite to store its data. It creates three Sqlite databases
|
||||
in `~/.bitcoin-s/${network}`: `chain.sqlite` for `chain` project,
|
||||
`node.sqlite` for `node` project and `wallet.sqlite` the wallet. This is the default configuration, it doesn't require
|
||||
additional changes in the config file.
|
||||
|
||||
`bitcoin-s` also supports PostgreSQL as a database backend. In order to use a PostgreSQL database for all project you
|
||||
need to add following into your config file:
|
||||
|
||||
```$xslt
|
||||
bitcoin-s {
|
||||
common {
|
||||
profile = "slick.jdbc.PostgresProfile$"
|
||||
db {
|
||||
driver = org.postgresql.Driver
|
||||
|
||||
# these 3 options will result into a jdbc url of
|
||||
# "jdbc:postgresql://localhost:5432/database"
|
||||
name = database
|
||||
host = localhost
|
||||
port = 5432
|
||||
|
||||
user = "user"
|
||||
password = "topsecret"
|
||||
numThreads = 5
|
||||
|
||||
# http://scala-slick.org/doc/3.3.3/database.html
|
||||
connectionPool = "HikariCP"
|
||||
registerMbeans = true
|
||||
}
|
||||
}
|
||||
|
||||
chain.profile = ${bitcoin-s.common.profile}
|
||||
chain.db = ${bitcoin-s.common.db}
|
||||
chain.db.poolName = "chain-connection-pool"
|
||||
|
||||
node.profile = ${bitcoin-s.common.profile}
|
||||
node.db = ${bitcoin-s.common.db}
|
||||
node.db.poolName = "node-connection-pool"
|
||||
|
||||
wallet.profile = ${bitcoin-s.common.profile}
|
||||
wallet.db = ${bitcoin-s.common.db}
|
||||
wallet.db.poolName = "wallet-connection-pool"
|
||||
|
||||
oracle.profile = ${bitcoin-s.common.profile}
|
||||
oracle.db = ${bitcoin-s.common.db}
|
||||
oracle.db.poolName = "oracle-connection-pool"
|
||||
}
|
||||
```
|
||||
|
||||
The database driver will create a separate SQL namespace for each sub-project: `chain`, `node` and `wallet`.
|
||||
|
||||
Also you can use mix databases and drivers in one configuration. For example, This configuration file enables Sqlite
|
||||
for `node` project (it's default, so its configuration is omitted), and `walletdb` and `chaindb` PostgreSQL databases
|
||||
for `wallet` and `chain` projects:
|
||||
|
||||
```$xslt
|
||||
bitcoin-s {
|
||||
chain {
|
||||
profile = "slick.jdbc.PostgresProfile$"
|
||||
db {
|
||||
driver = org.postgresql.Driver
|
||||
name = chaindb
|
||||
host = localhost
|
||||
port = 5432
|
||||
user = "user"
|
||||
password = "topsecret"
|
||||
}
|
||||
}
|
||||
wallet {
|
||||
profile = "slick.jdbc.PostgresProfile$"
|
||||
db {
|
||||
driver = org.postgresql.Driver
|
||||
name = walletdb
|
||||
host = localhost
|
||||
port = 5432
|
||||
user = "user"
|
||||
password = "topsecret"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
605
website/versioned_docs/version-1.9.9/core/adding-spks.md
Normal file
605
website/versioned_docs/version-1.9.9/core/adding-spks.md
Normal file
@ -0,0 +1,605 @@
|
||||
---
|
||||
id: version-1.9.9-adding-spks
|
||||
title: Adding New Script Types
|
||||
original_id: adding-spks
|
||||
---
|
||||
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
<!-- END doctoc -->
|
||||
|
||||
- [Adding a New ScriptPubKey Type](#adding-a-new-scriptpubkey-type)
|
||||
- [Step 0: Design Philosophy](#step-0-design-philosophy)
|
||||
- [Step 1: Create a New ScriptPubKey Trait](#step-1-create-a-new-scriptpubkey-trait)
|
||||
- [Step 2: Create Companion Object](#step-2-create-companion-object)
|
||||
- [Step 3: Add to Relevant fromAsm Methods](#step-3-add-to-relevant-fromasm-methods)
|
||||
- [Step 4: Create a ScriptSignature If Necessary](#step-4-create-a-scriptsignature-if-necessary)
|
||||
- [Step 5: Add to ScriptSignature.fromAsm If Applicable](#step-5-add-to-scriptsignaturefromasm-if-applicable)
|
||||
- [Step 6: Create Relevant BitcoinUTXOSpendingInfo](#step-6-create-relevant-bitcoinutxospendinginfo)
|
||||
- [Non-Nested Single-Key Spending Info](#non-nested-single-key-spending-info)
|
||||
- [Non-Nested Multi-Key Spending Info](#non-nested-multi-key-spending-info)
|
||||
- [Nested Spending Info](#nested-spending-info)
|
||||
- [Step 7: Add to Relevant Apply Methods](#step-7-add-to-relevant-apply-methods)
|
||||
- [Step 8: Create a Signer](#step-8-create-a-signer)
|
||||
- [Non-Nested Single-Key Spending Info](#non-nested-single-key-spending-info-1)
|
||||
- [Non-Nested Multi-Key Spending Info](#non-nested-multi-key-spending-info-1)
|
||||
- [Nested Spending Info](#nested-spending-info-1)
|
||||
- [Step 9: Add to BitcoinSigner.sign](#step-9-add-to-bitcoinsignersign)
|
||||
- [Step 10: Add to ScriptGenerators](#step-10-add-to-scriptgenerators)
|
||||
- [ScriptPubKey Generator](#scriptpubkey-generator)
|
||||
- [ScriptSignature Generator](#scriptsignature-generator)
|
||||
- [ScriptPubKey with Paired ScriptSignature Generator](#scriptpubkey-with-paired-scriptsignature-generator)
|
||||
- [Step 11: Add to CreditingTxGen](#step-11-add-to-creditingtxgen)
|
||||
- [Step 12: Fix all Non-Exhaustive Matches](#step-12-fix-all-non-exhaustive-matches)
|
||||
- [Step 13: Run tests and debug](#step-13-run-tests-and-debug)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
# Adding a New ScriptPubKey Type
|
||||
|
||||
In this document, we will describe how to add new script implementations and types in Bitcoin-S. We will use the following script template example which we have called P2PK with Timeout to illustrate the process:
|
||||
|
||||
```
|
||||
OP_IF
|
||||
<Public Key>
|
||||
OP_ELSE
|
||||
<Timeout> OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||
<Timeout Public Key>
|
||||
OP_ENDIF
|
||||
OP_CHECKSIG
|
||||
```
|
||||
|
||||
Here is [the actual pull request](https://github.com/bitcoin-s/bitcoin-s/pull/967) in which a very similar `ScriptPubKey` is implemented in Bitcoin-S.
|
||||
|
||||
Please note that this document only explains how to add new `RawScriptPubKey`s which are the subset of `ScriptPubKey`s which are fully described by their raw scripts. This is to say that this guide will not help in implementing a new segwit version, but should be helpful for most anything else.
|
||||
|
||||
It is also important to note that all new scripts should be implemented as if they are to appear on-chain without any P2SH or P2WSH. Bitcoin-S already supports conversions from raw on-chain scripts to these formats in the constructors for the script hash schemes which does not require extra support for new script types.
|
||||
|
||||
## Step 0: Design Philosophy
|
||||
|
||||
Bitcoin-S strives to have script types defined in such a way that they can be easily composed and reused. Before going through this guide and implementing a really large script template type, try to decompose your script into smaller re-usable pieces.
|
||||
|
||||
Also remember to consider what existing pieces you can use. For example, `LockTimeScriptPubKey`s are implemented in such a way that any other `RawScriptPubKey` can be given a time lock by nesting it within a `LockTimeScriptPubKey` subtype. Likewise, `ConditionalScriptPubKey`s are built to allow any other `RawScriptPubKey` type to populate both the `OP_IF/OP_NOTIF` case and the `OP_ELSE` case.
|
||||
|
||||
## Step 1: Create a New ScriptPubKey Trait
|
||||
|
||||
Go to `ScriptPubKey.scala` and add a new trait:
|
||||
|
||||
```scala
|
||||
sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey
|
||||
```
|
||||
|
||||
You will then want to add all of the relevant accessor methods. For our case of P2PKWithTimeout, this will mean giving access to the public key, timeout, and timeout public key. Lastly, you will want to add a scaladoc. In total, we get the following result:
|
||||
|
||||
```scala
|
||||
/** The type for ScriptPubKeys of the form:
|
||||
* OP_IF
|
||||
* <Public Key>
|
||||
* OP_ELSE
|
||||
* <Timeout> OP_CHECKSEQUENCEVERIFY OP_DROP
|
||||
* <Timeout Public Key>
|
||||
* OP_ENDIF
|
||||
* OP_CHECKSIG
|
||||
*/
|
||||
sealed trait P2PKWithTimeoutScriptPubKey extends RawScriptPubKey {
|
||||
|
||||
lazy val pubKey: ECPublicKey =
|
||||
ECPublicKey.fromBytes(this.asm(2).bytes)
|
||||
|
||||
lazy val lockTime: ScriptNumber = ScriptNumber.fromBytes(this.asm(5).bytes)
|
||||
|
||||
lazy val timeoutPubKey: ECPublicKey =
|
||||
ECPublicKey.fromBytes(this.asm(9).bytes)
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Create Companion Object
|
||||
|
||||
We now need a companion object which will fulfill four functionalities for us:
|
||||
|
||||
1. Contain a concrete `Impl` class for our SPK type
|
||||
- This simply means creating a `private case class` wrapping `asm: Vector[ScriptToken]`
|
||||
2. Create a `fromAsm` constructor
|
||||
- This should be a simple call to `buildScript` which is inherited from `ScriptFactory`
|
||||
3. Create a logical constructor from Bitcoin-S types
|
||||
- This means creating an `apply` method that takes in logical BItcoin-S types and constructs asm
|
||||
- Note that this may require the use of `BitcoinScriptUtil.calculatePushOp`
|
||||
4. Create an ASM filter which will detect if a given `Vector[ScriptToken]` corresponds to our type
|
||||
|
||||
This looks like the following:
|
||||
|
||||
```scala
|
||||
object P2PKWithTimeoutScriptPubKey
|
||||
extends ScriptFactory[P2PKWithTimeoutScriptPubKey] {
|
||||
private case class P2PKWithTimeoutScriptPubKeyImpl(asm: Vector[ScriptToken])
|
||||
extends P2PKWithTimeoutScriptPubKey
|
||||
|
||||
override def fromAsm(asm: Seq[ScriptToken]): P2PKWithTimeoutScriptPubKey = {
|
||||
buildScript(
|
||||
asm = asm.toVector,
|
||||
constructor = P2PKWithTimeoutScriptPubKeyImpl.apply,
|
||||
errorMsg = s"Given asm was not a P2PKWithTimeoutScriptPubKey, got $asm"
|
||||
)
|
||||
}
|
||||
|
||||
def apply(
|
||||
pubKey: ECPublicKey,
|
||||
lockTime: ScriptNumber,
|
||||
timeoutPubKey: ECPublicKey): P2PKWithTimeoutScriptPubKey = {
|
||||
val timeoutAsm = CSVScriptPubKey(lockTime, EmptyScriptPubKey).asm.toVector
|
||||
val pubKeyAsm = BitcoinScriptUtil
|
||||
.calculatePushOp(pubKey.bytes)
|
||||
.toVector ++ Vector(ScriptConstant(pubKey.bytes))
|
||||
val timeoutPubKeyAsm = BitcoinScriptUtil
|
||||
.calculatePushOp(timeoutPubKey.bytes)
|
||||
.toVector ++ Vector(ScriptConstant(timeoutPubKey.bytes))
|
||||
|
||||
P2PKWithTimeoutScriptPubKeyImpl(
|
||||
Vector(Vector(OP_IF),
|
||||
pubKeyAsm,
|
||||
Vector(OP_ELSE),
|
||||
timeoutAsm,
|
||||
timeoutPubKeyAsm,
|
||||
Vector(OP_ENDIF, OP_CHECKSIG)).flatten
|
||||
)
|
||||
}
|
||||
|
||||
override def isValidAsm(asm: Seq[ScriptToken]): Boolean = {
|
||||
if (asm.length == 12) {
|
||||
val pubKey = ECPublicKey.fromBytes(asm(2).bytes)
|
||||
val lockTimeTry = Try(ScriptNumber.fromBytes(asm(5).bytes))
|
||||
val timeoutPubKey = ECPublicKey.fromBytes(asm(9).bytes)
|
||||
|
||||
lockTimeTry match {
|
||||
case Success(lockTime) =>
|
||||
asm == P2PKWithTimeoutScriptPubKey(pubKey, lockTime, timeoutPubKey).asm
|
||||
case Failure(_) => false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Step 3: Add to Relevant fromAsm Methods
|
||||
|
||||
We now need to ensure that `ScriptPubKey.fromAsm(p2pkWithTimeoutSPK.asm)` returns our type. Since `P2PKWithTimeoutScriptPubKey extends RawScriptPubKey`, this means we must add to `RawScriptPubKey.fromAsm`. Note that order in this function's `match` can matter. Since our type is more specific than any other currently existing type, we put our new `case` at the top:
|
||||
|
||||
```scala
|
||||
asm match {
|
||||
case Nil => EmptyScriptPubKey
|
||||
case _ if P2PKWithTimeoutScriptPubKey.isValidAsm(asm) =>
|
||||
P2PKWithTimeoutScriptPubKey.fromAsm(asm)
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## Step 4: Create a ScriptSignature If Necessary
|
||||
|
||||
Often times a new `ScriptSignature` type will be necessary when introducing a new `ScriptPubKey` type. When this is the case, the procedure for adding a new `ScriptSignature` is more or less identical to steps 1 and 2 above. Here is what this looks like for `P2PKScriptPubKey` (note, this is not `P2PKWithTimeoutScriptPubKey`):
|
||||
|
||||
```scala
|
||||
/**
|
||||
* Represents a pay to public key script signature
|
||||
* https://bitcoin.org/en/developer-guide#pubkey
|
||||
* Signature script: <sig>
|
||||
*/
|
||||
sealed trait P2PKScriptSignature extends ScriptSignature {
|
||||
|
||||
/** PubKey scriptSignatures only have one signature */
|
||||
def signature: ECDigitalSignature = signatures.head
|
||||
|
||||
/** The digital signatures inside of the scriptSig */
|
||||
def signatures: Seq[ECDigitalSignature] = {
|
||||
Seq(ECDigitalSignature(BitcoinScriptUtil.filterPushOps(this.asm).head.hex))
|
||||
}
|
||||
|
||||
override def toString = s"P2PKScriptSignature($signature)"
|
||||
}
|
||||
|
||||
object P2PKScriptSignature extends ScriptFactory[P2PKScriptSignature] {
|
||||
private case class P2PKScriptSignatureImpl(
|
||||
override val asm: Vector[ScriptToken])
|
||||
extends P2PKScriptSignature
|
||||
|
||||
def fromAsm(asm: Seq[ScriptToken]): P2PKScriptSignature = {
|
||||
buildScript(asm.toVector,
|
||||
P2PKScriptSignatureImpl(_),
|
||||
"The given asm tokens were not a p2pk script sig: " + asm)
|
||||
}
|
||||
|
||||
def apply(signature: ECDigitalSignature): P2PKScriptSignature = {
|
||||
val pushOps = BitcoinScriptUtil.calculatePushOp(signature.bytes)
|
||||
val signatureConstant = ScriptConstant(signature.bytes)
|
||||
val asm = pushOps ++ Seq(signatureConstant)
|
||||
P2PKScriptSignature.fromAsm(asm)
|
||||
}
|
||||
|
||||
/** P2PK scriptSigs always have the pattern [pushop, digitalSignature] */
|
||||
override def isValidAsm(asm: Seq[ScriptToken]): Boolean = asm match {
|
||||
case Seq(_: BytesToPushOntoStack, _: ScriptConstant) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
However, it is sometimes not necessary to create a new `ScriptSignature` type for every new `ScriptPubKey`. This is because we want to maintain unique representations for every `ScriptSignature`, and it turns out that in our case of `P2PKWithTimeoutScriptPubKey`, script signatures are of the form
|
||||
|
||||
```
|
||||
<boolean> <signautre>
|
||||
```
|
||||
|
||||
which is already represented by `ConditionalScriptSignature`. When this happens, you only need to create an `object` for your new type, and then follow step 2 above, skipping the first part (adding an Impl `case class`):
|
||||
|
||||
```scala
|
||||
object P2PKWithTimeoutScriptSignature
|
||||
extends ScriptFactory[ConditionalScriptSignature] {
|
||||
override def fromAsm(asm: Seq[ScriptToken]): ConditionalScriptSignature = {
|
||||
buildScript(
|
||||
asm.toVector,
|
||||
ConditionalScriptSignature.fromAsm,
|
||||
s"The given asm tokens were not a P2PKWithTimeoutScriptSignature, got $asm"
|
||||
)
|
||||
}
|
||||
|
||||
def apply(
|
||||
beforeTimeout: Boolean,
|
||||
signature: ECDigitalSignature): ConditionalScriptSignature = {
|
||||
ConditionalScriptSignature(P2PKScriptSignature(signature), beforeTimeout)
|
||||
}
|
||||
|
||||
override def isValidAsm(asm: Seq[ScriptToken]): Boolean = {
|
||||
P2PKScriptSignature.isValidAsm(asm.dropRight(1)) && ConditionalScriptSignature
|
||||
.isValidAsm(asm)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Remember that in all of them above, `ScriptSignature`s are written as if they are to appear on-chain in transaction inputs rather than transaction witnesses, since Bitcoin-S supports turning any raw `ScriptSignature` into a `P2WSHWitness` without requiring explicit support for new script types.
|
||||
|
||||
## Step 5: Add to ScriptSignature.fromAsm If Applicable
|
||||
|
||||
If you added a new `ScriptSignature` type in the previous step, you must add a `case` to the `match` statement in `ScriptSignature.fromAsm` at the bottom of `ScriptSignature.scala`. For `P2PKScriptSignature` (note that this does not apply to `P2PKWithTimeoutScriptSignature` since there is no new unique type for this `ScriptSignature`), this looks like:
|
||||
|
||||
```scala
|
||||
tokens match {
|
||||
//...
|
||||
case _ if P2PKScriptSignature.isValidAsm(tokens) =>
|
||||
P2PKScriptSignature.fromAsm(tokens)
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## Step 6: Create Relevant InputInfo
|
||||
|
||||
`InputInfo` is the Bitcoin-S data structure for the information required to spend from a specific condition of a given `ScriptPubKey` other than private keys. Hence, when defining new script types, it is important to define how they are spent as well so that they can be useful.
|
||||
|
||||
There are two distinct kinds of scripts when it comes to signing in Bitcoin-S: scripts that have nesting (such as `ConditionalScriptPubKey`, `P2SHScriptPubKey`) and scripts without nesting (such as `MultiSignatureScriptPubKey`, `P2PKWithTimeoutScriptPubKey`, `P2PKHScriptPubKey`). We will cover each of these cases in turn, starting with the latter case as it applies to our example of `P2PKWithTimeout`. In both cases, please make sure to validate any parameter data using `require` statements when necessary, but make things correct by construction instead whenever possible. For example, if there is a redeem script and a `ScriptPubKey` which must wrap this redeem script, take as a parameter only the redeem script and construct the `ScriptPubKey` internally so that it is sure to be consistent.
|
||||
|
||||
### Non-Nesting Input Info
|
||||
|
||||
We create a new `case class` in `InputInfo.scala` which extends `RawInputInfo` and which contains in its parameters, all of the info required for spending a specific condition other than private keys and `HashType`. Here is what this looks like for `P2PKWithTimeout`:
|
||||
|
||||
```scala
|
||||
case class P2PKWithTimeoutInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: P2PKWithTimeoutScriptPubKey,
|
||||
isBeforeTimeout: Boolean)
|
||||
extends RawInputInfo {
|
||||
override def conditionalPath: ConditionalPath = {
|
||||
if (isBeforeTimeout) {
|
||||
ConditionalPath.nonNestedTrue
|
||||
} else {
|
||||
ConditionalPath.nonNestedFalse
|
||||
}
|
||||
}
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] =
|
||||
Vector(scriptPubKey.pubKey, scriptPubKey.timeoutPubKey)
|
||||
}
|
||||
```
|
||||
|
||||
### Nested Spending Info
|
||||
|
||||
The one new thing in the nested case is that we must create a `val nestedSpendingInfo: RawInputInfo` and make sure to pass on `hashPreImages: Vector[NetworkElement]` to the nested `InputInfo`, and pull public keys from the `nestedInputInfo`. For the case of spending `LockTimeScriptPubKey`s, this looks like the following:
|
||||
|
||||
```scala
|
||||
case class LockTimeInputInfo(
|
||||
outPoint: TransactionOutPoint,
|
||||
amount: CurrencyUnit,
|
||||
scriptPubKey: LockTimeScriptPubKey,
|
||||
conditionalPath: ConditionalPath,
|
||||
hashPreImages: Vector[NetworkElement] = Vector.empty
|
||||
) extends RawInputInfo {
|
||||
|
||||
val nestedInputInfo: RawInputInfo = RawInputInfo(
|
||||
outPoint,
|
||||
amount,
|
||||
scriptPubKey.nestedScriptPubKey,
|
||||
conditionalPath,
|
||||
hashPreImages)
|
||||
|
||||
override def pubKeys: Vector[ECPublicKey] = nestedInputInfo.pubKeys
|
||||
}
|
||||
```
|
||||
|
||||
## Step 7: Add to Relevant Apply Methods
|
||||
|
||||
Now that we have created our new `RawInputInfo`, we need to add them to the general-purpose input info constructors. This means adding a `case` to `RawInputInfo.apply` for your new `ScriptPubKey` type which constructs your relevant `RawInputInfo` from generic types (given as parameters in the `apply` methods). For `P2PKWithTimeout`, this looks like the following:
|
||||
|
||||
```scala
|
||||
scriptPubKey match {
|
||||
//...
|
||||
case p2pkWithTimeout: P2PKWithTimeoutScriptPubKey =>
|
||||
conditionalPath.headOption match {
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
"ConditionalPath must be specified for P2PKWithTimeout")
|
||||
case Some(beforeTimeout) =>
|
||||
P2PKWithTimeoutInputInfo(outPoint,
|
||||
amount,
|
||||
p2pkWithTimeout,
|
||||
beforeTimeout)
|
||||
}
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## Step 8: Create a Signer
|
||||
|
||||
We must now add signing functionality for our new script type within `Signer.scala`. This time, we have three different cases depending on your new script type.
|
||||
|
||||
### Non-Nested Single-Key Spending Info
|
||||
|
||||
For the non-nested case where only a single key is required, all we must do is create a new class which extends `RawSingleKeyBitcoinSigner` and implements `keyAndSigToScriptSig`. For `P2PKWithTimeout` this looks like the following:
|
||||
|
||||
```scala
|
||||
sealed abstract class P2PKWithTimeoutSigner
|
||||
extends RawSingleKeyBitcoinSigner[P2PKWithTimeoutInputInfo] {
|
||||
|
||||
override def keyAndSigToScriptSig(
|
||||
key: ECPublicKey,
|
||||
sig: ECDigitalSignature,
|
||||
spendingInfo: UTXOInfo[P2PKWithTimeoutInputInfo]): ScriptSignature = {
|
||||
P2PKWithTimeoutScriptSignature(spendingInfo.inputInfo.isBeforeTimeout, sig)
|
||||
}
|
||||
}
|
||||
|
||||
object P2PKWithTimeoutSigner extends P2PKWithTimeoutSigner
|
||||
```
|
||||
|
||||
### Non-Nested Multi-Key Spending Info
|
||||
|
||||
In the non-nested case where multiple keys are required, we must create a new `Signer`, which requires implementing the `sign` function. For `MultiSignature` this looks like the following:
|
||||
|
||||
```scala
|
||||
sealed abstract class MultiSigSigner extends Signer[MultiSignatureInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[MultiSignatureInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) =
|
||||
relevantInfo(spendingInfo, unsignedTx)
|
||||
|
||||
val keysAndSigsF = spendingInfo.toSingles.map { spendingInfoSingle =>
|
||||
signSingle(spendingInfoSingle, unsignedTx, isDummySignature)
|
||||
}
|
||||
|
||||
val signaturesF = Future.sequence(keysAndSigsF).map(_.map(_.signature))
|
||||
|
||||
val scriptSigF = signaturesF.map { sigs =>
|
||||
MultiSignatureScriptSignature(sigs)
|
||||
}
|
||||
|
||||
updateScriptSigInSigComponent(unsignedTx,
|
||||
inputIndex.toInt,
|
||||
output,
|
||||
scriptSigF)
|
||||
}
|
||||
}
|
||||
|
||||
object MultiSigSigner extends MultiSigSigner
|
||||
```
|
||||
|
||||
### Nested Spending Info
|
||||
|
||||
When signing for a nested script structure, we must create a new `Signer`. You will need to make a delegating call with the `nestedSpendingInfo` to `BitcoinSigner.sign`, but you may also need to do whatever else is needed with the nested result to construct a correct `ScriptSignature`. For `ConditionalScriptSignature`, this all looks like:
|
||||
|
||||
```scala
|
||||
/** Delegates to get a ScriptSignature for the case being
|
||||
* spent and then adds an OP_TRUE or OP_FALSE
|
||||
*/
|
||||
sealed abstract class ConditionalSigner extends Signer[ConditionalInputInfo] {
|
||||
|
||||
override def sign(
|
||||
spendingInfo: UTXOSatisfyingInfo[InputInfo],
|
||||
unsignedTx: Transaction,
|
||||
isDummySignature: Boolean,
|
||||
spendingInfoToSatisfy: UTXOSatisfyingInfo[ConditionalInputInfo])(
|
||||
implicit ec: ExecutionContext): Future[TxSigComponent] = {
|
||||
val (_, output, inputIndex, _) = relevantInfo(spendingInfo, unsignedTx)
|
||||
|
||||
val nestedSpendingInfo = spendingInfoToSatisfy.copy(
|
||||
inputInfo = spendingInfoToSatisfy.inputInfo.nestedInputInfo)
|
||||
|
||||
val missingOpSigComponentF = BitcoinSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
nestedSpendingInfo)
|
||||
|
||||
val scriptSigF = missingOpSigComponentF.map { sigComponent =>
|
||||
ConditionalScriptSignature(sigComponent.scriptSignature,
|
||||
spendingInfoToSatisfy.inputInfo.condition)
|
||||
}
|
||||
|
||||
updateScriptSigInSigComponent(unsignedTx,
|
||||
inputIndex.toInt,
|
||||
output,
|
||||
scriptSigF)
|
||||
}
|
||||
}
|
||||
object ConditionalSigner extends ConditionalSigner
|
||||
```
|
||||
|
||||
## Step 9: Add to BitcoinSigner.sign
|
||||
|
||||
We must now add the new signing functionality from the previous step to the general-purpose signing functions by adding a new `case` for your new `InputInfo` type in the `match` within `BitcoinSigner.sign`. In the case of `P2PKWithTimeout`, this looks like:
|
||||
|
||||
```scala
|
||||
spendingInfoToSatisfy match {
|
||||
//...
|
||||
case p2pKWithTimeout: P2PKWithTimeoutInputInfo =>
|
||||
P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
unsignedTx,
|
||||
isDummySignature,
|
||||
spendingFrom(p2pKWithTimeout))
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
We have now fully implemented the new script type! But have we done it correctly? We must now add the new script type to the Bitcoin-S test framework so that our scripts get added to existing Bitcoin-S property-based tests.
|
||||
|
||||
## Step 10: Add to ScriptGenerators
|
||||
|
||||
The first step to adding our new script type to Bitcoin-S property-based tests is creating generators for our new `ScriptPubKey` and `ScriptSignature` types in `ScriptGenerators.scala`.
|
||||
|
||||
It is important to note that in the current Bitcoin-S generator framework for `ScriptPubKey`s, all conditionals always spend only their `OP_TRUE` cases.
|
||||
|
||||
### ScriptPubKey Generator
|
||||
|
||||
Let's start by creating a generator for our `ScriptPubKey`, this generator should also return the private keys that were used to create the `ScriptPubKey`. To construct this `Gen`, you will likely need to use other generators for the internal structures in your script such as keys and lock times. For `P2PKWithTimeout` this looks like:
|
||||
|
||||
```scala
|
||||
def p2pkWithTimeoutScriptPubKey: Gen[
|
||||
(P2PKWithTimeoutScriptPubKey, Seq[ECPrivateKey])] =
|
||||
for {
|
||||
privKey <- CryptoGenerators.privateKey
|
||||
timeoutPrivKey <- CryptoGenerators.privateKey
|
||||
lockTime <- NumberGenerator.timeLockScriptNumbers
|
||||
} yield {
|
||||
(P2PKWithTimeoutScriptPubKey(privKey.publicKey,
|
||||
lockTime,
|
||||
timeoutPrivKey.publicKey),
|
||||
Vector(privKey, timeoutPrivKey))
|
||||
}
|
||||
```
|
||||
|
||||
Note that the private key used in the `OP_TRUE` case is the `head` of the `Seq[ECPrivateKey]` returned. This makes it possible for tests that only spend the `OP_TRUE` case to find the correct key, as it is expected to be the first one.
|
||||
|
||||
We must now add this `Gen` to all of the following `def`s in `ScriptGenerators.scala`: `randomNonP2SHScriptPubKey, scriptPubKey, nonWitnessScriptPubKey, nonConditionalRawScriptPubKey, rawScriptPubKey`, and if your `ScriptPubKey` has no lock times, you must also add the above `Gen` to `nonConditionalNonLocktimeRawScriptPubKey, nonLocktimeRawScriptPubKey` as well.
|
||||
|
||||
### ScriptSignature Generator
|
||||
|
||||
We must also create a generator for our `ScriptSignature` type, even if we did not introduce a new `ScriptSignature` type (in our example of `P2PKWithTimeout` we use a specific form of `ConditionalScriptSignature`). Once again you will likely need to use other existing generators. For `P2PKWithTimeoutScriptSignature`, this looks like:
|
||||
|
||||
```scala
|
||||
def p2pkWithTimeoutScriptSignature: Gen[ConditionalScriptSignature] =
|
||||
for {
|
||||
privKey <- CryptoGenerators.privateKey
|
||||
hash <- CryptoGenerators.doubleSha256Digest
|
||||
hashType <- CryptoGenerators.hashType
|
||||
signature = ECDigitalSignature.fromBytes(
|
||||
privKey.sign(hash).bytes ++ ByteVector.fromByte(hashType.byte))
|
||||
beforeTimeout <- NumberGenerator.bool
|
||||
} yield P2PKWithTimeoutScriptSignature(beforeTimeout, signature)
|
||||
```
|
||||
|
||||
We now add this `Gen` to `scriptSignature: Gen[ScriptSignature]` as well as adding a case for our new `ScriptPubKey` type in `pickCorrespondingScriptSignature` which should return our new `ScriptSignature` generator. If our `ScriptPubKey` does not have any lock times, you should also add this script signature `Gen` to `nonLockTimeConditionalScriptSignature` and `randomNonLockTimeScriptSig`.
|
||||
|
||||
### ScriptPubKey with Paired ScriptSignature Generator
|
||||
|
||||
Lastly, we need to construct a generator that returns both a `ScriptPubKey` and a `ScriptSignature` signing that that `ScriptPubKey`. All keys used in signing should also be returned. This all should be done by using the above `ScriptPubKey` generator, then constructing an `ScriptSignature` for your type where all actual signatures are `EmptyDigitalSignature`s. A `UTXOSatisfyingInfo` should then be constructed for the generated `ScriptPubKey` (using the private keys generated in the same line). Finally, a `TxSignatureComponent` should be created by using the new `Signer` for our script type. From this `TxSignatureComponent`, a `ScriptSignature` is readily available. For `P2PKWithTimeout`, this generator looks like:
|
||||
|
||||
```scala
|
||||
def signedP2PKWithTimeoutScriptSignature: Gen[
|
||||
(ConditionalScriptSignature, P2PKWithTimeoutScriptPubKey, ECPrivateKey)] =
|
||||
for {
|
||||
(spk, privKeys) <- p2pkWithTimeoutScriptPubKey
|
||||
hashType <- CryptoGenerators.hashType
|
||||
} yield {
|
||||
val emptyScriptSig = P2PKWithTimeoutScriptSignature(beforeTimeout = true,
|
||||
EmptyDigitalSignature)
|
||||
val (creditingTx, outputIndex) =
|
||||
TransactionGenerators.buildCreditingTransaction(spk)
|
||||
val (spendingTx, inputIndex) = TransactionGenerators
|
||||
.buildSpendingTransaction(creditingTx, emptyScriptSig, outputIndex)
|
||||
val spendingInfo = UTXOSatisfyingInfo(
|
||||
P2PKWithTimeoutInputInfo(
|
||||
TransactionOutPoint(creditingTx.txIdBE, inputIndex),
|
||||
creditingTx.outputs(outputIndex.toInt).value,
|
||||
spk,
|
||||
isBeforeTimeout = true),
|
||||
privKeys.toVector,
|
||||
hashType
|
||||
)
|
||||
val txSigComponentF = P2PKWithTimeoutSigner.sign(spendingInfo,
|
||||
spendingTx,
|
||||
isDummySignature = false)
|
||||
val txSigComponent = Await.result(txSigComponentF, timeout)
|
||||
val signedScriptSig =
|
||||
txSigComponent.scriptSignature.asInstanceOf[ConditionalScriptSignature]
|
||||
|
||||
(signedScriptSig, spk, privKeys.head)
|
||||
}
|
||||
```
|
||||
|
||||
I strongly advise you also look at at least one other `Gen` of this kind before writing your own.
|
||||
|
||||
## Step 11: Add to CreditingTxGen
|
||||
|
||||
Now that we have generators constructed for `ScriptPubKey`s, `ScriptSignature`s and their pairings completed, we will create a generator for our type's `SpendingInfoFull`. This should usually be as simple as mapping on the `ScriptPubKey` generator in `ScriptGenerators` and calling `build` (within `CreditinTxGen.scala`). We then also create another generator which returns lists of `SpendingInfo`s generated by the previous `Gen`. For `P2PKWithTimeout`, this looks like:
|
||||
|
||||
```scala
|
||||
def p2pkWithTimeoutOutput: Gen[UTXOSatisfyingInfo[InputInfo]] = {
|
||||
ScriptGenerators.p2pkWithTimeoutScriptPubKey.flatMap { p2pkWithTimeout =>
|
||||
build(p2pkWithTimeout._1, Seq(p2pkWithTimeout._2.head), None, None)
|
||||
}
|
||||
}
|
||||
|
||||
def p2pkWithTimeoutOutputs: Gen[Seq[UTXOSatisfyingInfo[InputInfo]]] = {
|
||||
Gen.choose(min, max).flatMap(n => Gen.listOfN(n, p2pkWithTimeoutOutput))
|
||||
}
|
||||
```
|
||||
|
||||
We must then add our output `Gen` to one of `cltvOutputGens` or `nonCLTVOutputGens` depending on whether the `ScriptPubKey` type has CLTVs (absolute lock times) or not. We must also add our output `Gen` to `nonP2SHOutput`, and also to `nonSHOutput` and `nonP2WSHOutput` in the case that your `ScriptPubKey` type has no CLTVs.
|
||||
|
||||
## Step 12: Fix all Non-Exhaustive Matches
|
||||
|
||||
All we have left is to clean up our code and make sure that nothing has been missed. Within an `sbt` terminal, you should run the following sequence of commands:
|
||||
|
||||
```bashrc
|
||||
clean
|
||||
project coreTest
|
||||
test:compile
|
||||
```
|
||||
|
||||
This should have quite a lengthy output but we are only interested in any compiler errors there may be, as well as non-exhaustive match compiler warnings. You should first fix any compiler errors you encounter, and then you can get the warnings again by running `clean` and then running `test:compile` again.
|
||||
|
||||
The warnings we're interested in should look something like this:
|
||||
|
||||
```
|
||||
[warn] /home/nkohen/Desktop/SuredBits/bitcoin-s-core/testkit/src/main/scala/org/bitcoins/testkit/core/gen/ScriptGenerators.scala:524:59: match may not be exhaustive.
|
||||
[warn] It would fail on the following input: P2PKWithTimeoutScriptPubKeyImpl(_)
|
||||
[warn] scriptPubKey: ScriptPubKey): Gen[ScriptSignature] = scriptPubKey match {
|
||||
[warn] ^
|
||||
[warn] one warning found
|
||||
```
|
||||
|
||||
You may get these warnings for your new `ScriptSignature` type as well. These are places where the compiler expects there to be defined functionality in a pattern match where one of our new types is a possibility, but for which no functionality is defined. You must go to each of these warnings and add a `case` for the relevant new type, or add this new type to an existing case when applicable.
|
||||
|
||||
## Step 13: Run tests and debug
|
||||
|
||||
Lastly, once everything is compiling nicely, all that is left is to run tests and debug. While within an `sbt` terminal session, run the following two commands to run the relevant tests:
|
||||
|
||||
```
|
||||
project coreTest
|
||||
test
|
||||
```
|
||||
|
||||
If all tests pass we are all done! If you encounter any test failures, you can re-run individual tests using the `testOnly` command which must be given the full name of the test you wish to run (these names should be at the bottom of the testing output and look something like `org.bitcoins.core.script.interpreter.ScriptInterpreterTest`).
|
59
website/versioned_docs/version-1.9.9/core/addresses.md
Normal file
59
website/versioned_docs/version-1.9.9/core/addresses.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
id: version-1.9.9-addresses
|
||||
title: Generating Addresses
|
||||
original_id: addresses
|
||||
---
|
||||
|
||||
Almost all Bitcoin applications need to generate addresses
|
||||
for their users somehow. There's a lot going on in getting
|
||||
a correct bitcoin address, but our APIs make it possible to
|
||||
to get started with all types of addresses in a matter of
|
||||
minutes.
|
||||
|
||||
## Generating SegWit (bech32) addresses
|
||||
|
||||
Generating native SegWit addresses in the bech32 format
|
||||
is something that all Bitcoin applications should enable,
|
||||
as it makes the transaction fees less expensive, and also
|
||||
makes the addresses more readable by humans. However, it
|
||||
has seen slower than necessary adoption. With Bitcoin-S
|
||||
you can generate bech32 addresses in four(!) lines of code
|
||||
(not counting comments and imports), so now there's no
|
||||
reason to keep using legacy transaction formats.
|
||||
|
||||
|
||||
```scala
|
||||
// this generates a random private key
|
||||
val privkey = ECPrivateKey()
|
||||
// privkey: ECPrivateKey = Masked(ECPrivateKey)
|
||||
val pubkey = privkey.publicKey
|
||||
// pubkey: org.bitcoins.crypto.ECPublicKey = ECPublicKey(02e2fa471bab2b8aeffc6090485db8c47be5bc86eb6d8d720ed6338d9b3c8387dd)
|
||||
|
||||
val segwitAddress = {
|
||||
// see https://bitcoin.org/en/glossary/pubkey-script
|
||||
// for reading resources on the details of scriptPubKeys
|
||||
// pay-to-witness-pubkey-hash scriptPubKey V0
|
||||
val scriptPubKey = P2WPKHWitnessSPKV0(pubkey)
|
||||
Bech32Address(scriptPubKey, TestNet3)
|
||||
}
|
||||
// segwitAddress: Bech32Address = tb1qlq2ev2hn5egsgzq2eeu7y4w3dvngahc0mzul60
|
||||
|
||||
println(segwitAddress.toString)
|
||||
// tb1qlq2ev2hn5egsgzq2eeu7y4w3dvngahc0mzul60
|
||||
```
|
||||
|
||||
## Generating legacy (base58) addresses
|
||||
|
||||
If you need to generate legacy addresses for backwards
|
||||
compatability reasons, that's also a walk in the park.
|
||||
Take a look:
|
||||
|
||||
```scala
|
||||
// we're reusing the same private/public key pair
|
||||
// from before. don't do this in an actual application!
|
||||
val legacyAddress = P2PKHAddress(pubkey, TestNet3)
|
||||
// legacyAddress: P2PKHAddress = n48hjVR5fseSoNPbE95Zw36PSTbTvfWWf9
|
||||
|
||||
println(legacyAddress.toString)
|
||||
// n48hjVR5fseSoNPbE95Zw36PSTbTvfWWf9
|
||||
```
|
226
website/versioned_docs/version-1.9.9/core/dlc.md
Normal file
226
website/versioned_docs/version-1.9.9/core/dlc.md
Normal file
File diff suppressed because one or more lines are too long
149
website/versioned_docs/version-1.9.9/core/hd-keys.md
Normal file
149
website/versioned_docs/version-1.9.9/core/hd-keys.md
Normal file
@ -0,0 +1,149 @@
|
||||
---
|
||||
id: version-1.9.9-hd-keys
|
||||
title: HD Key Generation
|
||||
original_id: hd-keys
|
||||
---
|
||||
|
||||
In modern Bitcoin wallets, users only need to write down
|
||||
a sequence of words, and that sequence is a complete backup
|
||||
of their wallet. This is thanks to what's called Hierarchical
|
||||
Deterministic key generation. In short, every wallet using HD
|
||||
key generation has a root seed for each wallet, and this
|
||||
seed can be used to generate an arbitrary amount of later
|
||||
private and public keys. This is done in a standardized manner,
|
||||
so different wallets can operate with the same standard.
|
||||
|
||||
> If you want to jump into the details of how this work,
|
||||
> you should check out
|
||||
> [BIP 32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
|
||||
|
||||
Bitcoin-S supports generating keys in this fashion. Here's a
|
||||
full example of how to obtain a wallet seed, and then
|
||||
use that to generate further private and public keys:
|
||||
|
||||
```scala
|
||||
import scodec.bits._
|
||||
import org.bitcoins.core.crypto._
|
||||
import org.bitcoins.core.hd._
|
||||
|
||||
// the length of the entropy bit vector determine
|
||||
// how long our phrase ends up being
|
||||
// 256 bits of entropy results in 24 words
|
||||
val entropy: BitVector = MnemonicCode.getEntropy256Bits
|
||||
// entropy: BitVector = BitVector(256 bits, 0x200d85f1f5ea36bc7cbdad333be9866bf716fe552a2a5e69d099669c003034d2)
|
||||
|
||||
val mnemonicCode = MnemonicCode.fromEntropy(entropy)
|
||||
// mnemonicCode: MnemonicCode = Masked(MnemonicCodeImpl)
|
||||
|
||||
mnemonicCode.words // the phrase the user should write down
|
||||
// res0: Vector[String] = Vector(cactus, history, ladder, type, pet, funny, very, remember, creek, tent, ghost, subject, imitate, left, feed, earn, fury, excess, chaos, snake, scale, blossom, spy, february)
|
||||
|
||||
// the password argument is an optional, extra security
|
||||
// measure. all MnemonicCode instances will give you a
|
||||
// valid BIP39 seed, but different passwords will give
|
||||
// you different seeds. So you could have as many wallets
|
||||
// from the same seed as you'd like, by simply giving them
|
||||
// different passwords.
|
||||
val bip39Seed = BIP39Seed.fromMnemonic(mnemonicCode,
|
||||
password = "secret password")
|
||||
// bip39Seed: BIP39Seed = Masked(BIP39SeedImpl)
|
||||
|
||||
val xpriv = ExtPrivateKey.fromBIP39Seed(ExtKeyVersion.SegWitMainNetPriv,
|
||||
bip39Seed)
|
||||
// xpriv: ExtPrivateKey = Masked(ExtPrivateKeyImpl)
|
||||
val xpub = xpriv.extPublicKey
|
||||
// xpub: ExtPublicKey = zpub6jftahH18ngZyL5vP6LTWDhHibjuyqFGvfQk5YUmucHG3ezGZVfkYjgvjP6StwvwvQdTf8mzNbh9gDdKiXUER7LEkFexyGCiroGacnqcPR1
|
||||
|
||||
// you can now use the generated xpriv to derive further
|
||||
// private or public keys
|
||||
|
||||
// this can be done with BIP89 paths (called SegWitHDPath in bitcoin-s)
|
||||
val segwitPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
|
||||
// segwitPath: SegWitHDPath = m/84'/0'/0'/0/0
|
||||
|
||||
// alternatively:
|
||||
val otherSegwitPath =
|
||||
SegWitHDPath(HDCoinType.Bitcoin,
|
||||
accountIndex = 0,
|
||||
HDChainType.External,
|
||||
addressIndex = 0)
|
||||
// otherSegwitPath: SegWitHDPath = m/84'/0'/0'/0/0
|
||||
|
||||
segwitPath == otherSegwitPath
|
||||
// res1: Boolean = true
|
||||
|
||||
// there's also paths available for legacy
|
||||
// addresses (LegacyHDPath) as well as nested
|
||||
// segwit paths (NestedSegWitPath)
|
||||
```
|
||||
|
||||
## Generating new addresses without having access to the private key
|
||||
|
||||
One the coolest features of HD wallets is that it's possible
|
||||
to generate addresses offline, without having access to the
|
||||
private keys. This feature is commonly called watch-only
|
||||
wallets, where a wallet can import information about all
|
||||
your past and future transactions, without being able to
|
||||
spend or steal any of your money.
|
||||
|
||||
Let's see an example of this:
|
||||
|
||||
```scala
|
||||
import scala.util.Success
|
||||
import org.bitcoins.core.protocol.script._
|
||||
import org.bitcoins.core.protocol.Bech32Address
|
||||
import org.bitcoins.core.config.TestNet3
|
||||
|
||||
// first account -------┐
|
||||
// bitcoin ----------┐ |
|
||||
// segwit --------┐ | |
|
||||
val accountPath = BIP32Path.fromString("m/84'/0'/0'")
|
||||
// accountPath: BIP32Path = m/84'/0'/0'
|
||||
val accountXpub = {
|
||||
// this key is sensitive, keep away from prying eyes!
|
||||
val accountXpriv = xpriv.deriveChildPrivKey(accountPath)
|
||||
|
||||
// this key is not sufficient to spend from, but we
|
||||
// can generate addresses with it!
|
||||
accountXpriv.extPublicKey
|
||||
}
|
||||
// accountXpub: ExtPublicKey = zpub6ro29U1xexQQddCkf3rY6iirQsvuVunBCEhcngGzMxRGN6LE9d5GqLqWjXuf9mhknPrGnNJoa2B5ApBGYBYrVoWHDvwDTbiDKJAQoPhTKkM
|
||||
|
||||
// address no. 0 ---------------┐
|
||||
// external address ----------┐ |
|
||||
val firstAddressPath = SegWitHDPath.fromString("m/84'/0'/0'/0/0")
|
||||
// firstAddressPath: SegWitHDPath = m/84'/0'/0'/0/0
|
||||
val firstAccountAddress = {
|
||||
// this is a bit quirky, but we're not interesting in
|
||||
// deriving the complete path from our account xpub
|
||||
// instead, we're only interested in the part after
|
||||
// the account level (3rd level). the .diff() method
|
||||
// achieves that
|
||||
val Some(pathDiff) = accountPath.diff(firstAddressPath)
|
||||
|
||||
// deriving public keys from hardened extended keys
|
||||
// is not possible, that's why .deriveChildPubKey()
|
||||
// returns a Try[ExtPublicKey]. A hardened key is marked
|
||||
// by a ' after the number in the notation we use above.
|
||||
val Success(extPubKey) = accountXpub.deriveChildPubKey(pathDiff)
|
||||
val pubkey = extPubKey.key
|
||||
val scriptPubKey = P2WPKHWitnessSPKV0(pubkey)
|
||||
Bech32Address(scriptPubKey, TestNet3)
|
||||
}
|
||||
// firstAccountAddress: Bech32Address = tb1qkpt3z925hn3cp844pc9qfhgvgsv5fquxte94fu
|
||||
|
||||
// tada! We just generated an address you can send money to,
|
||||
// without having access to the private key!
|
||||
firstAccountAddress.value
|
||||
// res2: String = tb1qkpt3z925hn3cp844pc9qfhgvgsv5fquxte94fu
|
||||
|
||||
// you can now continue deriving addresses from the same public
|
||||
// key, by imitating what we did above. To get the next
|
||||
// HD path to generate an address at:
|
||||
val nextAddressPath: SegWitHDPath = firstAddressPath.next
|
||||
// nextAddressPath: SegWitHDPath = m/84'/0'/0'/0/1
|
||||
```
|
||||
|
||||
### Signing things with HD keys
|
||||
|
||||
Please see [sign.md](../crypto/sign.md) for information on how to sign things with HD keys.
|
158
website/versioned_docs/version-1.9.9/core/txbuilder.md
Normal file
158
website/versioned_docs/version-1.9.9/core/txbuilder.md
Normal file
@ -0,0 +1,158 @@
|
||||
---
|
||||
id: version-1.9.9-txbuilder
|
||||
title: TxBuilder Example
|
||||
original_id: txbuilder
|
||||
---
|
||||
|
||||
Bitcoin-S features a transaction building API that allows you to construct and sign Bitcoin transactions. Here's an example of how to use it
|
||||
|
||||
|
||||
```scala
|
||||
implicit val ec: ExecutionContext = ExecutionContext.Implicits.global
|
||||
// ec: ExecutionContext = scala.concurrent.impl.ExecutionContextImpl$$anon$3@7316ebb8[Running, parallelism = 8, size = 0, active = 0, running = 0, steals = 0, tasks = 0, submissions = 0]
|
||||
|
||||
// Initialize a transaction builder
|
||||
val builder = RawTxBuilder()
|
||||
// builder: RawTxBuilder = RawTxBuilder()
|
||||
|
||||
// generate a fresh private key that we are going to use in the scriptpubkey
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
// privKey: ECPrivateKey = Masked(ECPrivateKey)
|
||||
val pubKey = privKey.publicKey
|
||||
// pubKey: ECPublicKey = ECPublicKey(02126ec203c2e1e7ad463d27486fbc955cf9cdecf402f685b4c044fffc80aedb5e)
|
||||
|
||||
// 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)
|
||||
// creditingSpk: P2PKHScriptPubKey = pkh(29ae1a73d0a12613e624414f36c555ac327727a3)
|
||||
val amount = 10000.satoshis
|
||||
// amount: Satoshis = 10000 sats
|
||||
|
||||
// this is the UTXO we are going to be spending
|
||||
val utxo =
|
||||
TransactionOutput(value = amount, scriptPubKey = creditingSpk)
|
||||
// utxo: TransactionOutput = TransactionOutput(10000 sats,pkh(29ae1a73d0a12613e624414f36c555ac327727a3))
|
||||
|
||||
// the private key that locks the funds for the script we are spending too
|
||||
val destinationPrivKey = ECPrivateKey.freshPrivateKey
|
||||
// destinationPrivKey: ECPrivateKey = Masked(ECPrivateKey)
|
||||
|
||||
// the amount we are sending -- 5000 satoshis -- to the destinationSPK
|
||||
val destinationAmount = 5000.satoshis
|
||||
// destinationAmount: Satoshis = 5000 sats
|
||||
|
||||
// the script that corresponds to destination private key, this is what is receiving the money
|
||||
val destinationSPK =
|
||||
P2PKHScriptPubKey(pubKey = destinationPrivKey.publicKey)
|
||||
// destinationSPK: P2PKHScriptPubKey = pkh(082b7e53b2e65be14eee5fadcc28a279c7354922)
|
||||
|
||||
// this is where we are sending money too
|
||||
// we could add more destinations here if we
|
||||
// wanted to batch transactions
|
||||
val destinations = {
|
||||
val destination0 = TransactionOutput(value = destinationAmount,
|
||||
scriptPubKey = destinationSPK)
|
||||
|
||||
Vector(destination0)
|
||||
}
|
||||
// destinations: Vector[TransactionOutput] = Vector(TransactionOutput(5000 sats,pkh(082b7e53b2e65be14eee5fadcc28a279c7354922)))
|
||||
|
||||
// Add the destinations to the tx builder
|
||||
builder ++= destinations
|
||||
// res0: RawTxBuilder = RawTxBuilder()
|
||||
|
||||
// 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 = Vector.empty,
|
||||
outputs = Vector(utxo),
|
||||
lockTime = UInt32.zero)
|
||||
// creditingTx: BaseTransaction = BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(29ae1a73d0a12613e624414f36c555ac327727a3))),UInt32Impl(0))
|
||||
|
||||
// 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)
|
||||
// outPoint: TransactionOutPoint = TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0)
|
||||
val input = TransactionInput(
|
||||
outPoint,
|
||||
EmptyScriptSignature,
|
||||
sequenceNumber = UInt32.zero)
|
||||
// input: TransactionInput = TransactionInputImpl(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),EmptyScriptSignature,UInt32Impl(0))
|
||||
|
||||
// Add a new input to our builder
|
||||
builder += input
|
||||
// res1: RawTxBuilder = RawTxBuilder()
|
||||
|
||||
// We can now generate a RawTxBuilderResult ready to be finalized
|
||||
val builderResult = builder.result()
|
||||
// builderResult: RawTxBuilderResult = RawTxBuilderResult(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),EmptyScriptSignature,UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(082b7e53b2e65be14eee5fadcc28a279c7354922))),UInt32Impl(0))
|
||||
|
||||
// this contains the information needed to analyze our input during finalization
|
||||
val inputInfo = P2PKHInputInfo(outPoint, amount, privKey.publicKey)
|
||||
// inputInfo: P2PKHInputInfo = P2PKHInputInfo(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),10000 sats,ECPublicKey(02126ec203c2e1e7ad463d27486fbc955cf9cdecf402f685b4c044fffc80aedb5e))
|
||||
|
||||
// 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(1.satoshi)
|
||||
// feeRate: SatoshisPerByte = 1 sats/byte
|
||||
|
||||
val changePrivKey = ECPrivateKey.freshPrivateKey
|
||||
// changePrivKey: ECPrivateKey = Masked(ECPrivateKey)
|
||||
val changeSPK = P2PKHScriptPubKey(pubKey = changePrivKey.publicKey)
|
||||
// changeSPK: P2PKHScriptPubKey = pkh(74ddfa5250245ebff96e45a3c07744dc1c7206cc)
|
||||
|
||||
// We chose a finalizer that adds a change output to our tx based on a fee rate
|
||||
val finalizer = StandardNonInteractiveFinalizer(
|
||||
Vector(inputInfo),
|
||||
feeRate,
|
||||
changeSPK)
|
||||
// finalizer: StandardNonInteractiveFinalizer = StandardNonInteractiveFinalizer(Vector(P2PKHInputInfo(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),10000 sats,ECPublicKey(02126ec203c2e1e7ad463d27486fbc955cf9cdecf402f685b4c044fffc80aedb5e))),1 sats/byte,pkh(74ddfa5250245ebff96e45a3c07744dc1c7206cc))
|
||||
|
||||
// We can now finalize the tx builder result from earlier with this finalizer
|
||||
val unsignedTx: Transaction = finalizer.buildTx(builderResult)
|
||||
// unsignedTx: Transaction = BaseTransaction(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),EmptyScriptSignature,UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(082b7e53b2e65be14eee5fadcc28a279c7354922)), TransactionOutput(4775 sats,pkh(74ddfa5250245ebff96e45a3c07744dc1c7206cc))),UInt32Impl(0))
|
||||
|
||||
// We now turn to signing the unsigned transaction
|
||||
// this contains all the information we need to
|
||||
// validly sign the UTXO above
|
||||
val utxoInfo = ScriptSignatureParams(inputInfo = inputInfo,
|
||||
prevTransaction = creditingTx,
|
||||
signers = Vector(privKey),
|
||||
hashType =
|
||||
HashType.sigHashAll)
|
||||
// utxoInfo: ScriptSignatureParams[P2PKHInputInfo] = ScriptSignatureParams(P2PKHInputInfo(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),10000 sats,ECPublicKey(02126ec203c2e1e7ad463d27486fbc955cf9cdecf402f685b4c044fffc80aedb5e)),BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(29ae1a73d0a12613e624414f36c555ac327727a3))),UInt32Impl(0)),Vector(Masked(ECPrivateKey)),SIGHASH_ALL(1))
|
||||
|
||||
// all of the UTXO spending information, since we only have
|
||||
// one input, this is just one element
|
||||
val utxoInfos: Vector[ScriptSignatureParams[InputInfo]] = Vector(utxoInfo)
|
||||
// utxoInfos: Vector[ScriptSignatureParams[InputInfo]] = Vector(ScriptSignatureParams(P2PKHInputInfo(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),10000 sats,ECPublicKey(02126ec203c2e1e7ad463d27486fbc955cf9cdecf402f685b4c044fffc80aedb5e)),BaseTransaction(Int32Impl(1),Vector(),Vector(TransactionOutput(10000 sats,pkh(29ae1a73d0a12613e624414f36c555ac327727a3))),UInt32Impl(0)),Vector(Masked(ECPrivateKey)),SIGHASH_ALL(1)))
|
||||
|
||||
// Yay! Now we use the RawTxSigner object to sign the 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 ScriptSignatureParams to produce a validly
|
||||
// signed input. This UTXO has:
|
||||
// 1: one input
|
||||
// 2: outputs (destination and change outputs)
|
||||
// 3: a fee rate of 1 satoshi/byte
|
||||
val signedTx: Transaction =
|
||||
RawTxSigner.sign(
|
||||
utx = unsignedTx,
|
||||
utxoInfos = utxoInfos,
|
||||
expectedFeeRate = feeRate
|
||||
)
|
||||
// signedTx: Transaction = BaseTransaction(Int32Impl(2),Vector(TransactionInputImpl(TransactionOutPoint(453f318b92c49bcc9fffd2f685be192cd5d45e834c6cc7d76ae2d82a451f921a:0),P2PKHScriptSignature(ECPublicKeyBytes(ByteVector(33 bytes, 0x02126ec203c2e1e7ad463d27486fbc955cf9cdecf402f685b4c044fffc80aedb5e)), ECDigitalSignature(304402203d8f07fe2d56bb3bd27f152470eec0d5e9953c9c3a30470d426b29f6c920706202202b6feed57a744dfeca396842d0dad2f7e762a69d03ac02bd0e2e4e66247ce76d01)),UInt32Impl(0))),Vector(TransactionOutput(5000 sats,pkh(082b7e53b2e65be14eee5fadcc28a279c7354922)), TransactionOutput(4775 sats,pkh(74ddfa5250245ebff96e45a3c07744dc1c7206cc))),UInt32Impl(0))
|
||||
```
|
||||
|
||||
```scala
|
||||
signedTx.inputs.length
|
||||
// res2: Int = 1
|
||||
|
||||
signedTx.outputs.length
|
||||
// res3: Int = 2
|
||||
|
||||
//remember, you can call .hex on any bitcoin-s data structure to get the hex representation!
|
||||
signedTx.hex
|
||||
// res4: String = 02000000011a921f452ad8e26ad7c76c4c835ed4d52c19be85f6d2ff9fcc9bc4928b313f45000000006a47304402203d8f07fe2d56bb3bd27f152470eec0d5e9953c9c3a30470d426b29f6c920706202202b6feed57a744dfeca396842d0dad2f7e762a69d03ac02bd0e2e4e66247ce76d012102126ec203c2e1e7ad463d27486fbc955cf9cdecf402f685b4c044fffc80aedb5e000000000288130000000000001976a914082b7e53b2e65be14eee5fadcc28a279c735492288aca7120000000000001976a91474ddfa5250245ebff96e45a3c07744dc1c7206cc88ac00000000
|
||||
```
|
65
website/versioned_docs/version-1.9.9/crypto/sign.md
Normal file
65
website/versioned_docs/version-1.9.9/crypto/sign.md
Normal file
@ -0,0 +1,65 @@
|
||||
---
|
||||
id: version-1.9.9-sign
|
||||
title: Sign API
|
||||
original_id: sign
|
||||
---
|
||||
|
||||
### The [`Sign` API](/api/org/bitcoins/crypto/Sign)
|
||||
|
||||
This is the API we define to sign things with. It takes in an arbitrary byte vector and returns a `Future[ECDigitalSignature]`. The reason we incorporate `Future`s here is for extensibility of this API. We would like to provide implementations of this API for hardware devices, which need to be asynchrnous since they may require user input.
|
||||
|
||||
From [Sign.scala](/api/org/bitcoins/crypto/Sign):
|
||||
|
||||
```scala
|
||||
import scodec.bits._
|
||||
import org.bitcoins.crypto._
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
trait Sign {
|
||||
def signFunction: ByteVector => Future[ECDigitalSignature]
|
||||
|
||||
def signFuture(bytes: ByteVector): Future[ECDigitalSignature] =
|
||||
signFunction(bytes)
|
||||
|
||||
def sign(bytes: ByteVector): ECDigitalSignature = {
|
||||
Await.result(signFuture(bytes), 30.seconds)
|
||||
}
|
||||
|
||||
def publicKey: ECPublicKey
|
||||
}
|
||||
```
|
||||
|
||||
The `ByteVector` that is input to the `signFunction` should be the hash that is output from [`TransactionSignatureSerializer`](/api/org/bitcoins/core/crypto/TransactionSignatureSerializer)'s `hashForSignature` method. Our in-memory [`BaseECKey`](/api/org/bitcoins/crypto/BaseECKey) types implement the `Sign` API.
|
||||
|
||||
If you wanted to implement a new `Sign` api for a hardware wallet, you can easily pass it into the `TxBuilder`/`Signer` classes to allow for you to use those devices to sign with Bitcoin-S.
|
||||
|
||||
This API is currently used to sign ordinary transactions with our [`Signer`](/api/org/bitcoins/core/wallet/signer/Signer)s. The `Signer` subtypes (i.e. `P2PKHSigner`) implement the specific functionality needed to produce a valid digital signature for their corresponding script type.
|
||||
|
||||
|
||||
### The [`ExtSign`](/api/org/bitcoins/crypto/Sign) API.
|
||||
|
||||
An [ExtKey](/api/org/bitcoins/core/crypto/ExtKey) is a data structure that can be used to generate more keys from a parent key. For more information look at [hd-keys.md](../core/hd-keys.md)
|
||||
|
||||
You can sign with `ExtPrivateKey` the same way you could with a normal `ECPrivateKey`.
|
||||
|
||||
```scala
|
||||
import org.bitcoins.core.hd._
|
||||
import org.bitcoins.core.crypto._
|
||||
|
||||
val extPrivKey = ExtPrivateKey(ExtKeyVersion.SegWitMainNetPriv)
|
||||
// extPrivKey: ExtPrivateKey = Masked(ExtPrivateKeyImpl)
|
||||
|
||||
extPrivKey.sign(DoubleSha256Digest.empty.bytes)
|
||||
// res0: ECDigitalSignature = ECDigitalSignature(3045022100cec2e7cbd4afbe780190e42a5fb2721f9123ec5012038b37a93f41fc06f788650220074179037a09bf3bab4c02afa4c5e2b0983b9e46622ccd9a7a493fa4b19d7a17)
|
||||
|
||||
val path = BIP32Path(Vector(BIP32Node(0,HardenedType.defaultOpt)))
|
||||
// path: BIP32Path = m/0'
|
||||
|
||||
extPrivKey.sign(DoubleSha256Digest.empty.bytes,path)
|
||||
// res1: ECDigitalSignature = ECDigitalSignature(304402207cfdc8144cd46d356458c9aff8f231af0dca7b3a2c10a4bf547b4b83dee9de4f022012f9d576ad9cf7d5fff67486e398575516995894d41702ee61558561b7b38262)
|
||||
```
|
||||
|
||||
With `ExtSign`, you can use `ExtPrivateKey` to sign transactions inside of `TxBuilder` since `UTXOSpendingInfo` takes in `Sign` as a parameter.
|
||||
|
||||
You can also provide a `path` to use to derive a child `ExtPrivateKey`, and then sign with that child private key
|
127
website/versioned_docs/version-1.9.9/getting-started.md
Normal file
127
website/versioned_docs/version-1.9.9/getting-started.md
Normal file
@ -0,0 +1,127 @@
|
||||
---
|
||||
id: version-1.9.9-getting-started
|
||||
title: Intro and Getting Started
|
||||
original_id: getting-started
|
||||
---
|
||||
|
||||
## Philosophy
|
||||
|
||||
Bitcoin-S is a loosely coupled set of cryptocurrency libraries for the JVM. They work well together, but also can be used
|
||||
independently. This project's goal is NOT to be a full node implementation, rather a set of scalable cryptocurrency libraries
|
||||
that use industry standard tools (rather than esoteric tech often found in cryptocurrency) where possible to make the lives of professional
|
||||
software engineers, security engineers, devops engineers and accountants easier.
|
||||
We are rapidly iterating on development with the goal of getting to a set of stable APIs that only change when the underlying bitcoin protocol changes.
|
||||
|
||||
If you are a professional working a cryptocurrency business and
|
||||
have feedback on how to make your lives easier, please reach out on [slack](https://join.slack.com/t/suredbits/shared_invite/zt-eavycu0x-WQL7XOakzQo8tAy7jHHZUw),
|
||||
[gitter](https://gitter.im/bitcoin-s-core/) or [twitter](https://twitter.com/Chris_Stewart_5/)!
|
||||
|
||||
## Getting prebuilt artifacts
|
||||
|
||||
### Java binaries
|
||||
|
||||
<details>
|
||||
|
||||
#### Latest release
|
||||
|
||||
Please see the release page on github, you can find it [here](https://github.com/bitcoin-s/bitcoin-s/releases)
|
||||
|
||||
#### Master builds
|
||||
|
||||
We build installers for mac, linux and windows everytime a PR is merged to master.
|
||||
|
||||
You can find the latest builds at this link:
|
||||
|
||||
https://github.com/bitcoin-s/bitcoin-s/actions/workflows/release.yml
|
||||
|
||||
Here is what the installers look like
|
||||
|
||||
![installers](/img/doc-imgs/github-artifacts.png)
|
||||
|
||||
</details>
|
||||
|
||||
### Docker
|
||||
|
||||
<details>
|
||||
We publish docker images to docker hub on every PR merge and tag on github.
|
||||
You can obtain the images for both the app server and oracle server on these
|
||||
docker hub repos
|
||||
|
||||
[bitcoin-s-server docker hub repo](https://hub.docker.com/r/bitcoinscala/bitcoin-s-server/tags?page=1&ordering=last_updated)
|
||||
|
||||
[bitcoin-s-oracle-server docker hub repo](https://hub.docker.com/r/bitcoinscala/bitcoin-s-oracle-server/tags?page=1&ordering=last_updated)
|
||||
</details>
|
||||
|
||||
### Library jars
|
||||
|
||||
<details>
|
||||
Add this to your `build.sbt`:
|
||||
|
||||
```scala
|
||||
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-bitcoind-rpc" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-core" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-chain" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-dlc-oracle" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-eclair-rpc" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-fee-provider" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-key-manager" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-lnd-rpc" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-node" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-oracle-explorer-client" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" % "bitcoin-s-secp256k1jni" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit-core" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-testkit" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-wallet" % "1.9.8"
|
||||
|
||||
libraryDependencies += "org.bitcoin-s" %% "bitcoin-s-zmq" % "1.9.8"
|
||||
|
||||
```
|
||||
|
||||
|
||||
### Nightly builds
|
||||
|
||||
You can also run on the bleeding edge of Bitcoin-S, by
|
||||
adding a snapshot build to your `build.sbt`. The most
|
||||
recent snapshot published is `1.9.8-76-654d4086-20240510-1154-SNAPSHOT`.
|
||||
|
||||
|
||||
|
||||
To fetch snapshots, you will need to add the correct
|
||||
resolver in your `build.sbt`:
|
||||
|
||||
```sbt
|
||||
resolvers += Resolver.sonatypeRepo("snapshots")
|
||||
```
|
||||
|
||||
The official maven repo for releases is
|
||||
|
||||
https://repo1.maven.org/maven2/org/bitcoin-s/
|
||||
|
||||
The repo for snapshots, which are published after everytime something is merged to master:
|
||||
|
||||
https://oss.sonatype.org/content/repositories/snapshots/org/bitcoin-s/
|
||||
|
||||
</details>
|
||||
|
||||
## Building JARs yourself
|
||||
|
||||
Please see [our setup docs](getting-setup.md)
|
||||
|
||||
## If you want to setup Bitcoin-S locally for development
|
||||
|
||||
Please see [our setup docs](getting-setup.md)
|
124
website/versioned_docs/version-1.9.9/key-manager/key-manager.md
Normal file
124
website/versioned_docs/version-1.9.9/key-manager/key-manager.md
Normal file
@ -0,0 +1,124 @@
|
||||
---
|
||||
id: version-1.9.9-key-manager
|
||||
title: Key Manager
|
||||
original_id: key-manager
|
||||
---
|
||||
|
||||
|
||||
### Key Manager
|
||||
|
||||
The key manager module's goal is to encapsulate all private key interactions with the [wallet](../wallet/wallet.md) project.
|
||||
|
||||
As of this writing, there is only one type of `KeyManager` - [`BIP39KeyManager`](/api/org/bitcoins/keymanager/bip39/BIP39KeyManager).
|
||||
|
||||
The [`BIP39KeyManager`](/api/org/bitcoins/keymanager/bip39/BIP39KeyManager) stores a [`MnemonicCode`](/api/org/bitcoins/core/crypto/MnemonicCode) on disk which can be decrypted and used as a hot wallet.
|
||||
|
||||
Over the long run, we want to make it so that the wallet project needs to communicate with the key-manager to access private keys.
|
||||
|
||||
This means that ALL SIGNING should be done inside of the key-manager, and private keys should not leave the key manager.
|
||||
|
||||
This makes it easier to reason about the security characteristics of our private keys, and a way to provide a uniform interface for alternative key storage systems (hsm, cloud based key storage, etc) to be plugged into the bitcoin-s library.
|
||||
|
||||
#### Creating a key manager
|
||||
|
||||
The first thing you need create a key manager is some entropy.
|
||||
|
||||
A popular way for bitcoin wallet's to represent entropy is [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) which you [can use in bitcoin-s](/api/org/bitcoins/core/crypto/BIP39Seed)
|
||||
|
||||
You can generate a `MnemonicCode` in bitcoin-s with the following code
|
||||
|
||||
```scala
|
||||
import org.bitcoins.core.crypto._
|
||||
|
||||
//get 256 bits of random entropy
|
||||
val entropy = MnemonicCode.getEntropy256Bits
|
||||
// entropy: scodec.bits.BitVector = BitVector(256 bits, 0x69c0caeba4cf6c09ba8713b2b80c756b65042ae12c5a177ef54e46c7074b4cb8)
|
||||
|
||||
val mnemonic = MnemonicCode.fromEntropy(entropy)
|
||||
// mnemonic: MnemonicCode = Masked(MnemonicCodeImpl)
|
||||
|
||||
//you can print that mnemonic seed with this
|
||||
println(mnemonic.words)
|
||||
// Vector(hawk, alien, road, end, walnut, age, tube, tiny, razor, scatter, bubble, strategy, expect, betray, lucky, mercy, blast, waste, fatal, mirror, idea, pizza, cream, avocado)
|
||||
```
|
||||
|
||||
Now that we have a `MnemonicCode` that was securely generated, we need to now create `KeyManagerParams` which tells us how to generate
|
||||
generate specific kinds of addresses for wallets.
|
||||
|
||||
`KeyManagerParams` takes 3 parameters:
|
||||
|
||||
1. `seedPath` there is where we store the `MnemonicCode` on your file system
|
||||
2. [`purpose`](/api/org/bitcoins/core/hd/HDPurpose) which represents what type of utxo this `KeyManager` is associated with. The specification for this is in [BIP43](https://github.com/bitcoin/bips/blob/master/bip-0043.mediawiki)
|
||||
3. [`network`](/api/org/bitcoins/core/config/NetworkParameters) what cryptocurrency network this key manager is associated with
|
||||
|
||||
|
||||
This controls how the root key is defined. The combination of `purpose` and `network` determine how the root `ExtKey` is serialized. For more information on how this works please see [hd-keys](../core/hd-keys.md)
|
||||
|
||||
Now we can construct a native segwit key manager for the regtest network!
|
||||
|
||||
```scala
|
||||
//this will create a temp directory with the prefix 'key-manager-example` that will
|
||||
//have a file in it called "encrypted-bitcoin-s-seed.json"
|
||||
val seedPath = Files.createTempDirectory("key-manager-example").resolve(WalletStorage.ENCRYPTED_SEED_FILE_NAME)
|
||||
// seedPath: Path = /var/folders/fg/scntn26d4h55x96zc456l0r40000gn/T/key-manager-example18304823303114843390/encrypted-bitcoin-s-seed.json
|
||||
|
||||
//let's create a native segwit key manager
|
||||
val purpose = HDPurposes.SegWit
|
||||
// purpose: HDPurpose = m/84'
|
||||
|
||||
//let's choose regtest as our network
|
||||
val network = RegTest
|
||||
// network: RegTest.type = RegTest
|
||||
|
||||
val kmParams = KeyManagerParams(seedPath, purpose, network)
|
||||
// kmParams: KeyManagerParams = KeyManagerParams(/var/folders/fg/scntn26d4h55x96zc456l0r40000gn/T/key-manager-example18304823303114843390/encrypted-bitcoin-s-seed.json,m/84',RegTest)
|
||||
|
||||
val aesPasswordOpt = Some(AesPassword.fromString("password"))
|
||||
// aesPasswordOpt: Some[AesPassword] = Some(Masked(AesPassword))
|
||||
|
||||
val km = BIP39KeyManager.initializeWithMnemonic(aesPasswordOpt, mnemonic, None, kmParams)
|
||||
// km: Either[KeyManagerInitializeError, BIP39KeyManager] = Right(org.bitcoins.keymanager.bip39.BIP39KeyManager@348b0a5c)
|
||||
|
||||
val rootXPub = km.right.get.getRootXPub
|
||||
// rootXPub: ExtPublicKey = vpub5SLqN2bLY4WeZi84c6UA6k2AwFyH3xBtRfuqU3duuYFGmEh8zD582Ly4iZ1pYHiBTHHVjuadQfSi1htotVH8amEzxdDTSwusSLxZUB1ktqC
|
||||
|
||||
println(rootXPub)
|
||||
// vpub5SLqN2bLY4WeZi84c6UA6k2AwFyH3xBtRfuqU3duuYFGmEh8zD582Ly4iZ1pYHiBTHHVjuadQfSi1htotVH8amEzxdDTSwusSLxZUB1ktqC
|
||||
```
|
||||
|
||||
Which should print something that looks like this
|
||||
|
||||
`vpub5SLqN2bLY4WeXxMqwJHJFBEwxSscGB2uDUnsTS3edVjZEwTrQDFDNqoR2xLqARQPabGaXsHSTenTRcqm2EnB9MpuC4vSk3LqSgNmGGZtuq7`
|
||||
|
||||
which is a native segwit `ExtPubKey` for the regtest network!
|
||||
|
||||
You can always change the `network` or `purpose` to support different things. You do _not_ need to initialize the key manager
|
||||
again after initializing it once. You can use the same `mnemonic` for different networks, which you control `KeyManagerParams`.
|
||||
|
||||
```scala
|
||||
//let's create a nested segwit key manager for mainnet
|
||||
val mainnetKmParams = KeyManagerParams(seedPath, HDPurposes.SegWit, MainNet)
|
||||
// mainnetKmParams: KeyManagerParams = KeyManagerParams(/var/folders/fg/scntn26d4h55x96zc456l0r40000gn/T/key-manager-example18304823303114843390/encrypted-bitcoin-s-seed.json,m/84',MainNet)
|
||||
|
||||
//we do not need to all `initializeWithMnemonic()` again as we have saved the seed to dis
|
||||
val mainnetKeyManager = BIP39KeyManager.fromMnemonic(mnemonic, mainnetKmParams, None, Instant.now, false)
|
||||
// mainnetKeyManager: BIP39KeyManager = org.bitcoins.keymanager.bip39.BIP39KeyManager@65d8124f
|
||||
|
||||
val mainnetXpub = mainnetKeyManager.getRootXPub
|
||||
// mainnetXpub: ExtPublicKey = zpub6jftahH18ngZxttXwXcew6QBd8Z4pS9t67zibdDTRZknydx3zqjNWbbcoNrAXvKs5qkijoxsFJruYrM4mGwBmhyQRz19nbBpXFD92Tgi4XG
|
||||
|
||||
println(mainnetXpub)
|
||||
// zpub6jftahH18ngZxttXwXcew6QBd8Z4pS9t67zibdDTRZknydx3zqjNWbbcoNrAXvKs5qkijoxsFJruYrM4mGwBmhyQRz19nbBpXFD92Tgi4XG
|
||||
```
|
||||
|
||||
Which gives us something that looks like this
|
||||
|
||||
`zpub6jftahH18ngZw98KGjRo5XcxeKTQ2eztsvskb1dC9XF5TLimQquTs6Ry7nBBA425D9joXmfgJJCexmJ1u2SELJZJfRi95gcnXadLpZzYb5c`
|
||||
|
||||
which is a p2sh wrapped segwit `ExtPubKey` for the bitcoin main network!
|
||||
|
||||
#### Creating a key manager from existing mnemonic
|
||||
|
||||
To create a `KeyManager` from existing mnemonic you need to specify the `seedPath` and then construct the `KeyManagerParams` that you would like.
|
||||
|
||||
Finally you call `KeyManager.fromParams()` that reads the mnemonic from disk and create's the key manager
|
135
website/versioned_docs/version-1.9.9/node/node.md
Normal file
135
website/versioned_docs/version-1.9.9/node/node.md
Normal file
@ -0,0 +1,135 @@
|
||||
---
|
||||
id: version-1.9.9-node
|
||||
title: Light Client
|
||||
original_id: node
|
||||
---
|
||||
|
||||
Bitcoin-s has node module that allows you to connect to the p2p network.
|
||||
|
||||
### Neutrino Node
|
||||
|
||||
Bitcoin-s has experimental support for neutrino which is a new lite client proposal on the bitcoin p2p network. You can
|
||||
read more about how neutrino works [here](https://suredbits.com/neutrino-what-is-it-and-why-we-need-it/). At this time,
|
||||
bitcoin-s only supports connecting to one trusted peer.
|
||||
|
||||
#### Limitations
|
||||
|
||||
Currently, the node does not have an active mempool.
|
||||
It is only aware of transactions it broadcasts and ones confirmed in blocks.
|
||||
|
||||
#### Callbacks
|
||||
|
||||
Bitcoin-S support call backs for the following events that happen on the bitcoin p2p network:
|
||||
|
||||
1. onTxReceived
|
||||
2. onBlockReceived
|
||||
3. onMerkleBlockReceived
|
||||
4. onCompactFilterReceived
|
||||
|
||||
That means every time one of these events happens on the p2p network, we will call your callback
|
||||
so that you can be notified of the event. These callbacks will be run after the message has been
|
||||
recieved and will execute sequentially. If any of them fail an error log will be output and the remainder of the callbacks will continue.
|
||||
Let's make an easy one
|
||||
|
||||
#### Example
|
||||
|
||||
Here is an example of constructing a neutrino node and registering a callback so you can be notified of an event.
|
||||
|
||||
To run the example, we need a bitcoind binary that has neutrino support.
|
||||
Bitcoin Core only has p2p neutrino support as of version 0.21.0.
|
||||
You will need to use a version of Bitcoin Core at least as old as 0.21.0.
|
||||
For your node to be able to service these filters you will need set
|
||||
`blockfilterindex=1` and `peerblockfilters=1` in your `bitcoin.conf` file.
|
||||
|
||||
|
||||
```scala
|
||||
implicit val system: ActorSystem = ActorSystem(s"node-example")
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
//we also require a bitcoind instance to connect to
|
||||
//so let's start one (make sure you ran 'sbt downloadBitcoind')
|
||||
val instance = BitcoindRpcTestUtil.instance(versionOpt = Some(BitcoindVersion.newest))
|
||||
val p2pPort = instance.p2pPort
|
||||
val bitcoindF = BitcoindRpcTestUtil.startedBitcoindRpcClient(Some(instance), Vector.newBuilder)
|
||||
|
||||
//contains information on how to connect to bitcoin's p2p info
|
||||
val peerF = bitcoindF.flatMap(b => NodeUnitTest.createPeer(b))
|
||||
|
||||
// set a data directory
|
||||
val prefix = s"node-example-${System.currentTimeMillis()}"
|
||||
val datadir = Files.createTempDirectory(prefix)
|
||||
|
||||
val tmpDir = BitcoinSTestAppConfig.tmpDir()
|
||||
// set the current network to regtest
|
||||
val config = ConfigFactory.parseString {
|
||||
s"""
|
||||
| bitcoin-s {
|
||||
| network = regtest
|
||||
| node {
|
||||
| mode = neutrino # neutrino, spv
|
||||
|
|
||||
| peers = ["127.0.0.1:$p2pPort"] # a list of peer addresses in form "hostname:portnumber"
|
||||
| # (e.g. "neutrino.testnet3.suredbits.com:18333")
|
||||
| # Port number is optional, the default value is 8333 for mainnet,
|
||||
| # 18333 for testnet and 18444 for regtest.
|
||||
| }
|
||||
| }
|
||||
|""".stripMargin
|
||||
}
|
||||
|
||||
implicit val appConfig: BitcoinSAppConfig = BitcoinSAppConfig(datadir, Vector(config))
|
||||
implicit val chainConfig: ChainAppConfig = appConfig.chainConf
|
||||
implicit val nodeConfig: NodeAppConfig = appConfig.nodeConf
|
||||
|
||||
val initNodeF = nodeConfig.start()
|
||||
|
||||
//yay! All setup done, let's create a node and then start it!
|
||||
val nodeF = for {
|
||||
peer <- peerF
|
||||
} yield {
|
||||
NeutrinoNode(
|
||||
walletCreationTimeOpt = None, //you can set this to only sync compact filters after the timestamp
|
||||
paramPeers = Vector(peer),
|
||||
nodeConfig = nodeConfig,
|
||||
chainConfig = chainConfig,
|
||||
actorSystem = system)
|
||||
}
|
||||
|
||||
//let's start it
|
||||
val startedNodeF = nodeF.flatMap(_.start())
|
||||
|
||||
//let's make a simple callback that print's the
|
||||
//blockhash everytime we receive a block on the network
|
||||
val blockReceivedFunc: OnBlockReceived = { (block: Block) =>
|
||||
Future.successful(
|
||||
println(s"Received blockhash=${block.blockHeader.hashBE}"))
|
||||
}
|
||||
|
||||
// Create callback
|
||||
val nodeCallbacks = NodeCallbacks.onBlockReceived(blockReceivedFunc)
|
||||
|
||||
// Add call to our node's config
|
||||
nodeConfig.addCallbacks(nodeCallbacks)
|
||||
|
||||
//let's test it out by generating a block with bitcoind!
|
||||
|
||||
val genBlockF = for {
|
||||
bitcoind <- bitcoindF
|
||||
addr <- bitcoind.getNewAddress
|
||||
hashes <- bitcoind.generateToAddress(1,addr)
|
||||
} yield hashes
|
||||
|
||||
//you should see our callback print a block hash
|
||||
//when running this code
|
||||
|
||||
//cleanup
|
||||
val cleanupF = for {
|
||||
_ <- genBlockF
|
||||
bitcoind <- bitcoindF
|
||||
node <- startedNodeF
|
||||
x = NeutrinoNodeConnectedWithBitcoind(node.asInstanceOf[NeutrinoNode],bitcoind)
|
||||
_ <- NodeUnitTest.destroyNodeConnectedWithBitcoind(x)
|
||||
} yield ()
|
||||
|
||||
Await.result(cleanupF, 60.seconds)
|
||||
```
|
130
website/versioned_docs/version-1.9.9/rpc/bitcoind.md
Normal file
130
website/versioned_docs/version-1.9.9/rpc/bitcoind.md
Normal file
@ -0,0 +1,130 @@
|
||||
---
|
||||
id: version-1.9.9-rpc-bitcoind
|
||||
title: bitcoind/Bitcoin Core
|
||||
original_id: rpc-bitcoind
|
||||
---
|
||||
|
||||
## Downloading bitcoind
|
||||
|
||||
The Bitcoin Core RPC client in Bitcoin-S currently supports the Bitcoin Core
|
||||
- 25
|
||||
- 26
|
||||
- 27
|
||||
|
||||
version lines. It can be set up to work with both local and remote Bitcoin Core servers.
|
||||
|
||||
You can fetch them using bitcoin-s by running the following sbt command. If you already have bitcoind installed on your machine, you can skip this step.
|
||||
|
||||
|
||||
```bash
|
||||
sbt downloadBitcoind
|
||||
```
|
||||
|
||||
The binaries will be stored in `~/.bitcoin-s/binaries/bitcoind/`
|
||||
|
||||
|
||||
## Connecting to a local `bitcoind` instance
|
||||
|
||||
### Getting started quickly, with default options:
|
||||
|
||||
```scala
|
||||
implicit val ec: ExecutionContext = ExecutionContext.global
|
||||
implicit val system: ActorSystem = ActorSystem("System")
|
||||
// this reads authentication credentials and
|
||||
// connection details from the default data
|
||||
// directory on your platform
|
||||
val client = BitcoindRpcClient.fromDatadir(binary=new File("/path/to/bitcoind"), datadir=new File("/path/to/bitcoind-datadir"))
|
||||
|
||||
val balance: Future[Bitcoins] = for {
|
||||
_ <- client.start()
|
||||
balance <- client.getBalance
|
||||
} yield balance
|
||||
```
|
||||
|
||||
## Multi-wallet `bitcoind` instances
|
||||
|
||||
When using the `bitcoind` with multiple wallets you will need to specify the wallet's name.
|
||||
To do so the wallet rpc functions have an optional `walletName` parameter.
|
||||
|
||||
```scala
|
||||
implicit val ec: ExecutionContext = ExecutionContext.global
|
||||
implicit val system: ActorSystem = ActorSystem("System")
|
||||
val client = BitcoindRpcClient.fromDatadir(binary=new File("/path/to/bitcoind"), datadir=new File("/path/to/bitcoind-datadir"))
|
||||
|
||||
for {
|
||||
_ <- client.start()
|
||||
_ <- client.walletPassphrase("mypassword", 10000, "walletName")
|
||||
balance <- client.getBalance("walletName")
|
||||
} yield balance
|
||||
```
|
||||
|
||||
## Connecting to a remote `bitcoind`
|
||||
|
||||
First, we create a secure connection to our `bitcoind` instance by setting
|
||||
up a SSH tunnel:
|
||||
|
||||
```bash
|
||||
ssh -L 8332:localhost:8332 my-cool-user@my-cool-website.com
|
||||
```
|
||||
|
||||
> Note: the port number '8332' is the default for mainnet. If you want to
|
||||
> connect to a testnet `bitcoind`, the default port is '18332'
|
||||
|
||||
Now that we have a secure connection between our remote `bitcoind`, we're
|
||||
ready to create the connection with our RPC client
|
||||
|
||||
```scala
|
||||
implicit val ec: ExecutionContext = ExecutionContext.global
|
||||
implicit val system: ActorSystem = ActorSystem("System")
|
||||
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 authCredentials = BitcoindAuthCredentials.PasswordBased(
|
||||
username = username,
|
||||
password = password
|
||||
)
|
||||
|
||||
val bitcoindInstance = {
|
||||
BitcoindInstanceLocal(
|
||||
network = MainNet,
|
||||
uri = new URI(s"http://localhost:${MainNet.port}"),
|
||||
rpcUri = new URI(s"http://localhost:${MainNet.rpcPort}"),
|
||||
authCredentials = authCredentials
|
||||
)
|
||||
}
|
||||
|
||||
val rpcCli = BitcoindRpcClient(bitcoindInstance)
|
||||
|
||||
rpcCli.getBalance.onComplete { case balance =>
|
||||
println(s"Wallet balance=${balance}")
|
||||
}
|
||||
```
|
||||
|
||||
## Error handling
|
||||
|
||||
All errors returned by Bitcoin Core are mapped to a corresponding
|
||||
[`BitcoindException`](https://github.com/bitcoin-s/bitcoin-s/blob/master/bitcoind-rpc/src/main/scala/org/bitcoins/rpc/BitcoindException.scala).
|
||||
These exceptions contain an error code and a message. `BitcoindException` is a sealed
|
||||
trait, which means you can easily pattern match exhaustively. Of course, other errors
|
||||
could also happen: network errors, stack overflows or out-of-memory errors. The provided
|
||||
class is only intended to cover errors returned by Bitcoin Core. An example of how error
|
||||
handling could look:
|
||||
|
||||
```scala
|
||||
implicit val ec: ExecutionContext = ExecutionContext.global
|
||||
|
||||
// let's assume you have an already running client,
|
||||
// so there's no need to start this one
|
||||
val cli = BitcoindRpcClient.fromDatadir(binary=new File("/path/to/bitcoind"), datadir=new File("/path/to/bitcoind-datadir"))
|
||||
|
||||
// let's also assume you have a bitcoin address
|
||||
val address: BitcoinAddress = BitcoinAddress("bc1qm8kec4xvucdgtzppzvvr2n6wp4m4w0k8akhf98")
|
||||
|
||||
val txid: Future[DoubleSha256DigestBE] =
|
||||
cli.sendToAddress(address, 3.bitcoins).recoverWith {
|
||||
case BitcoindWalletException.UnlockNeeded(_) =>
|
||||
cli.walletPassphrase("my_passphrase", 60).flatMap { _ =>
|
||||
cli.sendToAddress(address, 3.bitcoins)
|
||||
}
|
||||
}
|
||||
```
|
60
website/versioned_docs/version-1.9.9/rpc/eclair.md
Normal file
60
website/versioned_docs/version-1.9.9/rpc/eclair.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
id: version-1.9.9-rpc-eclair
|
||||
title: Eclair
|
||||
original_id: rpc-eclair
|
||||
---
|
||||
|
||||
This is a RPC client for [Eclair](https://github.com/acinq/eclair). It assumes that a bitcoind instance is running.
|
||||
|
||||
Currently this RPC client is written for [v0.10.0](https://github.com/ACINQ/eclair/releases/tag/v0.10.0) version of Eclair.
|
||||
|
||||
## Configuration of Eclair
|
||||
|
||||
Please see the configuration secion of the
|
||||
[Eclair README](https://github.com/acinq/eclair#configuring-eclair).
|
||||
|
||||
You can find the configuration we use for our testing infrastrture for eclair [here](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/testkit/src/main/scala/org/bitcoins/testkit/eclair/rpc/EclairRpcTestUtil.scala#L98).
|
||||
|
||||
## Starting Eclair
|
||||
|
||||
You need to download the jar from the [eclair's github](https://github.com/ACINQ/eclair/releases/tag/v0.5.0).
|
||||
|
||||
To run Eclair by unzipping the `eclair-node-0.10.0-a63d2c2-bin.zip` and then running
|
||||
|
||||
```bash
|
||||
$ ./eclair-node-0.5.0-ac08560/bin/eclair-node.sh
|
||||
```
|
||||
|
||||
If you wish to start Eclair from the RPC client, you can do one of the following:
|
||||
|
||||
1. Construct a [`EclairRpcClient.binary`](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala#L51) field set
|
||||
2. Set the [`ECLAIR_PATH`](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/eclair-rpc/src/main/scala/org/bitcoins/eclair/rpc/client/EclairRpcClient.scala#L701) environment variable to the directory where the Eclair Jar is located.
|
||||
|
||||
We will default to using the `binary` field first when trying to start the jar, and the fallback to `ECLAIR_PATH`.
|
||||
|
||||
Here is an example of how to start eclair:
|
||||
|
||||
|
||||
```scala
|
||||
implicit val system: ActorSystem = ActorSystem(s"eclair-rpc-${System.currentTimeMillis}")
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
val datadirPath = Paths.get("path", "to", "datadir")
|
||||
val binaryPath = Paths.get("path", "to", "eclair-node-0.10.0-a63d2c2", "bin", "eclair-node.sh")
|
||||
val instance = EclairInstanceLocal.fromDatadir(datadirPath.toFile, logbackXml = None, proxyParams = None)
|
||||
val client = new EclairRpcClient(instance, Some(binaryPath.toFile))
|
||||
|
||||
val startedF = client.start()
|
||||
|
||||
for {
|
||||
eclair <- startedF
|
||||
info <- eclair.getInfo
|
||||
} yield {
|
||||
println(s"Eclair info: $info")
|
||||
}
|
||||
```
|
||||
|
||||
### Connecting to the websocket
|
||||
|
||||
As of `v0.10.0` eclair supports a websocket endpoint. This means you can receive updates of what is happening with eclair
|
||||
in real time. You can see an example of us testing this [here](https://github.com/bitcoin-s/bitcoin-s/blob/a043d3858ef33da51229ee59c478d2a6c9d5a46f/eclair-rpc-test/src/test/scala/org/bitcoins/eclair/rpc/EclairRpcClientTest.scala#L591)
|
60
website/versioned_docs/version-1.9.9/rpc/lnd.md
Normal file
60
website/versioned_docs/version-1.9.9/rpc/lnd.md
Normal file
@ -0,0 +1,60 @@
|
||||
---
|
||||
id: version-1.9.9-lnd-rpc
|
||||
title: LND
|
||||
original_id: lnd-rpc
|
||||
---
|
||||
|
||||
This is an RPC client for [LND](https://github.com/LightningNetwork/lnd). It assumes that a bitcoind instance is running.
|
||||
|
||||
Currently, this RPC client is written for [v0.17.5](https://github.com/lightningnetwork/lnd/releases/tag/v0.17.3-beta) version of LND.
|
||||
|
||||
## Configuration of LND
|
||||
|
||||
Please see the [sample configuration for LND](https://github.com/lightningnetwork/lnd/blob/v0.17.3-beta/sample-lnd.conf).
|
||||
|
||||
You can find the configuration we use for our testing infrastructure for lnd [here](https://github.com/bitcoin-s/bitcoin-s/blob/656e0928bf1bf4f511f60dec625699b454f29a1f/testkit/src/main/scala/org/bitcoins/testkit/lnd/LndRpcTestUtil.scala#L90).
|
||||
|
||||
## Starting LND
|
||||
|
||||
You need to download the binaries from the [LND's github](https://github.com/lightningnetwork/lnd/releases/tag/v0.17.3-beta).
|
||||
|
||||
To run lnd by unzipping the `lnd-linux-amd64-v0.17.5-beta.tar.gz` (or whichever platform you are on) and then running
|
||||
|
||||
```bash
|
||||
$ ./lnd-linux-amd64-v0.17.3-beta/lnd
|
||||
```
|
||||
|
||||
If you wish to start lnd from the RPC client, you can construct a [`LndRpcClient.binary`](https://github.com/bitcoin-s/bitcoin-s/blob/656e0928bf1bf4f511f60dec625699b454f29a1f/lnd-rpc/src/main/scala/org/bitcoins/lnd/rpc/LndRpcClient.scala#L35) field set
|
||||
|
||||
We will default to using the `binary` field first when trying to start the jar, and the fallback to the default datadir (`~/.lnd`).
|
||||
|
||||
Here is an example of how to start lnd:
|
||||
|
||||
|
||||
```scala
|
||||
implicit val system: ActorSystem = ActorSystem(s"lnd-rpc-${System.currentTimeMillis}")
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
val datadirPath = Paths.get("path", "to", "datadir")
|
||||
val binaryPath = Paths.get("path", "to", "lnd-linux-amd64-v0.17.5-beta", "lnd")
|
||||
val instance = LndInstanceLocal.fromDataDir(datadirPath.toFile)
|
||||
val client = new LndRpcClient(instance, Some(binaryPath.toFile))
|
||||
|
||||
val startedF = client.start()
|
||||
|
||||
for {
|
||||
lnd <- startedF
|
||||
info <- lnd.getInfo
|
||||
} yield {
|
||||
println(s"Lnd info: $info")
|
||||
}
|
||||
```
|
||||
|
||||
### Updating to a new LND version
|
||||
|
||||
The lnd rpc module uses lnd's gRPC. This means when updating to the latest version, the `.proto` files will need to be updated.
|
||||
Bitcoin-S stores them in [lnd-rpc/src/main/protobuf](https://github.com/bitcoin-s/bitcoin-s/tree/master/lnd-rpc/src/main/protobuf).
|
||||
You can find the files to copy from LND [here](https://github.com/lightningnetwork/lnd/tree/master/lnrpc).
|
||||
|
||||
After updating the `proto` files you can run `sbt compile` and this will generate the corresponding class files, this should then give
|
||||
compile warnings for changed rpc functions.
|
92
website/versioned_docs/version-1.9.9/secp256k1/secp256k1.md
Normal file
92
website/versioned_docs/version-1.9.9/secp256k1/secp256k1.md
Normal file
@ -0,0 +1,92 @@
|
||||
---
|
||||
id: version-1.9.9-secp256k1
|
||||
title: Secp256k1
|
||||
original_id: secp256k1
|
||||
---
|
||||
|
||||
[Libsecp256k1](https://github.com/bitcoin-core/secp256k1) is used to preform cryptographic operations on the secp256k1 curve.
|
||||
This is the curve that bitcoin uses. There is a _signficant_ speedup when using this library compared to java crypto libraries
|
||||
like bouncy castle.
|
||||
|
||||
In bitcoin-s, we support native binaries for libsecp256k1
|
||||
|
||||
1. [linux 32 bit](../../secp256k1jni/natives/linux_32)
|
||||
2. [linux 64 bit](../../secp256k1jni/natives/linux_64)
|
||||
3. [mac osx 64 bit](../../secp256k1jni/natives/osx_64)
|
||||
4. [windows 64 bit](../../secp256k1jni/natives/windows_64)
|
||||
|
||||
Bitcoin-s uses a zero dependency library called [`native-lib-loader`](https://github.com/scijava/native-lib-loader).
|
||||
That does the appropriate loading of the library onto your classpath to be accessed.
|
||||
|
||||
### Using libsecp256k1
|
||||
|
||||
To tell if you have access to libsecp256k1 you can do the following
|
||||
|
||||
|
||||
```scala
|
||||
val isEnabled = org.bitcoin.Secp256k1Context.isEnabled()
|
||||
|
||||
println(s"Secp256k1Context.isEnabled=${isEnabled}")
|
||||
```
|
||||
|
||||
If libsecp256k1 is enabled, you can use [NativeSecp256k1](/api/org/bitcoin/NativeSecp256k1)
|
||||
with static method defined in the class.
|
||||
|
||||
|
||||
```scala
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
val pubKey = privKey.publicKey
|
||||
val dataToSign = DoubleSha256Digest.empty
|
||||
|
||||
val signature = NativeSecp256k1.sign(dataToSign.bytes.toArray, privKey.bytes.toArray)
|
||||
|
||||
val verify = NativeSecp256k1.verify(dataToSign.bytes.toArray, signature, pubKey.bytes.toArray)
|
||||
|
||||
println(s"Verified with NativeSecp256k1 signature=${verify}")
|
||||
|
||||
//you can also just directly sign with the ECKey interface:
|
||||
val signature2 = privKey.sign(dataToSign)
|
||||
|
||||
val verified2 = pubKey.verify(dataToSign, signature2)
|
||||
|
||||
println(s"Verified with NativeSecp256k1 again=${verified2}")
|
||||
```
|
||||
|
||||
### When libsecp256k1 isn't available, or you want to turn it off
|
||||
|
||||
There are two reasons you wouldn't want to use libsecp256k1
|
||||
|
||||
1. You don't trust the pre-compiled binaries we are using
|
||||
2. Your OS/arch is not supported
|
||||
|
||||
There are two ways you can circumvent libsecp256k1
|
||||
|
||||
1. Set `DISABLE_SECP256K1=true` in your environment variables. This will force `CryptoContext.default` to return false which will make Bitcoin-S act like `Secp256k1Context.isEnabled()` has returned false.
|
||||
2. Call Bouncy castle methods in `ECKey`.
|
||||
|
||||
Here is an example of calling bouncy castle methods in `ECKey`
|
||||
|
||||
```scala
|
||||
val privKey = ECPrivateKey.freshPrivateKey
|
||||
// privKey: ECPrivateKey = Masked(ECPrivateKey)
|
||||
// calls bouncy castle indirectly via CryptoContext
|
||||
val publicKey = privKey.publicKey
|
||||
// publicKey: ECPublicKey = ECPublicKey(02d9bc240bb04fae980b5ae9ac73b4f166e11fc0039ce8c7d80e8e7734611fd4a2)
|
||||
val dataToSign = DoubleSha256Digest.empty
|
||||
// dataToSign: DoubleSha256Digest = DoubleSha256Digest(0000000000000000000000000000000000000000000000000000000000000000)
|
||||
|
||||
// calls bouncy castle indirectly via CryptoContext
|
||||
val signature = privKey.sign(dataToSign.bytes)
|
||||
// signature: ECDigitalSignature = ECDigitalSignature(3045022100ac836f66eb7297bdc5280c55c1c1cd1c99e55fcdca4d73cf9830ec3608b8a57b022035e79c50c6f1254ad14be5c67b9ffc9d8958d5160dd4a0d123c9d7bc965731e0)
|
||||
|
||||
// calls bouncy castle indirectly via CryptoContext
|
||||
val verified = publicKey.verify(dataToSign.bytes, signature)
|
||||
// verified: Boolean = true
|
||||
|
||||
println(s"Verified with bouncy castle=${verified}")
|
||||
// Verified with bouncy castle=true
|
||||
```
|
||||
|
||||
### Building libsecp256k1
|
||||
|
||||
[See instructions here](add-to-jni.md#adding-to-bitcoin-s)
|
169
website/versioned_docs/version-1.9.9/testkit/testkit.md
Normal file
169
website/versioned_docs/version-1.9.9/testkit/testkit.md
Normal file
@ -0,0 +1,169 @@
|
||||
---
|
||||
id: version-1.9.9-testkit
|
||||
title: Testkit
|
||||
original_id: testkit
|
||||
---
|
||||
|
||||
## Philosophy of Testkit
|
||||
|
||||
The high level of of the bitcoin-s testkit is to mimic and provide functionality to test 3rd party applications.
|
||||
|
||||
There are other examples of these in the Scala ecosystem like the `akka-testkit` and `slick-testkit`.
|
||||
|
||||
We use this testkit to test bitcoin-s it self.
|
||||
|
||||
|
||||
### Testkit for bitcoind
|
||||
|
||||
This gives the ability to create and destroy `bitcoind` on the underlying operating system to test against.
|
||||
|
||||
Our [BitcoindRpcClient](/api/org/bitcoins/rpc/client/common/BitcoindRpcClient) is tested with the functionality provided in the testkit.
|
||||
A quick example of a useful utility method is [BitcoindRpcTestUtil.startedBitcoindRpcClient()](/api/org/bitcoins/testkit/rpc/BitcoindRpcTestUtil).
|
||||
This spins up a bitcoind regtest instance on machine and generates 101 blocks on that node.
|
||||
|
||||
This gives you the ability to start spending money immediately with that bitcoind node.
|
||||
|
||||
```scala
|
||||
implicit val system: ActorSystem = ActorSystem("bitcoind-testkit-example")
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
//pick our bitcoind version we want to spin up
|
||||
val bitcoindV = BitcoindVersion.newest
|
||||
|
||||
//create an instance
|
||||
val instance = BitcoindRpcTestUtil.instance(versionOpt = Some(bitcoindV))
|
||||
|
||||
//now let's create an rpc client off of that instance
|
||||
val bitcoindRpcClientF = BitcoindRpcTestUtil.startedBitcoindRpcClient(Some(instance), Vector.newBuilder)
|
||||
|
||||
//yay! it's started. Now you can run tests against this.
|
||||
//let's just grab the block count for an example
|
||||
val blockCountF = for {
|
||||
bitcoind <- bitcoindRpcClientF
|
||||
count <- bitcoind.getBlockCount
|
||||
} yield {
|
||||
//run a test against the block count
|
||||
assert(count > 0, s"Block count was not more than zero!")
|
||||
}
|
||||
|
||||
//when you are done, don't forget to destroy it! Otherwise it will keep running on the underlying os
|
||||
val stoppedF = for {
|
||||
rpc <- bitcoindRpcClientF
|
||||
_ <- blockCountF
|
||||
stopped <- BitcoindRpcTestUtil.stopServers(Vector(rpc))
|
||||
} yield stopped
|
||||
```
|
||||
|
||||
For more information on how the bitcoind rpc client works, see our [bitcoind rpc docs](../rpc/bitcoind.md)
|
||||
|
||||
#### Caching bitcoind in test cases
|
||||
|
||||
When doing integration tests with bitcoind, you likely do not want to spin up a
|
||||
new bitcoind for _every_ test that is run.
|
||||
|
||||
Not to fear, when using `testkit` you can use our bitcoind fixtures for your unit tests!
|
||||
These will only spin up on bitcoind per test suite, rather than one bitcoind per test.
|
||||
|
||||
We currently have two types of fixtures available to users of this dependency
|
||||
|
||||
1. [Connected pairs of bitcoind nodes](https://github.com/bitcoin-s/bitcoin-s/blob/eaac9c154c25f3bd76615ea2151092f06df6bdb4/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala#L282)
|
||||
2. [Bitcoind nodes with funded wallets](https://github.com/bitcoin-s/bitcoin-s/blob/eaac9c154c25f3bd76615ea2151092f06df6bdb4/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala#L161)
|
||||
|
||||
If you mixin either of those traits for your test, you will now have access to the corresponding fixture.
|
||||
|
||||
You can find an examples of how to use these two test fixtures
|
||||
|
||||
1. [Example of using a connected pair of nodes in test suite](https://github.com/bitcoin-s/bitcoin-s/blob/32a6db930bdf849a94d92cd1de160b87845ab168/bitcoind-rpc-test/src/test/scala/org/bitcoins/rpc/common/WalletRpcTest.scala#L37)
|
||||
2. [Example of using a bitcoind with funded wallet in test suite](https://github.com/bitcoin-s/bitcoin-s/blob/eaac9c154c25f3bd76615ea2151092f06df6bdb4/testkit/src/main/scala/org/bitcoins/testkit/rpc/BitcoindFixtures.scala#L161)
|
||||
|
||||
|
||||
### Testkit for eclair
|
||||
|
||||
We have similar utility methods for eclair. Eclair's testkit requires a bitcoind running (which we can spin up thanks to our bitcoind testkit).
|
||||
|
||||
Here is an example of spinning up an eclair lightning node, that is connected to a bitcoind and testing your lightning application.
|
||||
|
||||
Make sure to run `sbt downloadBitcoind downloadEclair` before running this so you have access to the underlying eclair binares
|
||||
|
||||
```scala
|
||||
//Steps:
|
||||
//1. Open and confirm channel on the underlying blockchain (regtest)
|
||||
//2. pay an invoice
|
||||
//3. Await until the payment is processed
|
||||
//4. assert the node has received the payment
|
||||
//5. cleanup
|
||||
|
||||
implicit val system: ActorSystem = ActorSystem("eclair-testkit-example")
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
//we need a bitcoind to connect eclair nodes to
|
||||
lazy val bitcoindRpcClientF: Future[BitcoindRpcClient] = {
|
||||
for {
|
||||
cli <- EclairRpcTestUtil.startedBitcoindRpcClient()
|
||||
// make sure we have enough money to open channels
|
||||
address <- cli.getNewAddress
|
||||
_ <- cli.generateToAddress(200, address)
|
||||
} yield cli
|
||||
}
|
||||
|
||||
//let's create two eclair nodes now
|
||||
val clientF = for {
|
||||
bitcoind <- bitcoindRpcClientF
|
||||
e <- EclairRpcTestUtil.randomEclairClient(Some(bitcoind))
|
||||
} yield e
|
||||
|
||||
val otherClientF = for {
|
||||
bitcoind <- bitcoindRpcClientF
|
||||
e <- EclairRpcTestUtil.randomEclairClient(Some(bitcoind))
|
||||
} yield e
|
||||
|
||||
//great, setup done! Let's run the test
|
||||
//to verify we can send a payment over the channel
|
||||
for {
|
||||
client <- clientF
|
||||
otherClient <- otherClientF
|
||||
_ <- EclairRpcTestUtil.openAndConfirmChannel(clientF, otherClientF)
|
||||
invoice <- otherClient.createInvoice("abc", 50.msats)
|
||||
info <- otherClient.getInfo
|
||||
_ = assert(info.nodeId == invoice.nodeId)
|
||||
infos <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
_ = assert(infos.isEmpty)
|
||||
paymentId <- client.payInvoice(invoice)
|
||||
_ <- EclairRpcTestUtil.awaitUntilPaymentSucceeded(client, paymentId)
|
||||
sentInfo <- client.getSentInfo(invoice.lnTags.paymentHash.hash)
|
||||
} yield {
|
||||
assert(sentInfo.head.amount == 50.msats)
|
||||
}
|
||||
|
||||
//don't forget to shutdown everything!
|
||||
val stop1F = clientF.map(c => EclairRpcTestUtil.shutdown(c))
|
||||
|
||||
val stop2F = otherClientF.map(o => EclairRpcTestUtil.shutdown(o))
|
||||
|
||||
val stoppedBitcoindF = for {
|
||||
bitcoind <- bitcoindRpcClientF
|
||||
_ <- BitcoindRpcTestUtil.stopServers(Vector(bitcoind))
|
||||
} yield ()
|
||||
|
||||
|
||||
val resultF = for {
|
||||
_ <- stop1F
|
||||
_ <- stop2F
|
||||
_ <- stoppedBitcoindF
|
||||
_ <- system.terminate()
|
||||
} yield ()
|
||||
|
||||
Await.result(resultF, 180.seconds)
|
||||
```
|
||||
|
||||
### Other modules
|
||||
|
||||
You may find useful testkit functionality for other modules here
|
||||
|
||||
1. [Chain](/api/org/bitcoins/testkit/chain/ChainUnitTest)
|
||||
2. [Key Manager](/api/org/bitcoins/testkit/keymanager/KeyManagerApiUnitTest)
|
||||
3. [Wallet](/api/org/bitcoins/testkit/wallet/BitcoinSWalletTest)
|
||||
4. [Node](/api/org/bitcoins/testkit/node/NodeUnitTest)
|
||||
|
||||
In general, you will find constructors and destructors of fixtures that can be useful when testing your applications
|
||||
if you are using any of those modules.
|
121
website/versioned_docs/version-1.9.9/wallet/address-tagging.md
Normal file
121
website/versioned_docs/version-1.9.9/wallet/address-tagging.md
Normal file
@ -0,0 +1,121 @@
|
||||
---
|
||||
id: version-1.9.9-address-tagging
|
||||
title: Address and UTXO tagging
|
||||
original_id: address-tagging
|
||||
---
|
||||
|
||||
|
||||
### Using AddressTags
|
||||
|
||||
The Bitcoin-S wallet allows you to give addresses, and their associated utxos,
|
||||
a tag. These tags allow you to separate funds between utxos so you can query utxos,
|
||||
and spend from them, based off of an AddressTag. The system also allows you to create
|
||||
your own custom address tags, that will be enforced by the library.
|
||||
|
||||
An address tag consists of the tag name, and a tag type. We use a tag type so we can have
|
||||
tag with the same name without complications.
|
||||
|
||||
To create an address with a tag you can use `getNewAddress` but pass in a `Vector[AddressTag]`.
|
||||
It will add to the address tag database along with all the corresponding tags.
|
||||
|
||||
```scala
|
||||
wallet.getNewAddress(tags = Vector(ExampleAddressTag))
|
||||
```
|
||||
|
||||
When sending with `sendToAddress` you can also a `Vector` of new `AddressTag`s that will be applied to the
|
||||
resulting change outputs. Any tags of a different tag type not included in `newTag`s will also be applied to
|
||||
the change outputs.
|
||||
|
||||
```scala
|
||||
wallet.sendToAddress(exampleAddress, Bitcoins(2), SatoshisPerVirtualByte.one, account, Vector(ExampleAddressTag))
|
||||
```
|
||||
|
||||
Also, when sending you can use `fundRawTransaction` and use `fromTagOpt` to pass in an optional `AddressTag`,
|
||||
this will use only utxos associated with the `AddressTag`.
|
||||
|
||||
```scala
|
||||
wallet.fundRawTransaction(
|
||||
destinations = destinations,
|
||||
feeRate = SatoshisPerVirtualByte.one,
|
||||
fromTagOpt = Some(ExampleAddressTag),
|
||||
markAsReserved = false)
|
||||
```
|
||||
|
||||
### Creating your own AddressTags
|
||||
|
||||
You can create your own custom `AddressTag`s. This allows you to tag addresses and utxos in any way that your
|
||||
application needs. To do this you are going to need to use `ExternalAddressTag`. As an example we will create
|
||||
`AddressTag`s for user specific funds.
|
||||
|
||||
We will need to define the tag type, then define the tag name for each tag, as well as a way to go to and
|
||||
from a `String`. Then we define the actual tags, we are going to have a `Company`, `InsuranceFund`, and `UserId`
|
||||
tags. We are going to make the `UserId` tag special, and allow it to take in any user id so we can have a huge
|
||||
set of users but all with different ids.
|
||||
|
||||
```scala
|
||||
object UserIdTagType extends ExternalAddressTagType {
|
||||
override val typeName: String = "UserIdTag"
|
||||
}
|
||||
|
||||
/** Allows to assign funds in a specific address to a user */
|
||||
sealed trait UserIdTag extends ExternalAddressTag {
|
||||
override val tagType: AddressTagType = UserIdTagType
|
||||
}
|
||||
|
||||
object UserIdTags extends AddressTagFactory[UserIdTag] {
|
||||
|
||||
override val tagType: ExternalAddressTagType = UserIdTagType
|
||||
|
||||
case object CompanyTagName extends ExternalAddressTagName {
|
||||
override def name: String = "Company"
|
||||
}
|
||||
|
||||
case object InsuranceFundTagName extends ExternalAddressTagName {
|
||||
override def name: String = "InsuranceFund"
|
||||
}
|
||||
|
||||
/** Funds that do not belong to any user and instead belong to the company */
|
||||
case object Company extends ExternalAddressTag with UserIdTag {
|
||||
override val tagName: ExternalAddressTagName = CompanyTagName
|
||||
}
|
||||
|
||||
/** Funds in the company's insurance fund */
|
||||
case object InsuranceFund extends ExternalAddressTag with UserIdTag {
|
||||
override val tagName: ExternalAddressTagName = InsuranceFundTagName
|
||||
}
|
||||
|
||||
/** Funds that are specific to an individual user */
|
||||
case class UserId(id: String) extends ExternalAddressTag with UserIdTag {
|
||||
override val tagName: ExternalAddressTagName = new ExternalAddressTagName {
|
||||
override def name: String = id
|
||||
}
|
||||
|
||||
val uid = id.toLong
|
||||
}
|
||||
|
||||
override val all: Vector[UserIdTag] = Vector(Company, InsuranceFund)
|
||||
|
||||
override val tagNames: Vector[AddressTagName] = Vector(CompanyTagName, InsuranceFundTagName)
|
||||
|
||||
override def fromStringOpt(str: String): Option[UserIdTag] = {
|
||||
all.find(tag => str.toLowerCase() == tag.toString.toLowerCase) match {
|
||||
case Some(tag) =>
|
||||
Some(tag)
|
||||
case None =>
|
||||
Some(UserId(str))
|
||||
}
|
||||
}
|
||||
|
||||
override def fromString(str: String): UserIdTag = {
|
||||
fromStringOpt(str) match {
|
||||
case Some(tag) => tag
|
||||
case None => sys.error(s"Could not find tag=$str")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def fromUID(uid: Long): UserIdTag = {
|
||||
UserId(uid.toString)
|
||||
}
|
||||
}
|
||||
```
|
108
website/versioned_docs/version-1.9.9/wallet/wallet-sync.md
Normal file
108
website/versioned_docs/version-1.9.9/wallet/wallet-sync.md
Normal file
@ -0,0 +1,108 @@
|
||||
---
|
||||
title: Wallet Sync
|
||||
id: version-1.9.9-wallet-sync
|
||||
original_id: wallet-sync
|
||||
---
|
||||
|
||||
## High level wallet state
|
||||
|
||||
Our wallet infrastructure has a specific table called `state_descriptors`.
|
||||
This tracks chain state for our wallet.
|
||||
Here is an example of the contents of this table
|
||||
|
||||
>sqlite> select * from state_descriptors;
|
||||
SyncHeight|0000000000000000000134aa9e949ea1d053042b8dfa59bdc73b0322a88f009e 665741
|
||||
|
||||
If you look carefully in the second column, you will see a string encoding indicating
|
||||
what the wallet state is. In this case, the last block hash seen by the wallet is
|
||||
|
||||
>0000000000000000000134aa9e949ea1d053042b8dfa59bdc73b0322a88f009e
|
||||
|
||||
and height
|
||||
|
||||
>665741
|
||||
|
||||
If you have access to a wallet, you can call
|
||||
|
||||
[`wallet.getSyncDescriptorOpt`](https://github.com/bitcoin-s/bitcoin-s/blob/36b5fc142715f8ab3ad053465d53dc29ab319790/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala#L160) to get access to this information
|
||||
|
||||
#### Wallet state from the cli
|
||||
|
||||
Alternatively, you can retrieve this information with `bitcoin-s-cli`
|
||||
|
||||
```
|
||||
./bitcoin-s-cli walletinfo
|
||||
{
|
||||
"wallet": {
|
||||
"keymanager": {
|
||||
"rootXpub": "..."
|
||||
},
|
||||
"xpub": "...",
|
||||
"hdPath": "...",
|
||||
"height": 1906239,
|
||||
"blockHash": "00000000dcf1066b8cd764a6104a9b5e95a55cd31adf9107974b2581ac90fdb9"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Syncing a wallet
|
||||
|
||||
Bitcoin-s provides a utility object called [`WalletSync`](https://github.com/bitcoin-s/bitcoin-s/blob/f3e81d027dfdda79e26642d5c29d381874ee72da/wallet/src/main/scala/org/bitcoins/wallet/sync/WalletSync.scala#L10)
|
||||
that provides useful utilities for syncing a bitcoin-s wallet.
|
||||
|
||||
### Syncing wallet for with access to full blocks
|
||||
|
||||
Inside of `WalletSync` we have a method called [`WalletSync.syncFullBlocks`](https://github.com/bitcoin-s/bitcoin-s/blob/f3e81d027dfdda79e26642d5c29d381874ee72da/wallet/src/main/scala/org/bitcoins/wallet/sync/WalletSync.scala#L18)
|
||||
This method takes 4 parameters
|
||||
|
||||
- a [Wallet](https://github.com/bitcoin-s/bitcoin-s/blob/36b5fc142715f8ab3ad053465d53dc29ab319790/wallet/src/main/scala/org/bitcoins/wallet/Wallet.scala#L46) to sync
|
||||
- `getBlockHeaderFunc` is a function to retrieve a block header based on a blockHash
|
||||
- `getBestBlockHashFunc` is a function to retrieve the best block hash for our blockchain
|
||||
- `getBlockFunc` is a function to retrieve a full [`Block`](https://github.com/bitcoin-s/bitcoin-s/blob/8a148357d560a40bf21e7c0e3f4074cd276534fe/core/src/main/scala/org/bitcoins/core/protocol/blockchain/Block.scala#L18) that corresponds to a block hash
|
||||
|
||||
Given these for things, we can use [`WalletSync.syncFullBlocks`](https://github.com/bitcoin-s/bitcoin-s/blob/f3e81d027dfdda79e26642d5c29d381874ee72da/wallet/src/main/scala/org/bitcoins/wallet/sync/WalletSync.scala#L18) to sync our entire wallet.
|
||||
|
||||
Here is a code example
|
||||
|
||||
|
||||
```scala
|
||||
implicit val system: ActorSystem = ActorSystem(s"wallet-sync-example")
|
||||
implicit val ec: ExecutionContext = system.dispatcher
|
||||
|
||||
// this reads authentication credentials and
|
||||
// connection details from the default data
|
||||
// directory on your platform
|
||||
val client = BitcoindRpcClient.fromDatadir(binary=new File("/path/to/bitcoind"), datadir=new File("/path/to/bitcoind-datadir"))
|
||||
|
||||
//yay! Now we have a started bitcoind.
|
||||
//We will use this as our datasource for syncing our wallet
|
||||
val bitcoindRpcClientF: Future[BitcoindRpcClient] = client.start()
|
||||
|
||||
|
||||
//wait for bitcoind to get started
|
||||
val bitcoind = Await.result(bitcoindRpcClientF, 10.seconds)
|
||||
|
||||
val getBestBlockHashFunc = () => bitcoind.getBestBlockHash
|
||||
|
||||
val getBlockHeaderFunc = { (hash: DoubleSha256DigestBE) => bitcoind.getBlockHeaderRaw(hash) }
|
||||
|
||||
val getBlockFunc = { (hash: DoubleSha256DigestBE) => bitcoind.getBlockRaw(hash) }
|
||||
|
||||
val genesisHashBEF = bitcoind.getBlockHash(0)
|
||||
|
||||
//yay! We are now all setup. Using our 3 functions above and a wallet, we can now sync
|
||||
//a fresh wallet
|
||||
implicit val walletAppConfig: WalletAppConfig = WalletAppConfig.fromDefaultDatadir()
|
||||
|
||||
val feeRateProvider: FeeRateApi = MempoolSpaceProvider.fromBlockTarget(6, proxyParams = None)
|
||||
val wallet = Wallet(bitcoind, bitcoind, feeRateProvider)
|
||||
|
||||
//yay! we have a synced wallet
|
||||
val syncedWalletF = genesisHashBEF.flatMap { genesisHash =>
|
||||
WalletSync.syncFullBlocks(wallet,
|
||||
getBlockHeaderFunc,
|
||||
getBestBlockHashFunc,
|
||||
getBlockFunc,
|
||||
genesisHash)
|
||||
}
|
||||
```
|
141
website/versioned_docs/version-1.9.9/wallet/wallet.md
Normal file
141
website/versioned_docs/version-1.9.9/wallet/wallet.md
Normal file
@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Wallet
|
||||
id: version-1.9.9-wallet
|
||||
original_id: wallet
|
||||
---
|
||||
|
||||
## Bitcoin-s wallet
|
||||
Bitcoin-s comes bundled with a rudimentary Bitcoin wallet. This wallet
|
||||
is capable of managing private keys, generating addresses, constructing
|
||||
and signing transactions, among other things. It is BIP32/BIP44/BIP49/BIP84
|
||||
compatible.
|
||||
|
||||
This wallet is currently only released as a library, and not as a binary.
|
||||
This is because it (nor the documentation) is not deemed production
|
||||
ready. Use at your own risk, and without too much money depending on it.
|
||||
|
||||
### How is the bitcoin-s wallet implemented
|
||||
|
||||
The bitcoin-s wallet is a scalable way for individuals up to large bitcoin exchanges to safely and securely store their bitcoin in a scalable way.
|
||||
|
||||
All key interactions are delegated to the [key-manager](../key-manager/key-manager.md) which is a minimal dependency library to store and use key material.
|
||||
|
||||
By default, we store the encrypted root key in `$HOME/.bitcoin-s/seeds/encrypted-bitcoin-s-seed.json`. This is the seed that is used for each of the wallets on each bitcoin network.
|
||||
Multiple wallet seeds can be saved using the `bitcoin-s.wallet.walletName` config option.
|
||||
You can read more in the [key manager docs](../key-manager/server-key-manager.md).
|
||||
|
||||
The wallet itself is used to manage the utxo life cycle, create transactions, and update wallet balances to show how much money you have the on a bitcoin network.
|
||||
|
||||
We use [slick](https://scala-slick.org/doc/3.3.1/) as middleware to support different database types. Depending on your use case, you can use something as simple as sqlite, or something much more scalable like postgres.
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
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
|
||||
implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.global
|
||||
implicit val system: ActorSystem = ActorSystem("System")
|
||||
|
||||
val config = ConfigFactory.parseString {
|
||||
"""
|
||||
| bitcoin-s {
|
||||
| network = regtest
|
||||
| }
|
||||
""".stripMargin
|
||||
}
|
||||
|
||||
|
||||
val datadir = Files.createTempDirectory("bitcoin-s-wallet")
|
||||
|
||||
|
||||
implicit val walletConfig: WalletAppConfig = WalletAppConfig(datadir, Vector(config))
|
||||
|
||||
// we also need to store chain state for syncing purposes
|
||||
implicit val chainConfig: ChainAppConfig = ChainAppConfig(datadir, Vector(config))
|
||||
|
||||
// when this future completes, we have
|
||||
// created the necessary directories and
|
||||
// databases for managing both chain state
|
||||
// and wallet state
|
||||
val configF: Future[Unit] = for {
|
||||
_ <- walletConfig.start()
|
||||
_ <- chainConfig.start()
|
||||
} yield ()
|
||||
|
||||
val bitcoindInstance = BitcoindInstanceLocal.fromDatadir()
|
||||
|
||||
val bitcoind = BitcoindRpcClient(bitcoindInstance)
|
||||
|
||||
// when this future completes, we have
|
||||
// synced our chain handler to our bitcoind
|
||||
// peer
|
||||
val syncF: Future[ChainApi] = configF.flatMap { _ =>
|
||||
val getBestBlockHashFunc = { () =>
|
||||
bitcoind.getBestBlockHash
|
||||
}
|
||||
|
||||
|
||||
val getBlockHeaderFunc = { (hash: DoubleSha256DigestBE) =>
|
||||
bitcoind.getBlockHeader(hash).map(_.blockHeader)
|
||||
}
|
||||
|
||||
val blockHeaderDAO = BlockHeaderDAO()
|
||||
val compactFilterHeaderDAO = CompactFilterHeaderDAO()
|
||||
val compactFilterDAO = CompactFilterDAO()
|
||||
val stateDAO = ChainStateDescriptorDAO()
|
||||
val chainHandler = ChainHandler(
|
||||
blockHeaderDAO,
|
||||
compactFilterHeaderDAO,
|
||||
compactFilterDAO,
|
||||
stateDAO,
|
||||
blockFilterCheckpoints = Map.empty)
|
||||
|
||||
ChainSync.sync(chainHandler, getBlockHeaderFunc, getBestBlockHashFunc)
|
||||
}
|
||||
|
||||
// once this future completes, we have a initialized
|
||||
// wallet
|
||||
val wallet = Wallet(new NodeApi {
|
||||
override def broadcastTransactions(txs: Vector[Transaction]): Future[Unit] = Future.successful(())
|
||||
override def downloadBlocks(blockHashes: Vector[DoubleSha256DigestBE]): Future[Unit] = Future.successful(())
|
||||
override def getConnectionCount: Future[Int] = Future.successful(0)
|
||||
}, chainApi, ConstantFeeRateProvider(SatoshisPerVirtualByte.one))
|
||||
val walletF: Future[WalletApi] = configF.flatMap { _ =>
|
||||
Wallet.initialize(wallet, None)
|
||||
}
|
||||
|
||||
// when this future completes, ww have sent a transaction
|
||||
// from bitcoind to the Bitcoin-S wallet
|
||||
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, 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, blockhash) <- transactionF
|
||||
_ <- wallet.processTransaction(tx, blockhash)
|
||||
balance <- wallet.getBalance()
|
||||
} yield balance
|
||||
|
||||
balanceF.foreach { balance =>
|
||||
println(s"Bitcoin-S wallet balance: $balance")
|
||||
}
|
||||
```
|
@ -1,4 +1,5 @@
|
||||
[
|
||||
"1.9.9",
|
||||
"1.9.8",
|
||||
"1.9.7",
|
||||
"1.9.6",
|
||||
|
Loading…
Reference in New Issue
Block a user