mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-19 05:43:51 +01:00
commit
d1aa6386f0
@ -8,6 +8,8 @@ import org.bitcoins.cli.CliReaders._
|
||||
import org.bitcoins.core.config.NetworkParameters
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol._
|
||||
import org.bitcoins.core.protocol.transaction.{EmptyTransaction, Transaction}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.picklers._
|
||||
import scopt.OParser
|
||||
import ujson.{Num, Str}
|
||||
@ -44,6 +46,13 @@ object CliCommand {
|
||||
endBlock: Option[BlockStamp],
|
||||
force: Boolean)
|
||||
extends CliCommand
|
||||
|
||||
// PSBT
|
||||
case class CombinePSBTs(psbts: Seq[PSBT]) extends CliCommand
|
||||
case class JoinPSBTs(psbts: Seq[PSBT]) extends CliCommand
|
||||
case class FinalizePSBT(psbt: PSBT) extends CliCommand
|
||||
case class ExtractFromPSBT(psbt: PSBT) extends CliCommand
|
||||
case class ConvertToPSBT(transaction: Transaction) extends CliCommand
|
||||
}
|
||||
|
||||
object Cli extends App {
|
||||
@ -157,6 +166,77 @@ object Cli extends App {
|
||||
.hidden()
|
||||
.action((_, conf) => conf.copy(command = GetPeers))
|
||||
.text(s"List the connected peers"),
|
||||
cmd("combinepsbts")
|
||||
.hidden()
|
||||
.action((_, conf) => conf.copy(command = CombinePSBTs(Seq.empty)))
|
||||
.text("Combines all the given PSBTs")
|
||||
.children(
|
||||
opt[Seq[PSBT]]("psbts")
|
||||
.required()
|
||||
.action((seq, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case combinePSBTs: CombinePSBTs =>
|
||||
combinePSBTs.copy(psbts = seq)
|
||||
case other => other
|
||||
}))
|
||||
),
|
||||
cmd("joinpsbts")
|
||||
.hidden()
|
||||
.action((_, conf) => conf.copy(command = JoinPSBTs(Seq.empty)))
|
||||
.text("Combines all the given PSBTs")
|
||||
.children(
|
||||
opt[Seq[PSBT]]("psbts")
|
||||
.required()
|
||||
.action((seq, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case joinPSBTs: JoinPSBTs =>
|
||||
joinPSBTs.copy(psbts = seq)
|
||||
case other => other
|
||||
}))
|
||||
),
|
||||
cmd("finalizepsbt")
|
||||
.hidden()
|
||||
.action((_, conf) => conf.copy(command = FinalizePSBT(PSBT.empty)))
|
||||
.text("Finalizes the given PSBT if it can")
|
||||
.children(
|
||||
opt[PSBT]("psbt")
|
||||
.required()
|
||||
.action((psbt, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case finalizePSBT: FinalizePSBT =>
|
||||
finalizePSBT.copy(psbt = psbt)
|
||||
case other => other
|
||||
}))
|
||||
),
|
||||
cmd("extractfrompsbt")
|
||||
.hidden()
|
||||
.action((_, conf) => conf.copy(command = ExtractFromPSBT(PSBT.empty)))
|
||||
.text("Extracts a transaction from the given PSBT if it can")
|
||||
.children(
|
||||
opt[PSBT]("psbt")
|
||||
.required()
|
||||
.action((psbt, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case extractFromPSBT: ExtractFromPSBT =>
|
||||
extractFromPSBT.copy(psbt = psbt)
|
||||
case other => other
|
||||
}))
|
||||
),
|
||||
cmd("converttopsbt")
|
||||
.hidden()
|
||||
.action((_, conf) =>
|
||||
conf.copy(command = ConvertToPSBT(EmptyTransaction)))
|
||||
.text("Creates an empty psbt from the given transaction")
|
||||
.children(
|
||||
opt[Transaction]("unsignedTx")
|
||||
.required()
|
||||
.action((tx, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case convertToPSBT: ConvertToPSBT =>
|
||||
convertToPSBT.copy(transaction = tx)
|
||||
case other => other
|
||||
}))
|
||||
),
|
||||
help('h', "help").text("Display this help message and exit"),
|
||||
arg[String]("<cmd>")
|
||||
.optional()
|
||||
@ -228,7 +308,19 @@ object Cli extends App {
|
||||
// besthash
|
||||
case GetBestBlockHash => RequestParam("getbestblockhash")
|
||||
// peers
|
||||
case GetPeers => RequestParam("getpeers")
|
||||
case GetPeers => RequestParam("getpeers")
|
||||
// PSBTs
|
||||
case CombinePSBTs(psbts) =>
|
||||
RequestParam("combinepsbts", Seq(up.writeJs(psbts)))
|
||||
case JoinPSBTs(psbts) =>
|
||||
RequestParam("joinpsbts", Seq(up.writeJs(psbts)))
|
||||
case FinalizePSBT(psbt) =>
|
||||
RequestParam("finalizepsbt", Seq(up.writeJs(psbt)))
|
||||
case ExtractFromPSBT(psbt) =>
|
||||
RequestParam("extractfrompsbt", Seq(up.writeJs(psbt)))
|
||||
case ConvertToPSBT(tx) =>
|
||||
RequestParam("converttopsbt", Seq(up.writeJs(tx)))
|
||||
|
||||
case NoCommand => ???
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,9 @@ import org.bitcoins.core.config.{NetworkParameters, Networks}
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol.BlockStamp.BlockTime
|
||||
import org.bitcoins.core.protocol._
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import scodec.bits.ByteVector
|
||||
import scopt._
|
||||
|
||||
/** scopt readers for parsing CLI params and options */
|
||||
@ -62,4 +65,17 @@ object CliReaders {
|
||||
case _ => BlockStamp.fromString(str).get
|
||||
}
|
||||
}
|
||||
|
||||
implicit val psbtReads: Read[PSBT] =
|
||||
new Read[PSBT] {
|
||||
val arity: Int = 1
|
||||
|
||||
val reads: String => PSBT = PSBT.fromString
|
||||
}
|
||||
|
||||
implicit val txReads: Read[Transaction] = new Read[Transaction] {
|
||||
val arity: Int = 1
|
||||
|
||||
val reads: String => Transaction = Transaction.fromHex
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package org.bitcoins
|
||||
|
||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||
import org.bitcoins.core.currency.Bitcoins
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import upickle.default._
|
||||
|
||||
package object picklers {
|
||||
@ -18,4 +20,10 @@ package object picklers {
|
||||
|
||||
implicit val blockStampPickler: ReadWriter[BlockStamp] =
|
||||
readwriter[String].bimap(_.mkString, BlockStamp.fromString(_).get)
|
||||
|
||||
implicit val psbtPickler: ReadWriter[PSBT] =
|
||||
readwriter[String].bimap(_.base64, PSBT.fromString)
|
||||
|
||||
implicit val transactionPickler: ReadWriter[Transaction] =
|
||||
readwriter[String].bimap(_.hex, Transaction.fromHex)
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ import akka.http.scaladsl.model.ContentTypes._
|
||||
import akka.http.scaladsl.server.ValidationRejection
|
||||
import akka.http.scaladsl.testkit.ScalatestRouteTest
|
||||
import org.bitcoins.chain.api.ChainApi
|
||||
import org.bitcoins.core.Core
|
||||
import org.bitcoins.core.api.CoreApi
|
||||
import org.bitcoins.core.crypto.DoubleSha256DigestBE
|
||||
import org.bitcoins.core.currency.{Bitcoins, CurrencyUnit}
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
@ -15,7 +17,8 @@ import org.bitcoins.core.protocol.BlockStamp.{
|
||||
BlockTime,
|
||||
InvalidBlockStamp
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction.EmptyTransaction
|
||||
import org.bitcoins.core.protocol.transaction.{EmptyTransaction, Transaction}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.util.FutureUtil
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.node.Node
|
||||
@ -49,8 +52,90 @@ class RoutesSpec
|
||||
|
||||
val walletRoutes = WalletRoutes(mockWalletApi, mockNode)
|
||||
|
||||
val coreRoutes: CoreRoutes = CoreRoutes(Core)
|
||||
|
||||
"The server" should {
|
||||
|
||||
"combine PSBTs" in {
|
||||
val psbt1 = PSBT(
|
||||
"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f00")
|
||||
val psbt2 = PSBT(
|
||||
"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708100f0102030405060708090a0b0c0d0e0f00")
|
||||
val expected = PSBT(
|
||||
"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0a0f0102030405060708100f0102030405060708090a0b0c0d0e0f00")
|
||||
|
||||
val route =
|
||||
coreRoutes.handleCommand(
|
||||
ServerCommand("combinepsbts",
|
||||
Arr(Arr(Str(psbt1.base64), Str(psbt2.base64)))))
|
||||
|
||||
Get() ~> route ~> check {
|
||||
contentType shouldEqual `application/json`
|
||||
responseAs[String] shouldEqual s"""{"result":"${expected.base64}","error":null}"""
|
||||
}
|
||||
|
||||
val joinRoute =
|
||||
coreRoutes.handleCommand(
|
||||
ServerCommand("joinpsbts",
|
||||
Arr(Arr(Str(psbt1.base64), Str(psbt2.base64)))))
|
||||
|
||||
Get() ~> joinRoute ~> check {
|
||||
contentType shouldEqual `application/json`
|
||||
responseAs[String] shouldEqual s"""{"result":"${expected.base64}","error":null}"""
|
||||
}
|
||||
}
|
||||
|
||||
"finalize a PSBT" in {
|
||||
val psbt = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
|
||||
val expected = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
|
||||
|
||||
val route =
|
||||
coreRoutes.handleCommand(
|
||||
ServerCommand("finalizepsbt", Arr(Str(psbt.hex))))
|
||||
|
||||
Get() ~> route ~> check {
|
||||
contentType shouldEqual `application/json`
|
||||
responseAs[String] shouldEqual s"""{"result":"${expected.base64}","error":null}"""
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"extract a transaction from a PSBT" in {
|
||||
val psbt = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
|
||||
val expected = Transaction(
|
||||
"0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000")
|
||||
|
||||
val route =
|
||||
coreRoutes.handleCommand(
|
||||
ServerCommand("extractfrompsbt", Arr(Str(psbt.hex))))
|
||||
|
||||
Get() ~> route ~> check {
|
||||
contentType shouldEqual `application/json`
|
||||
responseAs[String] shouldEqual s"""{"result":"${expected.hex}","error":null}"""
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"convert a transaction to a PSBT" in {
|
||||
val tx = Transaction(
|
||||
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000")
|
||||
val expected = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000")
|
||||
|
||||
val route =
|
||||
coreRoutes.handleCommand(
|
||||
ServerCommand("converttopsbt", Arr(Str(tx.hex))))
|
||||
|
||||
Get() ~> route ~> check {
|
||||
contentType shouldEqual `application/json`
|
||||
responseAs[String] shouldEqual s"""{"result":"${expected.base64}","error":null}"""
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"return the block count" in {
|
||||
(mockChainApi.getBlockCount: () => Future[Int])
|
||||
.expects()
|
||||
|
@ -0,0 +1,77 @@
|
||||
package org.bitcoins.server
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.http.scaladsl.server._
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.stream.ActorMaterializer
|
||||
import org.bitcoins.core.api.CoreApi
|
||||
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
case class CoreRoutes(core: CoreApi)(implicit system: ActorSystem)
|
||||
extends ServerRoute {
|
||||
import system.dispatcher
|
||||
implicit val materializer: ActorMaterializer = ActorMaterializer()
|
||||
|
||||
def handleCommand: PartialFunction[ServerCommand, StandardRoute] = {
|
||||
case ServerCommand("finalizepsbt", arr) =>
|
||||
FinalizePSBT.fromJsArr(arr) match {
|
||||
case Success(FinalizePSBT(psbt)) =>
|
||||
complete {
|
||||
core
|
||||
.finalizePSBT(psbt)
|
||||
.map(finalized => Server.httpSuccess(finalized.base64))
|
||||
}
|
||||
case Failure(exception) =>
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
}
|
||||
|
||||
case ServerCommand("extractfrompsbt", arr) =>
|
||||
ExtractFromPSBT.fromJsArr(arr) match {
|
||||
case Success(ExtractFromPSBT(psbt)) =>
|
||||
complete {
|
||||
core
|
||||
.extractFromPSBT(psbt)
|
||||
.map(tx => Server.httpSuccess(tx.hex))
|
||||
}
|
||||
case Failure(exception) =>
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
}
|
||||
|
||||
case ServerCommand("converttopsbt", arr) =>
|
||||
ConvertToPSBT.fromJsArr(arr) match {
|
||||
case Success(ConvertToPSBT(tx)) =>
|
||||
complete {
|
||||
core
|
||||
.convertToPSBT(tx)
|
||||
.map(psbt => Server.httpSuccess(psbt.base64))
|
||||
}
|
||||
case Failure(exception) =>
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
}
|
||||
|
||||
case ServerCommand("combinepsbts", arr) =>
|
||||
CombinePSBTs.fromJsArr(arr) match {
|
||||
case Success(CombinePSBTs(psbts)) =>
|
||||
complete {
|
||||
core
|
||||
.combinePSBTs(psbts)
|
||||
.map(psbt => Server.httpSuccess(psbt.base64))
|
||||
}
|
||||
case Failure(exception) =>
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
}
|
||||
|
||||
case ServerCommand("joinpsbts", arr) =>
|
||||
JoinPSBTs.fromJsArr(arr) match {
|
||||
case Success(JoinPSBTs(psbts)) =>
|
||||
complete {
|
||||
core
|
||||
.joinPSBTs(psbts)
|
||||
.map(psbt => Server.httpSuccess(psbt.base64))
|
||||
}
|
||||
case Failure(exception) =>
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import java.nio.file.Files
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import org.bitcoins.chain.config.ChainAppConfig
|
||||
import org.bitcoins.core.Core
|
||||
import org.bitcoins.core.api.ChainQueryApi
|
||||
import org.bitcoins.keymanager.KeyManagerInitializeError
|
||||
import org.bitcoins.keymanager.bip39.BIP39KeyManager
|
||||
@ -55,7 +56,9 @@ object Main extends App {
|
||||
val walletRoutes = WalletRoutes(wallet, node)
|
||||
val nodeRoutes = NodeRoutes(node)
|
||||
val chainRoutes = ChainRoutes(chainApi)
|
||||
val server = Server(nodeConf, Seq(walletRoutes, nodeRoutes, chainRoutes))
|
||||
val coreRoutes = CoreRoutes(Core)
|
||||
val server =
|
||||
Server(nodeConf, Seq(walletRoutes, nodeRoutes, chainRoutes, coreRoutes))
|
||||
|
||||
server.start()
|
||||
}
|
||||
|
@ -2,7 +2,9 @@ package org.bitcoins.server
|
||||
|
||||
import org.bitcoins.core.currency.Bitcoins
|
||||
import org.bitcoins.core.protocol.BlockStamp.BlockHeight
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import ujson._
|
||||
import upickle.default._
|
||||
|
||||
@ -15,6 +17,65 @@ object ServerCommand {
|
||||
implicit val rw: ReadWriter[ServerCommand] = macroRW
|
||||
}
|
||||
|
||||
case class CombinePSBTs(psbts: Seq[PSBT])
|
||||
|
||||
object CombinePSBTs extends ServerJsonModels {
|
||||
|
||||
def fromJsArr(jsArr: ujson.Arr): Try[CombinePSBTs] = {
|
||||
require(jsArr.arr.size == 1,
|
||||
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1")
|
||||
|
||||
Try(CombinePSBTs(jsToPSBTSeq(jsArr.arr.head)))
|
||||
}
|
||||
}
|
||||
|
||||
case class JoinPSBTs(psbts: Seq[PSBT])
|
||||
|
||||
object JoinPSBTs extends ServerJsonModels {
|
||||
|
||||
def fromJsArr(jsArr: ujson.Arr): Try[JoinPSBTs] = {
|
||||
CombinePSBTs
|
||||
.fromJsArr(jsArr)
|
||||
.map(combine => JoinPSBTs(combine.psbts))
|
||||
}
|
||||
}
|
||||
|
||||
case class FinalizePSBT(psbt: PSBT)
|
||||
|
||||
object FinalizePSBT extends ServerJsonModels {
|
||||
|
||||
def fromJsArr(jsArr: ujson.Arr): Try[FinalizePSBT] = {
|
||||
require(jsArr.arr.size == 1,
|
||||
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1")
|
||||
|
||||
Try(FinalizePSBT(jsToPSBT(jsArr.arr.head)))
|
||||
}
|
||||
}
|
||||
|
||||
case class ExtractFromPSBT(psbt: PSBT)
|
||||
|
||||
object ExtractFromPSBT extends ServerJsonModels {
|
||||
|
||||
def fromJsArr(jsArr: ujson.Arr): Try[ExtractFromPSBT] = {
|
||||
require(jsArr.arr.size == 1,
|
||||
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1")
|
||||
|
||||
Try(ExtractFromPSBT(jsToPSBT(jsArr.arr.head)))
|
||||
}
|
||||
}
|
||||
|
||||
case class ConvertToPSBT(tx: Transaction)
|
||||
|
||||
object ConvertToPSBT extends ServerJsonModels {
|
||||
|
||||
def fromJsArr(jsArr: ujson.Arr): Try[ConvertToPSBT] = {
|
||||
require(jsArr.arr.size == 1,
|
||||
s"Bad number of arguments: ${jsArr.arr.size}. Expected: 1")
|
||||
|
||||
Try(ConvertToPSBT(jsToTx(jsArr.arr.head)))
|
||||
}
|
||||
}
|
||||
|
||||
case class Rescan(
|
||||
batchSize: Option[Int],
|
||||
startBlock: Option[BlockStamp],
|
||||
@ -119,4 +180,12 @@ trait ServerJsonModels {
|
||||
}
|
||||
}
|
||||
|
||||
def jsToPSBTSeq(js: Value): Seq[PSBT] = {
|
||||
js.arr.foldLeft(Seq.empty[PSBT])((seq, psbt) => seq :+ jsToPSBT(psbt))
|
||||
}
|
||||
|
||||
def jsToPSBT(js: Value): PSBT = PSBT.fromString(js.str)
|
||||
|
||||
def jsToTx(js: Value): Transaction = Transaction.fromHex(js.str)
|
||||
|
||||
}
|
||||
|
109
core-test/src/test/scala/org/bitcoins/core/api/CoreApiTest.scala
Normal file
109
core-test/src/test/scala/org/bitcoins/core/api/CoreApiTest.scala
Normal file
@ -0,0 +1,109 @@
|
||||
package org.bitcoins.core.api
|
||||
|
||||
import org.bitcoins.core.Core
|
||||
import org.bitcoins.core.protocol.script.{P2PKHScriptSignature, ScriptSignature}
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.testkit.util.BitcoinSAsyncTest
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class CoreApiTest extends BitcoinSAsyncTest {
|
||||
|
||||
behavior of "CoreApi"
|
||||
|
||||
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
|
||||
generatorDrivenConfigNewCode
|
||||
|
||||
it must "successfully combine two PSBTs with unknown types" in {
|
||||
val psbt1 = PSBT(
|
||||
"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f00")
|
||||
val psbt2 = PSBT(
|
||||
"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708100f0102030405060708090a0b0c0d0e0f00")
|
||||
val expected = PSBT(
|
||||
"70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a0100000000000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0a0f0102030405060708100f0102030405060708090a0b0c0d0e0f000a0f0102030405060708090f0102030405060708090a0b0c0d0e0f0a0f0102030405060708100f0102030405060708090a0b0c0d0e0f00")
|
||||
|
||||
Core.joinPSBTs(Seq(psbt1, psbt2)).map(psbt => assert(psbt == expected))
|
||||
Core.combinePSBTs(Seq(psbt1, psbt2)).map(psbt => assert(psbt == expected))
|
||||
}
|
||||
|
||||
it must "fail to combine when given an empty seq" in {
|
||||
Core
|
||||
.joinPSBTs(Seq.empty)
|
||||
.failed
|
||||
.map(err => assertThrows[IllegalArgumentException](throw err))
|
||||
}
|
||||
|
||||
it must "catch errors when combining PSBTs" in {
|
||||
val psbt0 = PSBT.fromUnsignedTx(Transaction(
|
||||
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000"))
|
||||
|
||||
val psbt1 = PSBT.fromUnsignedTx(Transaction(
|
||||
"02000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef13357900000000"))
|
||||
|
||||
Core
|
||||
.joinPSBTs(Seq(psbt0, psbt1))
|
||||
.failed
|
||||
.map(err => assertThrows[IllegalArgumentException](throw err))
|
||||
}
|
||||
|
||||
it must "successfully finalize a PSBT" in {
|
||||
val psbt = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
|
||||
|
||||
val expected = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
|
||||
|
||||
Core.finalizePSBT(psbt).map(finalized => assert(finalized == expected))
|
||||
}
|
||||
|
||||
it must "catch errors when finalizing a PSBT" in {
|
||||
val psbt = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000")
|
||||
|
||||
Core
|
||||
.finalizePSBT(psbt)
|
||||
.failed
|
||||
.map(err => assertThrows[IllegalStateException](throw err))
|
||||
}
|
||||
|
||||
it must "successfully extract a transaction from a finalized PSBT" in {
|
||||
val psbt = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000")
|
||||
val expected = Transaction.fromHex(
|
||||
"0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000")
|
||||
|
||||
Core
|
||||
.extractFromPSBT(psbt)
|
||||
.map(tx => assert(tx == expected))
|
||||
}
|
||||
|
||||
it must "catch errors when extracting a transaction from a finalized PSBT" in {
|
||||
val psbt = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000")
|
||||
|
||||
Core
|
||||
.extractFromPSBT(psbt)
|
||||
.failed
|
||||
.map(err => assertThrows[IllegalStateException](throw err))
|
||||
}
|
||||
|
||||
it must "convert a tx to a PSBT" in {
|
||||
val tx = Transaction(
|
||||
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000")
|
||||
val expected = PSBT(
|
||||
"70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000")
|
||||
|
||||
Core.convertToPSBT(tx).map(psbt => assert(psbt == expected))
|
||||
}
|
||||
|
||||
it must "catch errors when converting a tx to a PSBT" in {
|
||||
val tx = Transaction(
|
||||
"0200000002cffe265210b42971f9642adcf88f6e6aaa40e6baec30037ce0088383b47c4f28031e306ffd2403004730450220259683be7d97e0a0f611926057bbd3fc8c5f886a06d39ec5ce330a87bcb33fa9022100ae6eb066c89dee2b5e4fefaa03450cfa6960508c2dc1c82f206bd66b9e8d3913463044022030d398ef4a69646ae40a84af9634e0608f4882269b92777f239c146555a3d4b602200ff23130697bca6bb7be3b69962387d6caeb25e198dc59969328d993119f9b5147304502206c0abf639640f56ab85c9800a2555be92569682a96bde261cfd9f1254578ee90022100c4b42232041d5f228ff832d1615151949d320eaf85bef9389463744f996e46b9473045022058d8a8ac02b5bd7139a99787dd42067e2de3a4e613f0878318aefcac4f13768f022100f620e66d3cc96230a0602e803a3f790861e1f66f72028e2ed79dc0ec3c24b98e4d01025f2103a2ae334a00a2a8b0191b41829b72eac3d4f207f87ea5b102383ed58716a43d452102351e70b50c30827ff18fde7b7458c991f74a938878f94c5f1384840b0775be2b210210c5d0c5528f61def05d0893e7b3145c5c5c58c4771c74de74470c69c8d86da121032999f0c7acd64f80b318beae5be7cedb4a72b0f370dcaa8ab9a0be1190df001821034afa6da2e9a4fbf84b33e5c35c81b0396542b4bf5eddaef0fb7e760833047fca21022875fe400ea9200ca013447c9dfe68e72d09e39a14c2b80f5beba35db00eb3a621031969406b809d0413d156fd2eb449cdc7b4b6fd431a3179abbd73cc2dd8a675bc21022f017723760552871039ba59678db384a1d195f2bcb6207a4514d1b86af0d6f42102a2d346e46656e5070433874521aaf24423f78438ec644c975ce8e2a32f35ef1d21027138384fcde2ae4aee8052f576227c3a158a09be5e8ee9df5145a39a06b5f6f32103d30805f2a4fc3d816b0f37b5500ffa7f59603ef45680c7f75991342d53af30d02103a27ef20b51281a98dcf55afdf53182d6a07c0d48afa511fa52d352faf79169b52102c50b2f80b12f0bbd304802916f906e366d017083d381cd45d64074fdb04b1b3d2103e3a5a4df1fc467589ed98d94f90cc1d41a2ac5a40b68a6c489cc1e4af04c5bb421039af4e1b6903db40e9cfe2c14cdd490c385d1339b0b7386719e277bbd5492378d5fae0f25e5bbcffe265210b42971f9642adcf88f6e6aaa40e6baec30037ce0088383b47c4f28031e306ffd2403004730450220259683be7d97e0a0f611926057bbd3fc8c5f886a06d39ec5ce330a87bcb33fa9022100ae6eb066c89dee2b5e4fefaa03450cfa6960508c2dc1c82f206bd66b9e8d3913463044022030d398ef4a69646ae40a84af9634e0608f4882269b92777f239c146555a3d4b602200ff23130697bca6bb7be3b69962387d6caeb25e198dc59969328d993119f9b5147304502206c0abf639640f56ab85c9800a2555be92569682a96bde261cfd9f1254578ee90022100c4b42232041d5f228ff832d1615151949d320eaf85bef9389463744f996e46b9473045022058d8a8ac02b5bd7139a99787dd42067e2de3a4e613f0878318aefcac4f13768f022100f620e66d3cc96230a0602e803a3f790861e1f66f72028e2ed79dc0ec3c24b98e4d01025f2103a2ae334a00a2a8b0191b41829b72eac3d4f207f87ea5b102383ed58716a43d452102351e70b50c30827ff18fde7b7458c991f74a938878f94c5f1384840b0775be2b210210c5d0c5528f61def05d0893e7b3145c5c5c58c4771c74de74470c69c8d86da121032999f0c7acd64f80b318beae5be7cedb4a72b0f370dcaa8ab9a0be1190df001821034afa6da2e9a4fbf84b33e5c35c81b0396542b4bf5eddaef0fb7e760833047fca21022875fe400ea9200ca013447c9dfe68e72d09e39a14c2b80f5beba35db00eb3a621031969406b809d0413d156fd2eb449cdc7b4b6fd431a3179abbd73cc2dd8a675bc21022f017723760552871039ba59678db384a1d195f2bcb6207a4514d1b86af0d6f42102a2d346e46656e5070433874521aaf24423f78438ec644c975ce8e2a32f35ef1d21027138384fcde2ae4aee8052f576227c3a158a09be5e8ee9df5145a39a06b5f6f32103d30805f2a4fc3d816b0f37b5500ffa7f59603ef45680c7f75991342d53af30d02103a27ef20b51281a98dcf55afdf53182d6a07c0d48afa511fa52d352faf79169b52102c50b2f80b12f0bbd304802916f906e366d017083d381cd45d64074fdb04b1b3d2103e3a5a4df1fc467589ed98d94f90cc1d41a2ac5a40b68a6c489cc1e4af04c5bb421039af4e1b6903db40e9cfe2c14cdd490c385d1339b0b7386719e277bbd5492378d5fae0f25e5bb0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000")
|
||||
|
||||
Core
|
||||
.convertToPSBT(tx)
|
||||
.failed
|
||||
.map(err => assertThrows[IllegalArgumentException](throw err))
|
||||
}
|
||||
}
|
56
core/src/main/scala/org/bitcoins/core/Core.scala
Normal file
56
core/src/main/scala/org/bitcoins/core/Core.scala
Normal file
@ -0,0 +1,56 @@
|
||||
package org.bitcoins.core
|
||||
|
||||
import org.bitcoins.core.api.CoreApi
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
|
||||
import scala.concurrent.Future
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
object Core extends CoreApi {
|
||||
override def combinePSBTs(psbts: Seq[PSBT]): Future[PSBT] = {
|
||||
if (psbts.isEmpty) {
|
||||
Future.failed(new IllegalArgumentException("No PSBTs given"))
|
||||
} else {
|
||||
try {
|
||||
val empty = PSBT.fromUnsignedTx(psbts.head.transaction)
|
||||
val combined =
|
||||
psbts.foldLeft(empty)((accum, psbt) => accum.combinePSBT(psbt))
|
||||
|
||||
Future.successful(combined)
|
||||
} catch {
|
||||
case err: IllegalArgumentException =>
|
||||
Future.failed(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def finalizePSBT(psbt: PSBT): Future[PSBT] = {
|
||||
psbt.finalizePSBT match {
|
||||
case Success(finalized) =>
|
||||
Future.successful(finalized)
|
||||
case Failure(err) =>
|
||||
Future.failed(err)
|
||||
}
|
||||
}
|
||||
|
||||
override def extractFromPSBT(psbt: PSBT): Future[Transaction] = {
|
||||
psbt.extractTransactionAndValidate match {
|
||||
case Success(extracted) =>
|
||||
Future.successful(extracted)
|
||||
case Failure(err) =>
|
||||
Future.failed(err)
|
||||
}
|
||||
}
|
||||
|
||||
override def convertToPSBT(transaction: Transaction): Future[PSBT] = {
|
||||
try {
|
||||
val psbt = PSBT.fromUnsignedTx(transaction)
|
||||
|
||||
Future.successful(psbt)
|
||||
} catch {
|
||||
case err: IllegalArgumentException =>
|
||||
Future.failed(err)
|
||||
}
|
||||
}
|
||||
}
|
18
core/src/main/scala/org/bitcoins/core/api/CoreApi.scala
Normal file
18
core/src/main/scala/org/bitcoins/core/api/CoreApi.scala
Normal file
@ -0,0 +1,18 @@
|
||||
package org.bitcoins.core.api
|
||||
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
trait CoreApi {
|
||||
def combinePSBTs(psbts: Seq[PSBT]): Future[PSBT]
|
||||
|
||||
def joinPSBTs(psbts: Seq[PSBT]): Future[PSBT] = combinePSBTs(psbts)
|
||||
|
||||
def finalizePSBT(psbt: PSBT): Future[PSBT]
|
||||
|
||||
def extractFromPSBT(psbt: PSBT): Future[Transaction]
|
||||
|
||||
def convertToPSBT(transaction: Transaction): Future[PSBT]
|
||||
}
|
@ -614,6 +614,23 @@ object PSBT extends Factory[PSBT] {
|
||||
// The magic bytes and separator defined by https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#specification
|
||||
final val magicBytes = hex"70736274ff"
|
||||
|
||||
final val empty = fromUnsignedTx(EmptyTransaction)
|
||||
|
||||
def fromString(str: String): PSBT = {
|
||||
ByteVector.fromHex(str) match {
|
||||
case Some(hex) =>
|
||||
PSBT(hex)
|
||||
case None =>
|
||||
ByteVector.fromBase64(str) match {
|
||||
case Some(base64) =>
|
||||
PSBT(base64)
|
||||
case None =>
|
||||
throw new IllegalArgumentException(
|
||||
s"String given must be in base64 or hexadecimal, got: $str")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def fromBytes(bytes: ByteVector): PSBT = {
|
||||
require(bytes.startsWith(magicBytes),
|
||||
s"A PSBT must start with the magic bytes $magicBytes, got: $bytes")
|
||||
|
Loading…
Reference in New Issue
Block a user