Skip to content

Commit

Permalink
parse block cypher response
Browse files Browse the repository at this point in the history
  • Loading branch information
h0ngcha0 committed Feb 23, 2018
1 parent fa46aa8 commit 9fc01a1
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 46 deletions.
4 changes: 2 additions & 2 deletions src/main/scala/me/hongchao/bitcoin4s/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package me.hongchao.bitcoin4s
import akka.actor.ActorSystem
import akka.stream.{ActorMaterializer, ActorMaterializerSettings, Materializer}
import com.typesafe.scalalogging.StrictLogging
import me.hongchao.bitcoin4s.external.{BlockchainInfoApi, HttpSender}
import me.hongchao.bitcoin4s.external.{BlockCypherApi, HttpSender}

trait Boot extends StrictLogging {
implicit val system: ActorSystem = ActorSystem("bitcoin4s")
Expand All @@ -17,6 +17,6 @@ trait Boot extends StrictLogging {
}

val httpSender = new HttpSender()
val blockchainInfoApi = new BlockchainInfoApi(httpSender)
val blockchainInfoApi = new BlockCypherApi(httpSender)

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package me.hongchao.bitcoin4s.external

import java.time.ZonedDateTime

import akka.http.scaladsl.model.{HttpRequest, Uri}
import akka.stream.Materializer

import scala.concurrent.{ExecutionContext, Future}
import BlockchainInfoApi._
import BlockCypherApi._
import akka.http.scaladsl.unmarshalling.Unmarshaller
import io.github.yzernik.bitcoinscodec.messages.TxWitness
import io.github.yzernik.bitcoinscodec.structures.{OutPoint, TxIn, TxOutWitness, Hash => ScodecHash}
Expand All @@ -14,8 +16,8 @@ import scodec.bits.ByteVector
import tech.minna.playjson.macros.json
import me.hongchao.bitcoin4s.crypto.Hash

// NOTE: https://blockchain.info/api/blockchain_api
class BlockchainInfoApi(httpSender: HttpSender)(
// https://www.blockcypher.com/dev/bitcoin/#transaction-api
class BlockCypherApi(httpSender: HttpSender)(
implicit
ec: ExecutionContext,
materializer: Materializer
Expand All @@ -30,84 +32,79 @@ class BlockchainInfoApi(httpSender: HttpSender)(
// TODO: Get rid of the deps for bitcoinscodec
// 1) do we actually need both tx and tx witness
// 2) if not, remove one of them and rewrite the transaction part in terms of scodec
object BlockchainInfoApi {
@json case class PreviousOutput(
spent: Boolean,
tx_index: Long,
`type`: Int,
addr: String,
value: Long,
n: Int,
script: String
) {
def toOutPoint = OutPoint(
hash = ScodecHash(ByteVector(Hash.fromHex(addr))),
index = tx_index
)
}
object BlockCypherApi {

@json case class TransactionInput(
prev_hash: String,
output_index: Int,
script: String,
output_value: Long,
sequence: Long,
witness: String,
prev_out: Option[PreviousOutput],
script: String
addresses: List[String],
script_type: String,
age: Long,
witness: List[String]
) {
val prevTxHash = ScodecHash(ByteVector(Hash.fromHex(prev_hash)))
def toTxIn = TxIn(
previous_output = prev_out.get.toOutPoint, // FIXME: remove .get
previous_output = OutPoint(prevTxHash, output_index),
sig_script = ByteVector(Hash.fromHex(script)),
sequence = sequence
)

def toWitnessScript = ??? // FIXME: how is witness structured
}

@json case class TransactionOutput(
spent: Boolean,
tx_index: Long,
`type`: Int,
addr: Option[String],
value: Long,
n: Int,
script: String
script: String,
spent_by: String,
addresses: List[String],
script_type: String
) {
def toTxOut = TxOutWitness(
value = value,
pk_script = ByteVector(Parser.parse(script).flatMap(_.bytes))
pk_script = ByteVector(Parser.parse(Hash.fromHex(script)).flatMap(_.bytes))
)
}

@json case class Transaction(
ver: Int,
inputs: Seq[TransactionInput],
weight: Long,
block_hash: String,
block_height: Long,
relayed_by: String,
out: Seq[TransactionOutput],
lock_time: Long,
block_index: Int,
hash: String,
addresses: Seq[String],
total: Long,
fees: Long,
size: Long,
confirmed: ZonedDateTime,
received: ZonedDateTime,
ver: Int,
lock_time: Long,
double_spend: Boolean,
time: Long,
tx_index: Long,
vin_sz: Int,
witn: String,
vout_sz: Int
vout_sz: Int,
confirmations: Long,
confidence: Int,
inputs: Seq[TransactionInput],
outputs: Seq[TransactionOutput]
) {
def toTxWitness = TxWitness(
version = ver,
tx_in = inputs.map(_.toTxIn).toList,
tx_out = out.map(_.toTxOut).toList,
tx_out = outputs.map(_.toTxOut).toList,
lock_time = lock_time
)
}

protected def rawTxUrl(txId: String) = Uri(s"https://blockchain.info/rawtx/$txId")
protected def rawTxUrl(txId: String) = Uri(s"https://chain.so/api/v2/tx/BTC/$txId")

protected implicit val transactionUnmarshaller = Unmarshaller.stringUnmarshaller.map { raw =>
def parseTransaction(raw: String): Transaction = {
Json.fromJson[Transaction](Json.parse(raw)) match {
case JsSuccess(value, _) =>
value
case JsError(e) =>
throw new RuntimeException(s"Failed to parse transaction $e")
}
}

protected implicit val transactionUnmarshaller = Unmarshaller.stringUnmarshaller.map(parseTransaction)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package me.hongchao.bitcoin4s.external

import me.hongchao.bitcoin4s.Spec

class BlockchainInfoApiSpec extends Spec {

val blockCypherRawTransaction =
s"""
|{
| "block_hash": "000000000000000000489e24c0f64b7027cc80d3d20faef1f1cdc3b0daac4019",
| "block_height": 508439,
| "block_index": 5,
| "hash": "1191bdc374a460839471cedb284fc82e5a9d2c45c49a4a4658db5ab9c5916911",
| "addresses": [
| "1FAt1gokixe9Y6QdHhXeJ5bjXmKCeNp6a6",
| "1Gez1oJoa5hwgfcXkYHj68298C4RQwApQA",
| "3BKAXzMepun6cBHGrouS95EnnYQAZVfXfz"
| ],
| "total": 2386774,
| "fees": 54400,
| "size": 142,
| "preference": "high",
| "relayed_by": "24.103.166.34:8333",
| "confirmed": "2018-02-09T22:44:55Z",
| "received": "2018-02-09T22:44:16.322Z",
| "ver": 2,
| "lock_time": 508438,
| "double_spend": false,
| "vin_sz": 1,
| "vout_sz": 2,
| "confirmations": 137,
| "confidence": 1,
| "inputs": [
| {
| "prev_hash": "4c24b3019f117f109ffac46eb406c544f6d5a57fd197e5ef845de71f15b42771",
| "output_index": 0,
| "script": "16001413ecf507209720bc23e47368d98303629eed74e3",
| "output_value": 2441174,
| "sequence": 4294967294,
| "addresses": [
| "3BKAXzMepun6cBHGrouS95EnnYQAZVfXfz"
| ],
| "script_type": "pay-to-script-hash",
| "age": 508427,
| "witness": [
| "3044022061d6ef24187f5666ed1d2c771d68f2ce8a0b539df632bd54ad757c9654d07f0202204cf5c998e43a934c8e241cb1a9ebff5ccecc06072dea47fe6e691d8ea61d10c701",
| "027bb5772df97bbfc616b7a1ae91a986ea4d295f4e27d26ecf43e073f92d721b7a"
| ]
| }
| ],
| "outputs": [
| {
| "value": 977703,
| "script": "76a9149b7016504b9b035c0ffbc54853d8758faf85271488ac",
| "spent_by": "b945cc88c7295a0fe53de3d654be277135957e8aaf9f243db94e19d889223a17",
| "addresses": [
| "1FAt1gokixe9Y6QdHhXeJ5bjXmKCeNp6a6"
| ],
| "script_type": "pay-to-pubkey-hash"
| },
| {
| "value": 1409071,
| "script": "76a914abb8debac72eef4b426d81fac4810c5cb3971f9f88ac",
| "spent_by": "dd1e147eaa6715f70e6be23d9af391b51fd19c76d26ba0ece865cf7539ab4f9b",
| "addresses": [
| "1Gez1oJoa5hwgfcXkYHj68298C4RQwApQA"
| ],
| "script_type": "pay-to-pubkey-hash"
| }
| ]
|}
""".stripMargin

"foo" should "bar" in {

val transaction = BlockCypherApi.parseTransaction(blockCypherRawTransaction)
println(s"blockchain.info tx: \n$transaction")

println(s"internal tx: \n${transaction.toTxWitness}")
}
}

0 comments on commit 9fc01a1

Please sign in to comment.