Completed
Pull Request — master (#255)
by thomas
90:53 queued 19:51
created

MerkleRoot::calculateHash()   C

Complexity

Conditions 7
Paths 9

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 7

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 42
ccs 16
cts 16
cp 1
rs 6.7272
cc 7
eloc 21
nc 9
nop 1
crap 7
1
<?php
2
3
namespace BitWasp\Bitcoin\Block;
4
5
use BitWasp\Bitcoin\Math\Math;
6
use BitWasp\Bitcoin\Collection\Transaction\TransactionCollection;
7
use BitWasp\Buffertools\Buffer;
8
use BitWasp\Buffertools\BufferInterface;
9
use BitWasp\Bitcoin\Exceptions\MerkleTreeEmpty;
10
use Pleo\Merkle\FixedSizeTree;
11
12
class MerkleRoot
13
{
14
    /**
15
     * @var TransactionCollection
16
     */
17
    private $transactions;
18
19
    /**
20
     * @var Math
21
     */
22
    private $math;
23
24
    /**
25
     * @var BufferInterface
26
     */
27
    private $lastHash;
28
29
    /**
30
     * Instantiate the class when given a block
31
     *
32
     * @param Math $math
33
     * @param TransactionCollection $txCollection
34
     */
35
    public function __construct(Math $math, TransactionCollection $txCollection)
36 48
    {
37
        $this->math = $math;
38 48
        $this->transactions = $txCollection;
39 48
    }
40 48
41
    /**
42
     * @param callable|null $hashFunction
43
     * @return BufferInterface
44
     * @throws MerkleTreeEmpty
45 42
     */
46
    public function calculateHash(callable $hashFunction = null)
47 42
    {
48
        if ($this->lastHash instanceof BufferInterface) {
49
            return $this->lastHash;
50
        }
51
52
        $hashFxn = $hashFunction ?: function ($value) {
53
            return hash('sha256', hash('sha256', $value, true), true);
54
        };
55 42
56
        $txCount = count($this->transactions);
57 42
58 42
        if ($txCount === 0) {
59
            // TODO: Probably necessary. Should always have a coinbase at least.
60
            throw new MerkleTreeEmpty('Cannot compute Merkle root of an empty tree');
61
        }
62
63
        if ($txCount === 1) {
64
            $binary = $hashFxn($this->transactions[0]->getBinary());
65
66
        } else {
67 48
            // Create a fixed size Merkle Tree
68 42
            $tree = new FixedSizeTree($txCount + ($txCount % 2), $hashFxn);
69 48
70
            // Compute hash of each transaction
71 48
            $last = '';
72
            foreach ($this->transactions as $i => $transaction) {
73 48
                $last = $transaction->getBinary();
74
                $tree->set($i, $last);
75 6
            }
76
77
            // Check if we need to repeat the last hash (odd number of transactions)
78 42
            if (!$this->math->isEven($txCount)) {
79 30
                $tree->set($txCount, $last);
80
            }
81 30
82
            $binary = $tree->hash();
83 12
        }
84
85
        $this->lastHash = (new Buffer($binary))->flip();
86 12
        return $this->lastHash;
87 12
    }
88
}
89