Merge pull request #26 from Christewart/merkle_tree_type

Adding function inside of Merkle to actually build and return the Mer…
This commit is contained in:
Chris Stewart 2016-08-12 13:31:42 -05:00 committed by GitHub
commit 37eb7f2d03
2 changed files with 47 additions and 36 deletions

View file

@ -3,7 +3,7 @@ package org.bitcoins.core.consensus
import org.bitcoins.core.crypto.DoubleSha256Digest
import org.bitcoins.core.protocol.blockchain.Block
import org.bitcoins.core.protocol.transaction.Transaction
import org.bitcoins.core.util.{BitcoinSLogger, CryptoUtil}
import org.bitcoins.core.util._
import scala.annotation.tailrec
@ -14,6 +14,8 @@ import scala.annotation.tailrec
* https://github.com/bitcoin/bitcoin/blob/master/src/consensus/merkle.cpp
*/
trait Merkle extends BitcoinSLogger {
type MerkleTree = BinaryTree[DoubleSha256Digest]
/**
* Computes the merkle root for the given block
* @param block the given block that needs the merkle root computed
@ -21,34 +23,6 @@ trait Merkle extends BitcoinSLogger {
*/
def computeBlockMerkleRoot(block : Block) : DoubleSha256Digest = computeMerkleRoot(block.transactions)
/**
* Computes the merkle root of a given sequence of hashes
* This hash function assumes that all of the hashes are big endian encoded
* @param hashes the big endian encoded hashes from which the merkle root is derived from
* @param accum the accumulator for the recursive call on this function - more than likely you do not need to worry about this
* @return the merkle root
*/
@tailrec
final def computeMerkleRoot(hashes : Seq[DoubleSha256Digest], accum : List[DoubleSha256Digest] = List()) : DoubleSha256Digest = hashes match {
case Nil =>
logger.debug("No more hashes to check")
if (accum.size == 1) DoubleSha256Digest(accum.head.bytes.reverse)
else if (accum.size == 0) throw new RuntimeException("We cannot have zero hashes and nothing in the accumulator, this means there is NO transaction in the block. " +
"There always should be ATLEAST one - the coinbase tx")
else computeMerkleRoot(accum.reverse, List())
case h :: h1 :: t =>
logger.debug("We have an even amount of txids")
logger.debug("Hashes: " + hashes.map(_.hex))
val hash = CryptoUtil.doubleSHA256(h.bytes ++ h1.bytes)
computeMerkleRoot(t, hash :: accum)
case h :: t =>
logger.debug("We have an odd amount of txids")
logger.debug("Hashes: " + hashes.map(_.hex))
//means that we have an odd amount of txids, this means we duplicate the last hash in the tree
val hash = CryptoUtil.doubleSHA256(h.bytes ++ h.bytes)
computeMerkleRoot(t,hash :: accum)
}
/**
* Computes the merkle root for the given sequence of transactions
* @param transactions the list of transactions whose merkle root needs to be computed
@ -58,8 +32,45 @@ trait Merkle extends BitcoinSLogger {
case Nil => throw new IllegalArgumentException("We cannot have zero transactions in the block. There always should be ATLEAST one - the coinbase tx")
case h :: Nil => h.txId
case h :: t =>
val txHashes = transactions.map(tx => tx.txId)
computeMerkleRoot(txHashes)
val leafs = transactions.map(tx => Leaf(tx.txId))
val merkleTree = build(leafs,Nil)
merkleTree.value.get
}
/** Builds a [[MerkleTree]] from sequence of sub merkle trees.
* This subTrees can be individual txids (leafs) or full blown subtrees
* @param subTrees the trees that need to be hashed
* @param accum the accumulated merkle trees, waiting to be hashed next round
* @return the entire Merkle tree computed from the given merkle trees
*/
@tailrec
final def build(subTrees: Seq[MerkleTree], accum: Seq[MerkleTree]): MerkleTree = subTrees match {
case Nil =>
if (accum.size == 1) accum.head
else if (accum.isEmpty) throw new IllegalArgumentException("Should never have sub tree size of zero, this implies there was zero hashes given")
else build(accum.reverse, Nil)
case h :: h1 :: t =>
logger.debug("Subtrees: " + subTrees)
val newTree = computeTree(h,h1)
build(t, newTree +: accum)
case h :: t =>
logger.debug("Subtrees: " + subTrees)
//means that we have an odd amount of txids, this means we duplicate the last hash in the tree
val newTree = computeTree(h,h)
build(t, newTree +: accum)
}
/** Builds a merkle tree from a sequence of hashes */
def build(hashes: Seq[DoubleSha256Digest]): MerkleTree = {
val leafs = hashes.map(Leaf(_))
build(leafs,Nil)
}
/** Computes the merkle tree of two sub merkle trees */
def computeTree(tree1: MerkleTree, tree2: MerkleTree): MerkleTree = {
val bytes = tree1.value.get.bytes ++ tree2.value.get.bytes
val hash = CryptoUtil.doubleSHA256(bytes)
Node(hash,tree1,tree2)
}
}

