Skip to content

Commit

Permalink
Merge pull request #72 from libsv/block-with-single-transaction
Browse files Browse the repository at this point in the history
Fix calculating merkle path with only one transaction in the block. F…
  • Loading branch information
sirdeggen authored Sep 19, 2023
2 parents 23c0125 + 9a82611 commit e0bb2d9
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 6 deletions.
27 changes: 21 additions & 6 deletions merklepath.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,25 @@ func (mp *MerklePath) CalculateRoot(txid string) (string, error) {
return StringFromBytesReverse(workingHash), nil
}

// getPathElements traverses the tree and returns the path to coinbase.
// getPathElements traverses the tree and returns the path to Merkle root.
func getPathElements(txIndex int, hashes []string) []string {
// if our hash index is odd the next hash of the path is the previous element in the array otherwise the next element.
var path []string
var hash string
if txIndex%2 == 0 {
path = append(path, hashes[txIndex+1])
hash = hashes[txIndex+1]
} else {
path = append(path, hashes[txIndex-1])
hash = hashes[txIndex-1]
}

// If we reach the coinbase hash stop path calculation.
// when generating path if the neighbour is empty we append itself
if hash == "" {
path = append(path, hashes[txIndex])
} else {
path = append(path, hash)
}

// If we reach the Merkle root hash stop path calculation.
if len(hashes) == 3 {
return path
}
Expand All @@ -137,8 +145,15 @@ func getPathElements(txIndex int, hashes []string) []string {

// GetTxMerklePath with merkle tree we calculate the merkle path for a given transaction.
func GetTxMerklePath(txIndex int, merkleTree []string) *MerklePath {
return &MerklePath{
merklePath := &MerklePath{
Index: uint64(txIndex),
Path: getPathElements(txIndex, merkleTree),
Path: nil,
}

// if we have only one transaction in the block there is no merkle path to calculate
if len(merkleTree) != 1 {
merklePath.Path = getPathElements(txIndex, merkleTree)
}

return merklePath
}
49 changes: 49 additions & 0 deletions merklepath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package bc_test

import (
"encoding/hex"
"encoding/json"
"testing"

"github.com/libsv/go-bc"
Expand Down Expand Up @@ -94,3 +95,51 @@ func TestGetMerklePath(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, expected, root)
}

func TestGetMerklePathOddPosition(t *testing.T) {
txids := []string{
"b6d4d13aa08bb4b6cdb3b329cef29b5a5d55d85a85c330d56fddbce78d99c7d6",
"426f65f6a6ce79c909e54d8959c874a767db3076e76031be70942b896cc64052",
"adc23d36cc457d5847968c2e4d5f017a6f12a2f165102d10d2843f5276cfe68e",
"728714bbbddd81a54cae473835ae99eb92ed78191327eb11a9d7494273dcad2a",
"e3aa0230aa81abd483023886ad12790acf070e2a9f92d7f0ae3bebd90a904361",
}

merkles, err := bc.BuildMerkleTreeStore(txids)
assert.NoError(t, err)

// build path for tx index 4.
path := bc.GetTxMerklePath(4, merkles)
root, err := bc.MerkleRootFromBranches("e3aa0230aa81abd483023886ad12790acf070e2a9f92d7f0ae3bebd90a904361", int(path.Index), path.Path)
assert.NoError(t, err)
assert.Equal(t, merkles[len(merkles)-1], root)
}

func TestGetMerklePathEmptyPath(t *testing.T) {
txids := []string{
"b6d4d13aa08bb4b6cdb3b329cef29b5a5d55d85a85c330d56fddbce78d99c7d6",
}

merkles, err := bc.BuildMerkleTreeStore(txids)
assert.NoError(t, err)

// build path for tx index 4.
path := bc.GetTxMerklePath(0, merkles)
root, err := bc.MerkleRootFromBranches("b6d4d13aa08bb4b6cdb3b329cef29b5a5d55d85a85c330d56fddbce78d99c7d6", int(path.Index), path.Path)
assert.NoError(t, err)
assert.Equal(t, merkles[len(merkles)-1], root)
assert.Equal(t, ([]string)(nil), path.Path)
assert.Equal(t, uint64(0), path.Index)
}

func TestGetMerklePathEmptyPathJson(t *testing.T) {
txids := []string{
"b6d4d13aa08bb4b6cdb3b329cef29b5a5d55d85a85c330d56fddbce78d99c7d6",
}

merkles, _ := bc.BuildMerkleTreeStore(txids)
path := bc.GetTxMerklePath(0, merkles)
js, err := json.Marshal(path)
assert.NoError(t, err)
assert.Equal(t, string(js), "{\"index\":0,\"path\":null}")
}

0 comments on commit e0bb2d9

Please sign in to comment.