mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
Add WalletZmqSubscribers (#5423)
Move connection logic into ZMQSubscriber.start() rather than SubscriberRunnable
This commit is contained in:
parent
dae6ebf9aa
commit
cb26f6de07
5 changed files with 105 additions and 60 deletions
|
@ -221,13 +221,15 @@ object BitcoindRpcBackendUtil extends Logging {
|
||||||
|
|
||||||
def startZMQWalletCallbacks(
|
def startZMQWalletCallbacks(
|
||||||
wallet: NeutrinoHDWalletApi,
|
wallet: NeutrinoHDWalletApi,
|
||||||
zmqConfig: ZmqConfig)(implicit ec: ExecutionContext): Unit = {
|
zmqConfig: ZmqConfig)(implicit
|
||||||
|
ec: ExecutionContext): WalletZmqSubscribers = {
|
||||||
require(zmqConfig != ZmqConfig.empty,
|
require(zmqConfig != ZmqConfig.empty,
|
||||||
"Must have the zmq raw configs defined to setup ZMQ callbacks")
|
"Must have the zmq raw configs defined to setup ZMQ callbacks")
|
||||||
|
|
||||||
zmqConfig.rawTx.foreach { zmq =>
|
val rawTxSub = zmqConfig.rawTx.map { zmq =>
|
||||||
val rawTxListener: Option[Transaction => Unit] = Some {
|
val rawTxListener: Option[Transaction => Unit] = Some {
|
||||||
{ tx: Transaction =>
|
{ tx: Transaction =>
|
||||||
|
println(s"Received tx ${tx.txIdBE.hex}, processing")
|
||||||
logger.debug(s"Received tx ${tx.txIdBE.hex}, processing")
|
logger.debug(s"Received tx ${tx.txIdBE.hex}, processing")
|
||||||
val f = wallet.processTransaction(tx, None)
|
val f = wallet.processTransaction(tx, None)
|
||||||
f.failed.foreach { err =>
|
f.failed.foreach { err =>
|
||||||
|
@ -241,14 +243,15 @@ object BitcoindRpcBackendUtil extends Logging {
|
||||||
hashTxListener = None,
|
hashTxListener = None,
|
||||||
hashBlockListener = None,
|
hashBlockListener = None,
|
||||||
rawTxListener = rawTxListener,
|
rawTxListener = rawTxListener,
|
||||||
rawBlockListener = None).start()
|
rawBlockListener = None)
|
||||||
}
|
}
|
||||||
|
|
||||||
zmqConfig.rawBlock.foreach { zmq =>
|
val rawBlockSub = zmqConfig.rawBlock.map { zmq =>
|
||||||
val rawBlockListener: Option[Block => Unit] = Some {
|
val rawBlockListener: Option[Block => Unit] = Some {
|
||||||
{ block: Block =>
|
{ block: Block =>
|
||||||
logger.info(
|
logger.info(
|
||||||
s"Received block ${block.blockHeader.hashBE.hex}, processing")
|
s"Received block ${block.blockHeader.hashBE.hex}, processing")
|
||||||
|
println(s"Received block ${block.blockHeader.hashBE.hex}, processing")
|
||||||
val f = wallet.processBlock(block)
|
val f = wallet.processBlock(block)
|
||||||
f.failed.foreach { err =>
|
f.failed.foreach { err =>
|
||||||
logger.error("failed to process raw block zmq message", err)
|
logger.error("failed to process raw block zmq message", err)
|
||||||
|
@ -261,8 +264,12 @@ object BitcoindRpcBackendUtil extends Logging {
|
||||||
hashTxListener = None,
|
hashTxListener = None,
|
||||||
hashBlockListener = None,
|
hashBlockListener = None,
|
||||||
rawTxListener = None,
|
rawTxListener = None,
|
||||||
rawBlockListener = rawBlockListener).start()
|
rawBlockListener = rawBlockListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val subs = WalletZmqSubscribers(rawTxSub, rawBlockSub)
|
||||||
|
subs.start()
|
||||||
|
subs
|
||||||
}
|
}
|
||||||
|
|
||||||
def createDLCWalletWithBitcoindCallbacks(
|
def createDLCWalletWithBitcoindCallbacks(
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.bitcoins.server
|
||||||
|
|
||||||
|
import org.bitcoins.core.util.StartStop
|
||||||
|
import org.bitcoins.zmq.ZMQSubscriber
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
case class WalletZmqSubscribers(
|
||||||
|
rawTxSubscriberOpt: Option[ZMQSubscriber],
|
||||||
|
rawBlockSubscriberOpt: Option[ZMQSubscriber])
|
||||||
|
extends StartStop[Unit] {
|
||||||
|
private val isStarted: AtomicBoolean = new AtomicBoolean(false)
|
||||||
|
|
||||||
|
override def start(): Unit = {
|
||||||
|
if (isStarted.get()) {
|
||||||
|
()
|
||||||
|
} else {
|
||||||
|
rawTxSubscriberOpt.foreach(_.start())
|
||||||
|
rawBlockSubscriberOpt.foreach(_.start())
|
||||||
|
isStarted.set(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override def stop(): Unit = {
|
||||||
|
if (isStarted.get()) {
|
||||||
|
rawTxSubscriberOpt.foreach(_.stop())
|
||||||
|
rawBlockSubscriberOpt.foreach(_.stop())
|
||||||
|
isStarted.set(false)
|
||||||
|
} else {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,10 +25,13 @@ abstract class RpcUtil extends AsyncUtil {
|
||||||
/** Genreates a zmq config with unused ports */
|
/** Genreates a zmq config with unused ports */
|
||||||
def zmqConfig: ZmqConfig = {
|
def zmqConfig: ZmqConfig = {
|
||||||
ZmqConfig(
|
ZmqConfig(
|
||||||
hashBlock = Some(new InetSocketAddress(randomPort)),
|
hashBlock =
|
||||||
hashTx = Some(new InetSocketAddress(randomPort)),
|
Some(InetSocketAddress.createUnresolved("127.0.0.1", randomPort)),
|
||||||
rawTx = Some(new InetSocketAddress(randomPort)),
|
hashTx =
|
||||||
rawBlock = Some(new InetSocketAddress(randomPort))
|
Some(InetSocketAddress.createUnresolved("127.0.0.1", randomPort)),
|
||||||
|
rawTx = Some(InetSocketAddress.createUnresolved("127.0.0.1", randomPort)),
|
||||||
|
rawBlock =
|
||||||
|
Some(InetSocketAddress.createUnresolved("127.0.0.1", randomPort))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ class BitcoindZMQBackendTest extends WalletAppConfigWithBitcoindNewestFixtures {
|
||||||
_ = assert(firstBalance == Satoshis.zero)
|
_ = assert(firstBalance == Satoshis.zero)
|
||||||
|
|
||||||
// Setup zmq subscribers
|
// Setup zmq subscribers
|
||||||
_ = BitcoindRpcBackendUtil.startZMQWalletCallbacks(
|
zmqSubs = BitcoindRpcBackendUtil.startZMQWalletCallbacks(
|
||||||
wallet,
|
wallet,
|
||||||
bitcoind.instance.zmqConfig)
|
bitcoind.instance.zmqConfig)
|
||||||
_ <- AsyncUtil.nonBlockingSleep(5.seconds)
|
_ <- AsyncUtil.nonBlockingSleep(5.seconds)
|
||||||
|
@ -72,9 +72,10 @@ class BitcoindZMQBackendTest extends WalletAppConfigWithBitcoindNewestFixtures {
|
||||||
confirmed <- wallet.getConfirmedBalance()
|
confirmed <- wallet.getConfirmedBalance()
|
||||||
_ = assert(confirmed == Satoshis.zero)
|
_ = assert(confirmed == Satoshis.zero)
|
||||||
|
|
||||||
_ <- attemptZMQBlock(6, wallet)
|
_ <- attemptZMQBlock(walletAppConfig.requiredConfirmations, wallet)
|
||||||
|
|
||||||
balance <- wallet.getConfirmedBalance()
|
balance <- wallet.getConfirmedBalance()
|
||||||
|
_ = zmqSubs.stop()
|
||||||
} yield {
|
} yield {
|
||||||
// use >= because of multiple attempts
|
// use >= because of multiple attempts
|
||||||
assert(balance >= amountToSend)
|
assert(balance >= amountToSend)
|
||||||
|
|
|
@ -30,7 +30,7 @@ class ZMQSubscriber(
|
||||||
extends Logging
|
extends Logging
|
||||||
with StartStop[Unit] {
|
with StartStop[Unit] {
|
||||||
|
|
||||||
private var running = true
|
private var isConnected = false
|
||||||
private val context = ZMQ.context(1)
|
private val context = ZMQ.context(1)
|
||||||
|
|
||||||
private val subscriber: ZMQ.Socket = context.socket(SocketType.SUB)
|
private val subscriber: ZMQ.Socket = context.socket(SocketType.SUB)
|
||||||
|
@ -40,54 +40,24 @@ class ZMQSubscriber(
|
||||||
private case object SubscriberRunnable extends Runnable {
|
private case object SubscriberRunnable extends Runnable {
|
||||||
|
|
||||||
override def run(): Unit = {
|
override def run(): Unit = {
|
||||||
logger.info(s"ZmqSubscriber connecting to uri=$uri")
|
while (isConnected && !subscriberThread.isInterrupted) {
|
||||||
subscriber.setLinger(2000)
|
try {
|
||||||
val isConnected = subscriber.connect(s"tcp://$uri")
|
val zmsg = ZMsg.recvMsg(subscriber, ZMQ.NOBLOCK)
|
||||||
|
if (zmsg != null) {
|
||||||
if (isConnected) {
|
val notificationTypeStr = zmsg.pop().getString(ZMQ.CHARSET)
|
||||||
hashTxListener.foreach { _ =>
|
val body = zmsg.pop().getData
|
||||||
subscriber.subscribe(HashTx.topic.getBytes(ZMQ.CHARSET))
|
processMsg(notificationTypeStr, body)
|
||||||
logger.debug("subscribed to the transaction hashes from zmq")
|
} else {
|
||||||
}
|
Thread.sleep(1)
|
||||||
|
|
||||||
rawTxListener.foreach { _ =>
|
|
||||||
subscriber.subscribe(RawTx.topic.getBytes(ZMQ.CHARSET))
|
|
||||||
logger.debug("subscribed to raw transactions from zmq")
|
|
||||||
}
|
|
||||||
|
|
||||||
hashBlockListener.foreach { _ =>
|
|
||||||
subscriber.subscribe(HashBlock.topic.getBytes(ZMQ.CHARSET))
|
|
||||||
logger.debug("subscribed to the hashblock stream from zmq")
|
|
||||||
}
|
|
||||||
|
|
||||||
rawBlockListener.foreach { _ =>
|
|
||||||
subscriber.subscribe(RawBlock.topic.getBytes(ZMQ.CHARSET))
|
|
||||||
logger.debug("subscribed to raw block stream from zmq")
|
|
||||||
}
|
|
||||||
|
|
||||||
while (running && !subscriberThread.isInterrupted) {
|
|
||||||
try {
|
|
||||||
val zmsg = ZMsg.recvMsg(subscriber, ZMQ.NOBLOCK)
|
|
||||||
if (zmsg != null) {
|
|
||||||
val notificationTypeStr = zmsg.pop().getString(ZMQ.CHARSET)
|
|
||||||
val body = zmsg.pop().getData
|
|
||||||
processMsg(notificationTypeStr, body)
|
|
||||||
} else {
|
|
||||||
Thread.sleep(100)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
case e: ZMQException if e.getErrorCode == ZMQ.Error.ETERM.getCode =>
|
|
||||||
context.term()
|
|
||||||
logger.info(s"Done terminating zmq context msg=${e.getMessage}")
|
|
||||||
case e: Exception =>
|
|
||||||
context.term()
|
|
||||||
logger.info(s"Done terminating zmq context msg=${e.getMessage}")
|
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
case e: ZMQException if e.getErrorCode == ZMQ.Error.ETERM.getCode =>
|
||||||
|
context.term()
|
||||||
|
logger.info(s"Done terminating zmq context msg=${e.getMessage}")
|
||||||
|
case e: Exception =>
|
||||||
|
context.term()
|
||||||
|
logger.info(s"Done terminating zmq context msg=${e.getMessage}")
|
||||||
}
|
}
|
||||||
logger.info(s"Terminated")
|
|
||||||
} else {
|
|
||||||
logger.error(s"Failed to connect to zmq socket ${uri}")
|
|
||||||
throw new RuntimeException(s"Failed to connect to zmq socket ${uri}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -99,8 +69,38 @@ class ZMQSubscriber(
|
||||||
subscriberThread.setDaemon(true)
|
subscriberThread.setDaemon(true)
|
||||||
|
|
||||||
override def start(): Unit = {
|
override def start(): Unit = {
|
||||||
logger.info("starting zmq")
|
logger.info(s"ZmqSubscriber connecting to uri=$uri")
|
||||||
|
|
||||||
|
isConnected = subscriber.connect(s"tcp://$uri")
|
||||||
subscriberThread.start()
|
subscriberThread.start()
|
||||||
|
if (isConnected) {
|
||||||
|
hashTxListener.foreach { _ =>
|
||||||
|
val result = subscriber.subscribe(HashTx.topic.getBytes(ZMQ.CHARSET))
|
||||||
|
if (result) logger.debug("subscribed to hashtxs stream from zmq")
|
||||||
|
else logger.error(s"Failed to subscribe to ${HashTx.topic}")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawTxListener.foreach { _ =>
|
||||||
|
val result = subscriber.subscribe(RawTx.topic.getBytes(ZMQ.CHARSET))
|
||||||
|
if (result) logger.debug("subscribed to rawtxs stream from zmq")
|
||||||
|
else logger.error(s"Failed to subscribe to ${RawTx.topic}")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashBlockListener.foreach { _ =>
|
||||||
|
val result = subscriber.subscribe(HashBlock.topic.getBytes(ZMQ.CHARSET))
|
||||||
|
if (result) logger.debug("subscribed to hashblock stream from zmq")
|
||||||
|
else logger.error(s"Failed to subscribe to ${HashBlock.topic}")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawBlockListener.foreach { _ =>
|
||||||
|
val result = subscriber.subscribe(RawBlock.topic.getBytes(ZMQ.CHARSET))
|
||||||
|
if (result) logger.debug("subscribed to raw block stream from zmq")
|
||||||
|
else logger.error(s"Failed to subscribe to ${RawBlock.topic}")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.error(s"Failed to connect to zmq socket ${uri}")
|
||||||
|
throw new RuntimeException(s"Failed to connect to zmq socket ${uri}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Stops running the zmq subscriber and cleans up after zmq
|
/** Stops running the zmq subscriber and cleans up after zmq
|
||||||
|
@ -111,7 +111,7 @@ class ZMQSubscriber(
|
||||||
//i think this could technically not work, because currently we are blocking
|
//i think this could technically not work, because currently we are blocking
|
||||||
//on Zmsg.recvMsg in our while loop. If we don't get another message we won't
|
//on Zmsg.recvMsg in our while loop. If we don't get another message we won't
|
||||||
//be able toe evaluate the while loop again. Moving forward with this for now.
|
//be able toe evaluate the while loop again. Moving forward with this for now.
|
||||||
running = false
|
isConnected = false
|
||||||
subscriber.close()
|
subscriber.close()
|
||||||
logger.info("Attempting to terminate context")
|
logger.info("Attempting to terminate context")
|
||||||
context.term()
|
context.term()
|
||||||
|
|
Loading…
Add table
Reference in a new issue