mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-19 21:45:36 +01:00
Merge pull request #1351 from benthecarman/sendrawtx
This commit is contained in:
commit
220080ff02
11 changed files with 100 additions and 5 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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]].
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(_ => ())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Add table
Reference in a new issue