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 3d58c66b79
11 changed files with 100 additions and 5 deletions

View File

@ -208,6 +208,21 @@ object ConsoleCli {
cmd("stop")
.action((_, conf) => conf.copy(command = Stop))
.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 ==="),
cmd("combinepsbts")
.action((_, conf) => conf.copy(command = CombinePSBTs(Seq.empty)))
@ -356,6 +371,8 @@ object ConsoleCli {
// peers
case GetPeers => RequestParam("getpeers")
case Stop => RequestParam("stop")
case SendRawTransaction(tx) =>
RequestParam("sendrawtransaction", Seq(up.writeJs(tx)))
// PSBTs
case CombinePSBTs(psbts) =>
RequestParam("combinepsbts", Seq(up.writeJs(psbts)))
@ -477,6 +494,7 @@ object CliCommand {
// Node
case object GetPeers extends CliCommand
case object Stop extends CliCommand
case class SendRawTransaction(tx: Transaction) extends CliCommand
// Chain
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 {
// positive cases

View File

@ -6,6 +6,8 @@ import akka.http.scaladsl.server._
import akka.stream.ActorMaterializer
import org.bitcoins.node.Node
import scala.util.{Failure, Success}
case class NodeRoutes(node: Node)(implicit system: ActorSystem)
extends ServerRoute {
import system.dispatcher
@ -25,5 +27,17 @@ case class NodeRoutes(node: Node)(implicit system: ActorSystem)
system.terminate()
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])
object CombinePSBTs extends ServerJsonModels {

View File

@ -1,6 +1,7 @@
package org.bitcoins.core.api
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.protocol.transaction.Transaction
import scala.concurrent.Future
@ -9,6 +10,11 @@ import scala.concurrent.Future
*/
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]].
*/

View File

@ -8,6 +8,7 @@ import akka.actor.ActorSystem
import org.bitcoins.core.api.NodeApi
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.keymanager.bip39.BIP39KeyManager
import org.bitcoins.node.NodeCallbacks
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
// but for the examples sake we will keep it small.
val nodeApi = new NodeApi {
override def broadcastTransaction(transaction: Transaction): Future[Unit] = {
bitcoind.sendRawTransaction(transaction).map(_ => ())
}
override def downloadBlocks(
blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = {
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
// wallet
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(())
}, new ChainQueryApi {
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 */
def broadcastTransaction(transaction: Transaction): Future[Unit] = {
override def broadcastTransaction(transaction: Transaction): Future[Unit] = {
val broadcastTx = BroadcastAbleTransaction(transaction)
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, NodeApi, NodeChainQueryApi}
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.blockchain.{Block, BlockHeader}
import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil}
import org.bitcoins.rpc.client.common.BitcoindRpcClient
import org.bitcoins.rpc.client.v19.BitcoindV19RpcClient
import org.bitcoins.commons.jsonmodels.bitcoind.GetBlockFilterResult
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.wallet.Wallet
import scala.concurrent.{ExecutionContext, Future}
@ -117,6 +118,11 @@ abstract class SyncUtil extends BitcoinSLogger {
implicit ec: ExecutionContext): 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]].
*/
@ -153,9 +159,6 @@ abstract class SyncUtil extends BitcoinSLogger {
walletF: Future[Wallet])(implicit ec: ExecutionContext): 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]].
*/
@ -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.gcs.BlockFilter
import org.bitcoins.core.protocol.BlockStamp
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.db.AppConfig
import org.bitcoins.keymanager.bip39.BIP39KeyManager
@ -249,6 +250,9 @@ object BitcoinSWalletTest extends WalletLogger {
object MockNodeApi extends NodeApi {
override def broadcastTransaction(transaction: Transaction): Future[Unit] =
FutureUtil.unit
override def downloadBlocks(
blockHashes: Vector[DoubleSha256Digest]): Future[Unit] = FutureUtil.unit

View File

@ -48,6 +48,9 @@ sealed trait WalletApi {
*/
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
* to get information about our transactions, pubkeys and scripts.