mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-19 05:43:51 +01:00
Watch arbitrary SPKs (#1860)
* Watch arbitrary SPKs * fix unit tests * fix unit tests * one more fix * revert the compiler parameters
This commit is contained in:
parent
113f97946c
commit
2f8dcd1e57
@ -1,8 +1,11 @@
|
||||
package org.bitcoins.node
|
||||
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol.script.MultiSignatureScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
|
||||
import org.bitcoins.core.util.EnvUtil
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerByte
|
||||
import org.bitcoins.crypto.ECPublicKey
|
||||
import org.bitcoins.rpc.client.common.BitcoindVersion
|
||||
import org.bitcoins.rpc.util.AsyncUtil
|
||||
import org.bitcoins.server.BitcoinSAppConfig
|
||||
@ -14,7 +17,7 @@ import org.bitcoins.testkit.node.{
|
||||
NodeUnitTest
|
||||
}
|
||||
import org.bitcoins.testkit.wallet.BitcoinSWalletTest
|
||||
import org.bitcoins.wallet.Wallet
|
||||
import org.bitcoins.wallet.{OnTransactionProcessed, Wallet, WalletCallbacks}
|
||||
import org.scalatest.FutureOutcome
|
||||
|
||||
import scala.concurrent.{Future, Promise}
|
||||
@ -36,14 +39,19 @@ class NeutrinoNodeWithWalletTest extends NodeUnitTest {
|
||||
} else {
|
||||
withNeutrinoNodeFundedWalletBitcoind(
|
||||
test = test,
|
||||
callbacks = callbacks,
|
||||
nodeCallbacks = nodeCallbacks,
|
||||
walletCallbacks = walletCallbacks,
|
||||
bip39PasswordOpt = getBIP39PasswordOpt(),
|
||||
versionOpt = Some(BitcoindVersion.Experimental))
|
||||
versionOpt = Some(BitcoindVersion.Experimental)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// unlike other mutable collection types java.util.Vector is thread safe
|
||||
private var txs = new java.util.Vector[Transaction]()
|
||||
private var walletP: Promise[Wallet] = Promise()
|
||||
private var walletF: Future[Wallet] = walletP.future
|
||||
|
||||
after {
|
||||
//reset assertion after a test runs, because we
|
||||
//are doing mutation to work around our callback
|
||||
@ -51,13 +59,24 @@ class NeutrinoNodeWithWalletTest extends NodeUnitTest {
|
||||
//after a NeutrinoNode is constructed :-(
|
||||
walletP = Promise()
|
||||
walletF = walletP.future
|
||||
txs = new java.util.Vector[Transaction]()
|
||||
}
|
||||
|
||||
val TestAmount = 1.bitcoin
|
||||
val FeeRate = SatoshisPerByte(10.sats)
|
||||
val TestFees = 2240.sats
|
||||
|
||||
def callbacks: NodeCallbacks = {
|
||||
def walletCallbacks: WalletCallbacks = {
|
||||
val onTxProcessed: OnTransactionProcessed = { tx =>
|
||||
Future {
|
||||
txs.add(tx)
|
||||
()
|
||||
}
|
||||
}
|
||||
WalletCallbacks(onTransactionProcessed = Vector(onTxProcessed))
|
||||
}
|
||||
|
||||
def nodeCallbacks: NodeCallbacks = {
|
||||
val onBlock: OnBlockReceived = { block =>
|
||||
for {
|
||||
wallet <- walletF
|
||||
@ -153,6 +172,51 @@ class NeutrinoNodeWithWalletTest extends NodeUnitTest {
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
it must "watch an arbitrary SPKs" taggedAs UsesExperimentalBitcoind in {
|
||||
param =>
|
||||
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
|
||||
|
||||
walletP.success(wallet)
|
||||
|
||||
def generateBlock() =
|
||||
for {
|
||||
_ <-
|
||||
bitcoind.getNewAddress
|
||||
.flatMap(bitcoind.generateToAddress(1, _))
|
||||
_ <- NodeTestUtil.awaitSync(node, bitcoind)
|
||||
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
|
||||
} yield ()
|
||||
|
||||
val pk1 = ECPublicKey.freshPublicKey
|
||||
val pk2 = ECPublicKey.freshPublicKey
|
||||
val spk = MultiSignatureScriptPubKey(2, Vector(pk1, pk2))
|
||||
val sats = TestAmount
|
||||
val output = TransactionOutput(sats, spk)
|
||||
|
||||
for {
|
||||
_ <- node.sync()
|
||||
_ <- NodeTestUtil.awaitSync(node, bitcoind)
|
||||
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
|
||||
|
||||
// start watching
|
||||
_ <- wallet.watchScriptPubKey(spk)
|
||||
|
||||
// send
|
||||
txSent <- wallet.sendToOutputs(Vector(output), FeeRate)
|
||||
_ <- node.broadcastTransaction(txSent)
|
||||
|
||||
// confirm
|
||||
_ <- generateBlock()
|
||||
_ <- generateBlock()
|
||||
_ <- generateBlock()
|
||||
|
||||
// verify
|
||||
_ <- AsyncUtil.awaitConditionF { () =>
|
||||
Future { txs.contains(txSent) }
|
||||
}
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
it must "rescan and receive information about received payments" taggedAs UsesExperimentalBitcoind in {
|
||||
param =>
|
||||
val NeutrinoNodeFundedWalletBitcoind(node, wallet, bitcoind, _) = param
|
||||
|
@ -55,6 +55,7 @@ class SpvNodeWithWalletTest extends NodeUnitTest {
|
||||
assertionP.success(result)
|
||||
}
|
||||
}
|
||||
()
|
||||
}
|
||||
}
|
||||
NodeCallbacks(
|
||||
|
@ -7,6 +7,7 @@ import sbt.Keys._
|
||||
import scala.util.Properties
|
||||
|
||||
object CommonSettings {
|
||||
|
||||
private val isCI = {
|
||||
sys.props
|
||||
.get("CI")
|
||||
@ -38,21 +39,18 @@ object CommonSettings {
|
||||
apiURL := homepage.value.map(_.toString + "/api").map(url(_)),
|
||||
// scaladoc settings end
|
||||
////
|
||||
|
||||
scalacOptions in Compile := compilerOpts(scalaVersion.value),
|
||||
//remove annoying import unused things in the scala console
|
||||
//https://stackoverflow.com/questions/26940253/in-sbt-how-do-you-override-scalacoptions-for-console-in-all-configurations
|
||||
scalacOptions in (Compile, console) ~= (_ filterNot (s =>
|
||||
s == "-Ywarn-unused-import"
|
||||
|| s =="-Ywarn-unused"
|
||||
|| s =="-Xfatal-warnings"
|
||||
//for 2.13 -- they use different compiler opts
|
||||
|| s == "-Ywarn-unused"
|
||||
|| s == "-Xfatal-warnings"
|
||||
//for 2.13 -- they use different compiler opts
|
||||
|| s == "-Xlint:unused")),
|
||||
|
||||
//we don't want -Xfatal-warnings for publishing with publish/publishLocal either
|
||||
scalacOptions in (Compile,doc) ~= (_ filterNot (s =>
|
||||
scalacOptions in (Compile, doc) ~= (_ filterNot (s =>
|
||||
s == "-Xfatal-warnings")),
|
||||
|
||||
scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value,
|
||||
scalacOptions in Test := testCompilerOpts,
|
||||
Compile / compile / javacOptions ++= {
|
||||
@ -77,7 +75,7 @@ object CommonSettings {
|
||||
)
|
||||
}
|
||||
|
||||
private val scala2_13CompilerOpts = Seq("-Xlint:unused","-Xfatal-warnings")
|
||||
private val scala2_13CompilerOpts = Seq("-Xlint:unused", "-Xfatal-warnings")
|
||||
|
||||
private val nonScala2_13CompilerOpts = Seq(
|
||||
"-Xmax-classfile-name",
|
||||
@ -123,5 +121,6 @@ object CommonSettings {
|
||||
|
||||
lazy val prodSettings: Seq[Setting[_]] = settings
|
||||
|
||||
lazy val binariesPath = Paths.get(Properties.userHome, ".bitcoin-s", "binaries")
|
||||
lazy val binariesPath =
|
||||
Paths.get(Properties.userHome, ".bitcoin-s", "binaries")
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import org.bitcoins.testkit.node.fixture.{
|
||||
}
|
||||
import org.bitcoins.testkit.rpc.BitcoindRpcTestUtil
|
||||
import org.bitcoins.testkit.wallet.{BitcoinSWalletTest, WalletWithBitcoindRpc}
|
||||
import org.bitcoins.wallet.WalletCallbacks
|
||||
import org.scalatest.FutureOutcome
|
||||
|
||||
import scala.concurrent.duration._
|
||||
@ -250,11 +251,13 @@ trait NodeUnitTest extends BitcoinSFixture with EmbeddedPg {
|
||||
|
||||
makeDependentFixture(
|
||||
build = () =>
|
||||
NodeUnitTest.createSpvNodeFundedWalletBitcoind(callbacks = callbacks,
|
||||
NodeUnitTest.createSpvNodeFundedWalletBitcoind(nodeCallbacks =
|
||||
callbacks,
|
||||
bip39PasswordOpt =
|
||||
bip39PasswordOpt,
|
||||
versionOpt =
|
||||
Option(V18))(
|
||||
versionOpt = Option(V18),
|
||||
walletCallbacks =
|
||||
WalletCallbacks.empty)(
|
||||
system, // Force V18 because Spv is disabled on versions after
|
||||
appConfig),
|
||||
destroy = NodeUnitTest.destroyNodeFundedWalletBitcoind(
|
||||
@ -264,9 +267,10 @@ trait NodeUnitTest extends BitcoinSFixture with EmbeddedPg {
|
||||
|
||||
def withNeutrinoNodeFundedWalletBitcoind(
|
||||
test: OneArgAsyncTest,
|
||||
callbacks: NodeCallbacks,
|
||||
nodeCallbacks: NodeCallbacks,
|
||||
bip39PasswordOpt: Option[String],
|
||||
versionOpt: Option[BitcoindVersion] = None)(implicit
|
||||
versionOpt: Option[BitcoindVersion] = None,
|
||||
walletCallbacks: WalletCallbacks = WalletCallbacks.empty)(implicit
|
||||
system: ActorSystem,
|
||||
appConfig: BitcoinSAppConfig): FutureOutcome = {
|
||||
|
||||
@ -274,9 +278,10 @@ trait NodeUnitTest extends BitcoinSFixture with EmbeddedPg {
|
||||
build = () =>
|
||||
NodeUnitTest
|
||||
.createNeutrinoNodeFundedWalletBitcoind(
|
||||
callbacks,
|
||||
nodeCallbacks,
|
||||
bip39PasswordOpt,
|
||||
versionOpt)(system, appConfig),
|
||||
versionOpt,
|
||||
walletCallbacks)(system, appConfig),
|
||||
destroy = NodeUnitTest.destroyNodeFundedWalletBitcoind(
|
||||
_: NodeFundedWalletBitcoind)(system, appConfig)
|
||||
)(test)
|
||||
@ -372,7 +377,8 @@ object NodeUnitTest extends P2PLogger {
|
||||
|
||||
/** Creates a spv node, a funded bitcoin-s wallet, all of which are connected to bitcoind */
|
||||
def createSpvNodeFundedWalletBitcoind(
|
||||
callbacks: NodeCallbacks,
|
||||
nodeCallbacks: NodeCallbacks,
|
||||
walletCallbacks: WalletCallbacks,
|
||||
bip39PasswordOpt: Option[String],
|
||||
versionOpt: Option[BitcoindVersion] = None)(implicit
|
||||
system: ActorSystem,
|
||||
@ -381,12 +387,13 @@ object NodeUnitTest extends P2PLogger {
|
||||
require(appConfig.isSPVEnabled && !appConfig.isNeutrinoEnabled)
|
||||
for {
|
||||
bitcoind <- BitcoinSFixture.createBitcoindWithFunds(versionOpt)
|
||||
node <- createSpvNode(bitcoind, callbacks)
|
||||
node <- createSpvNode(bitcoind, nodeCallbacks)
|
||||
fundedWallet <- BitcoinSWalletTest.fundedWalletAndBitcoind(
|
||||
bitcoind,
|
||||
node,
|
||||
node,
|
||||
bip39PasswordOpt)
|
||||
bip39PasswordOpt,
|
||||
walletCallbacks)
|
||||
} yield {
|
||||
SpvNodeFundedWalletBitcoind(node = node,
|
||||
wallet = fundedWallet.wallet,
|
||||
@ -397,9 +404,10 @@ object NodeUnitTest extends P2PLogger {
|
||||
|
||||
/** Creates a neutrino node, a funded bitcoin-s wallet, all of which are connected to bitcoind */
|
||||
def createNeutrinoNodeFundedWalletBitcoind(
|
||||
callbacks: NodeCallbacks,
|
||||
nodeCallbacks: NodeCallbacks,
|
||||
bip39PasswordOpt: Option[String],
|
||||
versionOpt: Option[BitcoindVersion])(implicit
|
||||
versionOpt: Option[BitcoindVersion],
|
||||
walletCallbacks: WalletCallbacks)(implicit
|
||||
system: ActorSystem,
|
||||
appConfig: BitcoinSAppConfig): Future[
|
||||
NeutrinoNodeFundedWalletBitcoind] = {
|
||||
@ -407,12 +415,13 @@ object NodeUnitTest extends P2PLogger {
|
||||
require(appConfig.isNeutrinoEnabled && !appConfig.isSPVEnabled)
|
||||
for {
|
||||
bitcoind <- BitcoinSFixture.createBitcoindWithFunds(versionOpt)
|
||||
node <- createNeutrinoNode(bitcoind, callbacks)
|
||||
node <- createNeutrinoNode(bitcoind, nodeCallbacks)
|
||||
fundedWallet <- BitcoinSWalletTest.fundedWalletAndBitcoind(
|
||||
bitcoindRpcClient = bitcoind,
|
||||
nodeApi = node,
|
||||
chainQueryApi = node,
|
||||
bip39PasswordOpt = bip39PasswordOpt)
|
||||
bip39PasswordOpt = bip39PasswordOpt,
|
||||
walletCallbacks = walletCallbacks)
|
||||
} yield {
|
||||
NeutrinoNodeFundedWalletBitcoind(node = node,
|
||||
wallet = fundedWallet.wallet,
|
||||
|
@ -26,7 +26,7 @@ import org.bitcoins.testkit.util.FileUtil
|
||||
import org.bitcoins.testkit.wallet.FundWalletUtil.FundedWallet
|
||||
import org.bitcoins.testkit.{BitcoinSTestAppConfig, EmbeddedPg}
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.bitcoins.wallet.{Wallet, WalletLogger}
|
||||
import org.bitcoins.wallet.{Wallet, WalletCallbacks, WalletLogger}
|
||||
import org.scalatest._
|
||||
|
||||
import scala.concurrent.{
|
||||
@ -564,10 +564,12 @@ object BitcoinSWalletTest extends WalletLogger {
|
||||
versionOpt: Option[BitcoindVersion],
|
||||
nodeApi: NodeApi,
|
||||
bip39PasswordOpt: Option[String],
|
||||
chainQueryApi: ChainQueryApi)(implicit
|
||||
chainQueryApi: ChainQueryApi,
|
||||
walletCallbacks: WalletCallbacks)(implicit
|
||||
config: BitcoinSAppConfig,
|
||||
system: ActorSystem): Future[WalletWithBitcoind] = {
|
||||
import system.dispatcher
|
||||
config.walletConf.addCallbacks(walletCallbacks)
|
||||
for {
|
||||
wallet <- BitcoinSWalletTest.createWallet2Accounts(nodeApi,
|
||||
chainQueryApi,
|
||||
@ -581,10 +583,12 @@ object BitcoinSWalletTest extends WalletLogger {
|
||||
bitcoindRpcClient: BitcoindRpcClient,
|
||||
nodeApi: NodeApi,
|
||||
chainQueryApi: ChainQueryApi,
|
||||
bip39PasswordOpt: Option[String])(implicit
|
||||
bip39PasswordOpt: Option[String],
|
||||
walletCallbacks: WalletCallbacks)(implicit
|
||||
config: BitcoinSAppConfig,
|
||||
system: ActorSystem): Future[WalletWithBitcoind] = {
|
||||
import system.dispatcher
|
||||
config.walletConf.addCallbacks(walletCallbacks)
|
||||
for {
|
||||
wallet <- BitcoinSWalletTest.createWallet2Accounts(
|
||||
nodeApi = nodeApi,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.bitcoins.wallet
|
||||
|
||||
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
||||
import org.bitcoins.core.protocol.script.EmptyScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.TransactionOutput
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import org.bitcoins.core.wallet.utxo.StorageLocationTag.HotStorage
|
||||
@ -244,4 +245,20 @@ class AddressHandlingTest extends BitcoinSWalletTest {
|
||||
assert(hotStorageTags.head.address == addr)
|
||||
}
|
||||
}
|
||||
|
||||
it must "add public key scripts to watch" in { fundedWallet: FundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
val spk = EmptyScriptPubKey
|
||||
for {
|
||||
before <- wallet.listScriptPubKeys()
|
||||
spkDb <- wallet.watchScriptPubKey(spk)
|
||||
after <- wallet.listScriptPubKeys()
|
||||
} yield {
|
||||
assert(before.size + 1 == after.size)
|
||||
assert(spkDb.scriptPubKey == spk)
|
||||
assert(spkDb.id.nonEmpty)
|
||||
assert(!before.contains(spkDb))
|
||||
assert(after.contains(spkDb))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,11 +140,9 @@ abstract class Wallet
|
||||
Wallet] = {
|
||||
for {
|
||||
utxos <- listUtxos()
|
||||
addresses <- listAddresses()
|
||||
scripts <- listScriptPubKeys()
|
||||
scriptPubKeys =
|
||||
utxos.flatMap(_.redeemScriptOpt).toSet ++ addresses
|
||||
.map(_.scriptPubKey)
|
||||
.toSet
|
||||
utxos.flatMap(_.redeemScriptOpt).toSet ++ scripts.map(_.scriptPubKey)
|
||||
_ <- FutureUtil.sequentially(blockFilters) {
|
||||
case (blockHash, blockFilter) =>
|
||||
val matcher = SimpleFilterMatcher(blockFilter)
|
||||
|
@ -10,6 +10,7 @@ import org.bitcoins.core.config.NetworkParameters
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.hd.AddressType
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
Transaction,
|
||||
TransactionOutPoint,
|
||||
@ -24,6 +25,7 @@ import org.bitcoins.wallet.WalletLogger
|
||||
import org.bitcoins.wallet.models.{
|
||||
AddressDb,
|
||||
AddressTagDb,
|
||||
ScriptPubKeyDb,
|
||||
SpendingInfoDb,
|
||||
TransactionDb
|
||||
}
|
||||
@ -129,6 +131,10 @@ trait WalletApi extends WalletLogger {
|
||||
|
||||
def listUnusedAddresses(): Future[Vector[AddressDb]]
|
||||
|
||||
def listScriptPubKeys(): Future[Vector[ScriptPubKeyDb]]
|
||||
|
||||
def watchScriptPubKey(scriptPubKey: ScriptPubKey): Future[ScriptPubKeyDb]
|
||||
|
||||
def markUTXOsAsReserved(
|
||||
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]]
|
||||
|
||||
|
@ -20,7 +20,8 @@ import org.bitcoins.wallet.models.{
|
||||
AccountDb,
|
||||
AddressDb,
|
||||
AddressDbHelper,
|
||||
AddressTagDb
|
||||
AddressTagDb,
|
||||
ScriptPubKeyDb
|
||||
}
|
||||
|
||||
import scala.concurrent.{Await, Future, Promise, TimeoutException}
|
||||
@ -108,6 +109,13 @@ private[wallet] trait AddressHandling extends WalletLogger {
|
||||
}
|
||||
}
|
||||
|
||||
override def listScriptPubKeys(): Future[Vector[ScriptPubKeyDb]] =
|
||||
scriptPubKeyDAO.findAll()
|
||||
|
||||
override def watchScriptPubKey(
|
||||
scriptPubKey: ScriptPubKey): Future[ScriptPubKeyDb] =
|
||||
scriptPubKeyDAO.create(ScriptPubKeyDb(scriptPubKey = scriptPubKey))
|
||||
|
||||
/** Enumerates the public keys in this wallet */
|
||||
protected[wallet] def listPubkeys(): Future[Vector[ECPublicKey]] =
|
||||
addressDAO.findAllPubkeys()
|
||||
|
Loading…
Reference in New Issue
Block a user