Merge pull request #1351 from benthecarman/sendrawtx

This commit is contained in:
Ben Carman 2020-04-21 17:59:44 -05:00 committed by GitHub
commit 220080ff02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 5 deletions

View file

@ -208,6 +208,21 @@ object ConsoleCli {
cmd("stop") cmd("stop")
.action((_, conf) => conf.copy(command = Stop)) .action((_, conf) => conf.copy(command = Stop))
.text("Request a graceful shutdown of Bitcoin-S"), .text("Request a graceful shutdown of Bitcoin-S"),
cmd("sendrawtransaction")
.action((_, conf) =>
conf.copy(command = SendRawTransaction(EmptyTransaction)))
.text("Broadcasts the raw transaction")
.children(
arg[Transaction]("tx")
.text("Transaction serialized in hex")
.required()
.action((tx, conf) =>
conf.copy(command = conf.command match {
case sendRawTransaction: SendRawTransaction =>
sendRawTransaction.copy(tx = tx)
case other => other
}))
),
note(sys.props("line.separator") + "=== PSBT ==="), note(sys.props("line.separator") + "=== PSBT ==="),
cmd("combinepsbts") cmd("combinepsbts")
.action((_, conf) => conf.copy(command = CombinePSBTs(Seq.empty))) .action((_, conf) => conf.copy(command = CombinePSBTs(Seq.empty)))
@ -356,6 +371,8 @@ object ConsoleCli {
// peers // peers
case GetPeers => RequestParam("getpeers") case GetPeers => RequestParam("getpeers")
case Stop => RequestParam("stop") case Stop => RequestParam("stop")
case SendRawTransaction(tx) =>
RequestParam("sendrawtransaction", Seq(up.writeJs(tx)))
// PSBTs // PSBTs
case CombinePSBTs(psbts) => case CombinePSBTs(psbts) =>
RequestParam("combinepsbts", Seq(up.writeJs(psbts))) RequestParam("combinepsbts", Seq(up.writeJs(psbts)))
@ -477,6 +494,7 @@ object CliCommand {
// Node // Node
case object GetPeers extends CliCommand case object GetPeers extends CliCommand
case object Stop extends CliCommand case object Stop extends CliCommand
case class SendRawTransaction(tx: Transaction) extends CliCommand
// Chain // Chain
case object GetBestBlockHash extends CliCommand case object GetBestBlockHash extends CliCommand

View file

@ -393,6 +393,26 @@ class RoutesSpec
} }
} }
"send a raw transaction" in {
val tx = Transaction(
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000")
(mockNode
.broadcastTransaction(_: Transaction))
.expects(tx)
.returning(FutureUtil.unit)
.anyNumberOfTimes()
val route =
nodeRoutes.handleCommand(
ServerCommand("sendrawtransaction", Arr(Str(tx.hex))))
Get() ~> route ~> check {
contentType shouldEqual `application/json`
responseAs[String] shouldEqual s"""{"result":"Broadcasted ${tx.txIdBE}","error":null}"""
}
}
"send to an address" in { "send to an address" in {
// positive cases // positive cases

View file

@ -6,6 +6,8 @@ import akka.http.scaladsl.server._
import akka.stream.ActorMaterializer import akka.stream.ActorMaterializer
import org.bitcoins.node.Node import org.bitcoins.node.Node
import scala.util.{Failure, Success}
case class NodeRoutes(node: Node)(implicit system: ActorSystem) case class NodeRoutes(node: Node)(implicit system: ActorSystem)
extends ServerRoute { extends ServerRoute {
import system.dispatcher import system.dispatcher
@ -25,5 +27,17 @@ case class NodeRoutes(node: Node)(implicit system: ActorSystem)
system.terminate() system.terminate()
nodeStopping nodeStopping
} }
case ServerCommand("sendrawtransaction", arr) =>
SendRawTransaction.fromJsArr(arr) match {
case Failure(exception) =>
reject(ValidationRejection("failure", Some(exception)))
case Success(SendRawTransaction(tx)) =>
complete {
node.broadcastTransaction(tx).map { _ =>
Server.httpSuccess(s"Broadcasted ${tx.txIdBE}")
}
}
}
} }
} }