View file

@ -24,7 +24,7 @@ class MerkleTest extends FlatSpec with MustMatchers {
require(tx1.txId.hex == BitcoinSUtil.flipEndianess("5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2"))
val transactions = Seq(coinbaseTx,tx1)
Merkle.computeMerkleRoot(transactions).hex must be ("8fb300e3fdb6f30a4c67233b997f99fdd518b968b9a3fd65857bfe78b2600719")
Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("8fb300e3fdb6f30a4c67233b997f99fdd518b968b9a3fd65857bfe78b2600719"))
}
it must "correctly compute the merkle root for a block with 3 transactions" in {
@ -37,7 +37,7 @@ class MerkleTest extends FlatSpec with MustMatchers {
require(tx2.txId.hex == BitcoinSUtil.flipEndianess("d8c9d6a13a7fb8236833b1e93d298f4626deeb78b2f1814aa9a779961c08ce39"))
val transactions = Seq(coinbaseTx, tx1, tx2)
Merkle.computeMerkleRoot(transactions).hex must be ("d277b5d20fab7cdb8140ab953323b585445d4920ad7226623d9c7ed0bc6b9a57")
Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("d277b5d20fab7cdb8140ab953323b585445d4920ad7226623d9c7ed0bc6b9a57"))
}
it must "correctly compute the merkle root for the 100,000 block which has 4 transactions" in {
@ -50,7 +50,7 @@ class MerkleTest extends FlatSpec with MustMatchers {
val transactions = Seq(coinbaseTx, tx1,tx2,tx3)
Merkle.computeMerkleRoot(transactions).hex must be ("f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766")
Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766"))
}
@ -68,7 +68,7 @@ class MerkleTest extends FlatSpec with MustMatchers {
require(tx4.txId.hex == BitcoinSUtil.flipEndianess("b79606d7377285e1a4f9cb757c9f1a1d36f09c10bc52be49b170c6dd40767669"))
val transactions = Seq(coinbaseTx,tx1,tx2,tx3,tx4)
Merkle.computeMerkleRoot(transactions).hex must be ("36b38854f9adf76b4646ab2c0f949846408cfab2c045f110d01f84f4122c5add")
Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("36b38854f9adf76b4646ab2c0f949846408cfab2c045f110d01f84f4122c5add"))
}
it must "correctly compute the merkle root for a block with 11 transactions" in {
@ -98,6 +98,6 @@ class MerkleTest extends FlatSpec with MustMatchers {
val transactions = Seq(coinbaseTx,tx1,tx2,tx3,tx4,tx5,tx6,tx7,tx8,tx9,tx10)
Merkle.computeMerkleRoot(transactions).hex must be ("2c8230dfb6c7ad0949cbdb7d3c3616cc10770db7e832bbf5c9b261388828c6c4")
Merkle.computeMerkleRoot(transactions).hex must be (BitcoinSUtil.flipEndianess("2c8230dfb6c7ad0949cbdb7d3c3616cc10770db7e832bbf5c9b261388828c6c4"))
}
}