Successfully serializing and deserializing a raw bitcoin output

This commit is contained in:
Chris Stewart 2016-01-13 13:57:45 -06:00
parent eac24ded52
commit bbff4ee676
8 changed files with 95 additions and 38 deletions

View File

@ -1,5 +1,7 @@
package org.scalacoin.marshallers
import org.scalacoin.util.ScalacoinUtil
/**
* Created by chris on 1/11/16.
*/
@ -11,7 +13,9 @@ trait RawBitcoinSerializer[T] {
* @param hex
* @return
*/
def read(hex : String) : T
def read(hex : String) : T = read(ScalacoinUtil.decodeHex(hex))
def read(bytes : List[Byte]) : T
/**
* Takes a type T and writes it into the appropriate type T

View File

@ -11,17 +11,18 @@ import org.scalacoin.util.ScalacoinUtil
trait RawScriptPubKeyParser extends RawBitcoinSerializer[ScriptPubKey] with ScriptParser {
override def read(hex : String) : ScriptPubKey = {
require(hex.size > 0, "Cannot parse a scriptPubKey from an empty string")
val bytes = ScalacoinUtil.decodeHex(hex)
override def read(bytes : List[Byte]) : ScriptPubKey = {
require(bytes.size > 0, "Cannot parse a scriptPubKey from an empty byte list")
//first byte indicates how many bytes the script is
val scriptSize = bytes.head
val script : List[ScriptToken] = parse(bytes.tail)
//not sure how to get addresses from a scriptPubKey
ScriptPubKeyImpl(script,hex,Seq())
ScriptPubKeyImpl(script,ScalacoinUtil.encodeHex(bytes),Seq())
}
override def write(scriptPubKey : ScriptPubKey) : String = ???
override def write(scriptPubKey : ScriptPubKey) : String = {
scriptPubKey.hex
}
}
object RawScriptPubKeyParser extends RawScriptPubKeyParser

View File

@ -10,9 +10,9 @@ import org.scalacoin.util.ScalacoinUtil
*/
trait RawScriptSignatureParser extends RawBitcoinSerializer[ScriptSignature] with ScriptParser {
def read(hex : String) : ScriptSignature = {
val scriptSig : List[ScriptToken] = parse(ScalacoinUtil.decodeHex(hex))
ScriptSignatureImpl(scriptSig,hex)
def read(bytes : List[Byte]) : ScriptSignature = {
val scriptSig : List[ScriptToken] = parse(bytes)
ScriptSignatureImpl(scriptSig,ScalacoinUtil.encodeHex(bytes))
}
def write(scriptSig : ScriptSignature) : String = scriptSig.hex

View File

@ -9,11 +9,10 @@ import org.scalacoin.util.ScalacoinUtil
* https://bitcoin.org/en/developer-reference#outpoint
*
*/
object RawTransactionOutPointMarshaller extends RawBitcoinSerializer[TransactionOutPoint] {
trait RawTransactionOutPointMarshaller extends RawBitcoinSerializer[TransactionOutPoint] {
def read(str : String) : TransactionOutPoint = {
val bytes = ScalacoinUtil.decodeHex(str)
override def read(bytes : List[Byte]) : TransactionOutPoint = {
val txId : List[Byte] = bytes.slice(0,16)
val index : BigInt = BigInt(bytes.slice(16, bytes.size).toArray)
TransactionOutPointImpl(ScalacoinUtil.encodeHex(txId), index.toInt)
@ -25,3 +24,6 @@ object RawTransactionOutPointMarshaller extends RawBitcoinSerializer[Transaction
}
}
object RawTransactionOutPointMarshaller extends RawTransactionOutPointMarshaller

View File

@ -1,29 +1,57 @@
package org.scalacoin.marshallers.transaction
import org.scalacoin.currency.Satoshis
import org.scalacoin.currency.{CurrencyUnits, Satoshis}
import org.scalacoin.marshallers.RawBitcoinSerializer
import org.scalacoin.marshallers.script.ScriptParser
import org.scalacoin.marshallers.script.{RawScriptPubKeyParser, ScriptParser}
import org.scalacoin.protocol.transaction.{TransactionOutputImpl, TransactionOutput}
import org.scalacoin.util.ScalacoinUtil
import scala.annotation.tailrec
/**
* Created by chris on 1/11/16.
* https://bitcoin.org/en/developer-reference#txout
*/
object RawTransactionOutputParser extends RawBitcoinSerializer[Seq[TransactionOutput]] with ScriptParser {
trait RawTransactionOutputParser extends RawBitcoinSerializer[Seq[TransactionOutput]] with ScriptParser {
override def read(str : String) : Seq[TransactionOutput] = {
val bytes = ScalacoinUtil.decodeHex(str)
val numOutputs = bytes(0).toInt
val satoshisHex = ScalacoinUtil.encodeHex(bytes.slice(1,9))
val satoshis = Satoshis(Integer.parseInt(satoshisHex,16))
val scriptBytes = bytes.slice(10,bytes.size)
val script = parse(scriptBytes)
/*TransactionOutputImpl(satoshis, 0, script)*/
???
override def read(bytes : List[Byte]) : Seq[TransactionOutput] = {
val numOutputs = bytes.head.toInt
@tailrec
def loop(bytes : List[Byte], accum : List[TransactionOutput], outputsLeftToParse : Int) : List[TransactionOutput] = {
if (outputsLeftToParse > 0) {
val satoshisHex = ScalacoinUtil.encodeHex(bytes.take(8).reverse)
val satoshis = Satoshis(Integer.parseInt(satoshisHex, 16))
//it doesn't include itself towards the size, thats why it is incremented by one
val firstScriptPubKeyByte = 8
val scriptPubKeySize = bytes(firstScriptPubKeyByte).toInt + 1
val scriptPubKeyBytes = bytes.slice(firstScriptPubKeyByte, firstScriptPubKeyByte + scriptPubKeySize)
val script = RawScriptPubKeyParser.read(scriptPubKeyBytes)
val newAccum = TransactionOutputImpl(satoshis, 0, script) :: accum
val bytesToBeParsed = bytes.slice(firstScriptPubKeyByte + scriptPubKeySize, bytes.size)
loop(bytesToBeParsed, newAccum, outputsLeftToParse-1)
} else accum
}
loop(bytes.tail,List(),numOutputs).reverse
}
override def write(outputs : Seq[TransactionOutput]) : String = ???
override def write(outputs : Seq[TransactionOutput]) : String = {
val numOutputs = ScalacoinUtil.encodeHex(outputs.size.toByte)
val serializedOutputs : Seq[String] = for {
output <- outputs
} yield {
val satoshis = CurrencyUnits.toSatoshis(output.value)
//TODO: Clean this up, this is very confusing. If you remove this .reverse method calls you can see the unit test failing
val satoshisHexWithoutPadding : String =
ScalacoinUtil.encodeHex(ScalacoinUtil.decodeHex(ScalacoinUtil.encodeHex(satoshis)).reverse).reverse
val paddingNeeded = 16 - satoshisHexWithoutPadding.size
val padding : Seq[String] = for ( i <- 0 until paddingNeeded) yield "0"
val satoshisHex = padding.mkString + satoshisHexWithoutPadding
satoshisHex.reverse + output.scriptPubKey.hex
}
numOutputs + serializedOutputs.mkString
}
}
object RawTransactionOutputParser extends RawTransactionOutputParser

View File

@ -16,12 +16,14 @@ class RawScriptPubKeyParserTest extends FlatSpec with MustMatchers with RawScrip
//scriptPubKey taken from https://bitcoin.org/en/developer-reference#raw-transaction-format
val rawScriptPubKey = "1976a914cbc20a7664f2f69e5355aa427045bc15e7c6c77288ac"
"RawScriptPubKeyParser" must "parse a hex string into a scriptPubKey" in {
val scriptPubKey : ScriptPubKey = read(rawScriptPubKey)
println(encodeBase58(decodeHex("cbc20a7664f2f69e5355aa427045bc15e7c6c772")))
scriptPubKey.asm must be (Seq(OP_DUP,OP_HASH160,ScriptConstantImpl("cbc20a7664f2f69e5355aa427045bc15e7c6c772"),OP_EQUALVERIFY,OP_CHECKSIG))
}
it must "read then write the scriptPubKey and get the original scriptPubKey" in {
val scriptPubKey : ScriptPubKey = read(rawScriptPubKey)
write(scriptPubKey) must be (rawScriptPubKey)
}
}

View File

@ -1,19 +1,39 @@
package org.scalacoin.marshallers.transaction
import org.scalacoin.marshallers.RawBitcoinSerializer
import org.scalacoin.currency.{CurrencyUnits, Bitcoins}
import org.scalacoin.protocol.P2SH
import org.scalacoin.protocol.transaction.TransactionOutput
import org.scalacoin.script.bitwise.OP_EQUAL
import org.scalacoin.script.constant.ScriptConstantImpl
import org.scalacoin.script.crypto.OP_HASH160
import org.scalatest.{MustMatchers, FlatSpec}
/**
* Created by chris on 1/11/16.
* https://bitcoin.org/en/developer-reference#txout
*/
class RawTransactionOutputMarshallerTest extends FlatSpec with MustMatchers {
class RawTransactionOutputMarshallerTest extends FlatSpec with MustMatchers with RawTransactionOutputParser {
//txid cad1082e674a7bd3bc9ab1bc7804ba8a57523607c876b8eb2cbe645f2b1803d6
val rawTxOutput = "02204e00000000000017a914eda8ae08b5c9f973f49543e90a7c292367b3337c87197d2d000000000017a914be2319b9060429692ebeffaa3be38497dc5380c887"
val rawTxOutput = "02204e00000000000017a914eda8ae08b5c9f973f49543e90a7c292367b3337c87" +
"197d2d000000000017a914be2319b9060429692ebeffaa3be38497dc5380c887"
"RawTransactionOutputMarshaller" must "read a serialized tx output" in {
val _ : Seq[TransactionOutput] = ???
val txOutput : Seq[TransactionOutput] = read(rawTxOutput)
val firstOutput = txOutput.head
val secondOutput = txOutput(1)
firstOutput.value must be (CurrencyUnits.toSatoshis(Bitcoins(0.0002)))
secondOutput.value must be (CurrencyUnits.toSatoshis(Bitcoins(0.02981145)))
firstOutput.scriptPubKey.asm must be (Seq(OP_HASH160, ScriptConstantImpl("eda8ae08b5c9f973f49543e90a7c292367b3337c"), OP_EQUAL))
secondOutput.scriptPubKey.asm must be (Seq(OP_HASH160, ScriptConstantImpl("be2319b9060429692ebeffaa3be38497dc5380c8"), OP_EQUAL))
firstOutput.scriptPubKey.addressType must be (P2SH)
secondOutput.scriptPubKey.addressType must be (P2SH)
}
it must "seralialize a transaction output" in {
val txOutput = read(rawTxOutput)
write(txOutput) must be (rawTxOutput)
}
}