blockchain: Add CalcMerkleRoot

CalcMerkleRoot uses the rolling merkle root algorithm to calculate the
merkle root commitment inside the Bitcoin block header.  It allocates
significantly less memory than the BuildMerkleTreeStore function that's
currently in use (99.9% in an average block with 2000 txs).
This commit is contained in:
Conner Fromknecht 2019-04-22 18:28:59 -07:00 committed by Calvin Kim
parent 2d23f94002
commit 025fa65c93
2 changed files with 41 additions and 6 deletions

View File

@ -9,9 +9,9 @@ import (
"fmt"
"math"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/btcutil"
)
const (
@ -85,7 +85,7 @@ func HashMerkleBranches(left, right *chainhash.Hash) chainhash.Hash {
//
// The above stored as a linear array is as follows:
//
// [h1 h2 h3 h4 h12 h34 root]
// [h1 h2 h3 h4 h12 h34 root]
//
// As the above shows, the merkle root is always the last element in the array.
//
@ -153,6 +153,36 @@ func BuildMerkleTreeStore(transactions []*btcutil.Tx, witness bool) []*chainhash
return merkles
}
// CalcMerkleRoot computes the merkle root over a set of hashed leaves. The
// interior nodes are computed opportunistically as the leaves are added to the
// abstract tree to reduce the total number of allocations. Throughout the
// computation, this computation only requires storing O(log n) interior
// nodes.
//
// This method differs from BuildMerkleTreeStore in that the interior nodes are
// discarded instead of being returned along with the root. CalcMerkleRoot is
// slightly faster than BuildMerkleTreeStore and requires significantly less
// memory and fewer allocations.
//
// A merkle tree is a tree in which every non-leaf node is the hash of its
// children nodes. A diagram depicting how this works for bitcoin transactions
// where h(x) is a double sha256 follows:
//
// root = h1234 = h(h12 + h34)
// / \
// h12 = h(h1 + h2) h34 = h(h3 + h4)
// / \ / \
// h1 = h(tx1) h2 = h(tx2) h3 = h(tx3) h4 = h(tx4)
//
// The additional bool parameter indicates if we are generating the merkle tree
// using witness transaction id's rather than regular transaction id's. This
// also presents an additional case wherein the wtxid of the coinbase transaction
// is the zeroHash.
func CalcMerkleRoot(transactions []*btcutil.Tx, witness bool) chainhash.Hash {
s := newRollingMerkleTreeStore(uint64(len(transactions)))
return s.calcMerkleRoot(transactions, witness)
}
// ExtractWitnessCommitment attempts to locate, and return the witness
// commitment for a block. The witness commitment is of the form:
// SHA256(witness root || witness nonce). The function additionally returns a

View File

@ -11,17 +11,22 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/stretchr/testify/require"
)
// TestMerkle tests the BuildMerkleTreeStore API.
func TestMerkle(t *testing.T) {
block := btcutil.NewBlock(&Block100000)
merkles := BuildMerkleTreeStore(block.Transactions(), false)
calculatedMerkleRoot := merkles[len(merkles)-1]
calcMerkleRoot := CalcMerkleRoot(block.Transactions(), false)
merkleStoreTree := BuildMerkleTreeStore(block.Transactions(), false)
merkleStoreRoot := merkleStoreTree[len(merkleStoreTree)-1]
require.Equal(t, *merkleStoreRoot, calcMerkleRoot)
wantMerkle := &Block100000.Header.MerkleRoot
if !wantMerkle.IsEqual(calculatedMerkleRoot) {
if !wantMerkle.IsEqual(&calcMerkleRoot) {
t.Errorf("BuildMerkleTreeStore: merkle root mismatch - "+
"got %v, want %v", calculatedMerkleRoot, wantMerkle)
"got %v, want %v", calcMerkleRoot, wantMerkle)
}
}