View file

@ -68,6 +68,18 @@ object GetAddressInfo extends ServerJsonModels {
} }
} }
case class SendRawTransaction(tx: Transaction)
object SendRawTransaction extends ServerJsonModels {
def fromJsArr(jsArr: ujson.Arr): Try[SendRawTransaction] = {
require(jsArr.arr.size == 1,
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1")
Try(SendRawTransaction(jsToTx(jsArr.arr.head)))
}
}
case class CombinePSBTs(psbts: Seq[PSBT]) case class CombinePSBTs(psbts: Seq[PSBT])
object CombinePSBTs extends ServerJsonModels { object CombinePSBTs extends ServerJsonModels {

View file

@ -1,6 +1,7 @@
package org.bitcoins.core.api package org.bitcoins.core.api
import org.bitcoins.core.crypto.DoubleSha256Digest import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.protocol.transaction.Transaction
import scala.concurrent.Future import scala.concurrent.Future
@ -9,6 +10,11 @@ import scala.concurrent.Future
*/ */
trait NodeApi { trait NodeApi {
/**
* Broadcasts the given transaction over the P2P network
*/
def broadcastTransaction(transaction: Transaction): Future[Unit]
/** /**
* Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]]. * Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]].
*/ */

View file

@ -8,6 +8,7 @@ import akka.actor.ActorSystem
import org.bitcoins.core.api.NodeApi import org.bitcoins.core.api.NodeApi
import org.bitcoins.core.crypto.DoubleSha256Digest import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.protocol.blockchain.Block import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.keymanager.bip39.BIP39KeyManager import org.bitcoins.keymanager.bip39.BIP39KeyManager
import org.bitcoins.node.NodeCallbacks import org.bitcoins.node.NodeCallbacks
import org.bitcoins.node.networking.peer.DataMessageHandler._ import org.bitcoins.node.networking.peer.DataMessageHandler._
@ -85,6 +86,11 @@ val exampleCallback = createCallback(exampleProcessBlock)
// Here is where we are defining our actual node api, Ideally this could be it's own class // Here is where we are defining our actual node api, Ideally this could be it's own class
// but for the examples sake we will keep it small. // but for the examples sake we will keep it small.
val nodeApi = new NodeApi { val nodeApi = new NodeApi {
override def broadcastTransaction(transaction: Transaction): Future[Unit] = {
bitcoind.sendRawTransaction(transaction).map(_ => ())
}
override def downloadBlocks( override def downloadBlocks(
blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = { blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = {
val blockFs = blockHashes.map(hash => bitcoind.getBlockRaw(hash)) val blockFs = blockHashes.map(hash => bitcoind.getBlockRaw(hash))

View file

@ -141,6 +141,7 @@ val keyManager = BIP39KeyManager.initialize(walletConfig.kmParams, bip39Password
// once this future completes, we have a initialized // once this future completes, we have a initialized
// wallet // wallet
val wallet = Wallet(keyManager, new NodeApi { val wallet = Wallet(keyManager, new NodeApi {
override def broadcastTransaction(tx: Transaction): Future[Unit] = Future.successful(())
override def downloadBlocks(blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = Future.successful(()) override def downloadBlocks(blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = Future.successful(())
}, new ChainQueryApi { }, new ChainQueryApi {
override def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]] = Future.successful(None) override def getBlockHeight(blockHash: DoubleSha256DigestBE): Future[Option[Int]] = Future.successful(None)

View file

@ -189,7 +189,7 @@ trait Node extends NodeApi with ChainQueryApi with P2PLogger {
} }
/** Broadcasts the given transaction over the P2P network */ /** Broadcasts the given transaction over the P2P network */
def broadcastTransaction(transaction: Transaction): Future[Unit] = { override def broadcastTransaction(transaction: Transaction): Future[Unit] = {
val broadcastTx = BroadcastAbleTransaction(transaction) val broadcastTx = BroadcastAbleTransaction(transaction)
txDAO.create(broadcastTx).onComplete { txDAO.create(broadcastTx).onComplete {

View file

@ -4,13 +4,14 @@ import org.bitcoins.chain.blockchain.sync.FilterWithHeaderHash
import org.bitcoins.core.api.ChainQueryApi.FilterResponse import org.bitcoins.core.api.ChainQueryApi.FilterResponse
import org.bitcoins.core.api.{ChainQueryApi, NodeApi, NodeChainQueryApi} import org.bitcoins.core.api.{ChainQueryApi, NodeApi, NodeChainQueryApi}
import org.bitcoins.core.crypto.{DoubleSha256Digest, DoubleSha256DigestBE} import org.bitcoins.core.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.core.gcs.{FilterType} import org.bitcoins.core.gcs.FilterType
import org.bitcoins.core.protocol.BlockStamp import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader} import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader}
import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil} import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil}
import org.bitcoins.rpc.client.common.BitcoindRpcClient import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.commons.jsonmodels.bitcoind.GetBlockFilterResult import org.bitcoins.commons.jsonmodels.bitcoind.GetBlockFilterResult
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.wallet.Wallet import org.bitcoins.wallet.Wallet
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
@ -117,6 +118,11 @@ abstract class SyncUtil extends BitcoinSLogger {
implicit ec: ExecutionContext): NodeApi = { implicit ec: ExecutionContext): NodeApi = {
new NodeApi { new NodeApi {
override def broadcastTransaction(
transaction: Transaction): Future[Unit] = {
bitcoindRpcClient.sendRawTransaction(transaction).map(_ => ())
}
/** /**
* Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]]. * Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]].
*/ */
@ -153,9 +159,6 @@ abstract class SyncUtil extends BitcoinSLogger {
walletF: Future[Wallet])(implicit ec: ExecutionContext): NodeApi = { walletF: Future[Wallet])(implicit ec: ExecutionContext): NodeApi = {
new NodeApi { new NodeApi {
/**
* Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]].
*/
/** /**
* Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]]. * Request the underlying node to download the given blocks from its peers and feed the blocks to [[org.bitcoins.node.NodeCallbacks]].
*/ */
@ -196,6 +199,14 @@ abstract class SyncUtil extends BitcoinSLogger {
() ()
} }
} }
/**
* Broadcasts the given transaction over the P2P network
*/
override def broadcastTransaction(
transaction: Transaction): Future[Unit] = {
bitcoindRpcClient.sendRawTransaction(transaction).map(_ => ())
}
} }
} }

View file

@ -8,6 +8,7 @@ import org.bitcoins.core.crypto.{DoubleSha256Digest, DoubleSha256DigestBE}
import org.bitcoins.core.currency._ import org.bitcoins.core.currency._
import org.bitcoins.core.gcs.BlockFilter import org.bitcoins.core.gcs.BlockFilter
import org.bitcoins.core.protocol.BlockStamp import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.util.FutureUtil import org.bitcoins.core.util.FutureUtil
import org.bitcoins.db.AppConfig import org.bitcoins.db.AppConfig
import org.bitcoins.keymanager.bip39.BIP39KeyManager import org.bitcoins.keymanager.bip39.BIP39KeyManager
@ -249,6 +250,9 @@ object BitcoinSWalletTest extends WalletLogger {
object MockNodeApi extends NodeApi { object MockNodeApi extends NodeApi {
override def broadcastTransaction(transaction: Transaction): Future[Unit] =
FutureUtil.unit
override def downloadBlocks( override def downloadBlocks(
blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = FutureUtil.unit blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = FutureUtil.unit

View file

@ -48,6 +48,9 @@ sealed trait WalletApi {
*/ */
trait LockedWalletApi extends WalletApi with WalletLogger { trait LockedWalletApi extends WalletApi with WalletLogger {
def broadcastTransaction(transaction: Transaction): Future[Unit] =
nodeApi.broadcastTransaction(transaction)
/** /**
* Retrieves a bloom filter that that can be sent to a P2P network node * Retrieves a bloom filter that that can be sent to a P2P network node
* to get information about our transactions, pubkeys and scripts. * to get information about our transactions, pubkeys and scripts.