Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix] optimize the BUMP calculation function to make it O(log(n)) #83

Merged
merged 8 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: latest
args: --deadline=480s --skip-dirs=vendor --tests
args: --tests
build:
strategy:
matrix:
Expand Down Expand Up @@ -54,6 +53,6 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
with:
version: latest
args: release --clean
args: release --rm-dist
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ run-unit-tests-cover:
open file:///$(shell pwd)/cover.html

run-linter:
@golangci-lint run --deadline=480s --skip-dirs=vendor --tests
@golangci-lint run --skip-dirs=vendor --tests

install-linter:
@curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)bin v1.45.2
Expand Down
93 changes: 42 additions & 51 deletions bump.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,69 +219,60 @@ func (bump *BUMP) CalculateRootGivenTxid(txid string) (string, error) {

// NewBUMPFromMerkleTreeAndIndex with merkle tree we calculate the merkle path for a given transaction.
func NewBUMPFromMerkleTreeAndIndex(blockHeight uint64, merkleTree []*chainhash.Hash, txIndex uint64) (*BUMP, error) {
if len(merkleTree) == 0 {
return nil, errors.New("merkle tree is empty")
}

bump := &BUMP{
BlockHeight: blockHeight,
Path: [][]leaf{},
}
t := true

truePointer := true
txid := merkleTree[txIndex].String()
txidLeaf := leaf{Txid: &truePointer, Hash: &txid, Offset: &txIndex}

if len(merkleTree) == 1 {
// there is no merkle path to calculate
bump.Path = [][]leaf{{txidLeaf}}
return bump, nil
}

oddTxIndex := false

numOfTxids := (len(merkleTree) + 1) / 2
treeHeight := int(math.Log2(float64(numOfTxids)))
numOfHashes := numOfTxids
bump.Path = make([][]leaf, treeHeight)

if len(merkleTree) == 0 {
return nil, errors.New("merkle tree is empty")
}

// these are the offsets for the txid we're interested in.
offsets := make([]uint64, treeHeight)
for i := 0; i < treeHeight; i++ {
if txIndex>>uint64(i)&1 == 0 {
offsets[i] = txIndex>>uint64(i) + 1
levelOffset := 0
for height := 0; height < treeHeight; height++ {
offset := txIndex >> uint64(height)
if offset&1 == 0 {
// offset is even we need to use the hash to the right.
offset++
} else {
offsets[i] = txIndex>>uint64(i) - 1
// we need to use the hash to the left.
oddTxIndex = true
offset--
}
}

// if we have only one transaction in the block there is no merkle path to calculate
if len(merkleTree) != 1 {
// if our hash index is odd the next hash of the path is the previous element in the array otherwise the next element.
levelOffset := 0
for height := 0; height < treeHeight; height++ {
leaves := []leaf{}
bump.Path = append(bump.Path, leaves)
for offset := 0; offset < numOfHashes; offset++ {
o := uint64(offset)
// only include the hashes for the txid we're interested in.
if height == 0 {
if o != txIndex && o != offsets[height] {
continue
}
} else {
if o != offsets[height] {
continue
}
}
thisLeaf := leaf{Offset: &o}
hash := merkleTree[levelOffset+offset]
if hash.IsEqual(nil) {
thisLeaf.Duplicate = &t
} else {
sh := hash.String()
thisLeaf.Hash = &sh
if height == 0 && txIndex == o {
thisLeaf.Txid = &t
}
}
bump.Path[height] = append(bump.Path[height], thisLeaf)
}
levelOffset += numOfHashes
numOfHashes >>= 1
thisLeaf := leaf{Offset: &offset}
hash := merkleTree[levelOffset+int(offset)]
if hash.IsEqual(nil) {
thisLeaf.Duplicate = &truePointer
} else {
sh := hash.String()
thisLeaf.Hash = &sh
}
bump.Path[height] = []leaf{thisLeaf}
levelOffset += numOfHashes
numOfHashes >>= 1
}
if oddTxIndex {
// if the txIndex is odd we need to add the txid to the path.
bump.Path[0] = append(bump.Path[0], txidLeaf)
} else {
sh := merkleTree[0].String()
o := uint64(0)
bump.Path = [][]leaf{{leaf{Hash: &sh, Offset: &o, Txid: &t}}}
// otherwise prepend it.
bump.Path[0] = append([]leaf{txidLeaf}, bump.Path[0]...)
}

return bump, nil
Expand Down
33 changes: 32 additions & 1 deletion bump_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package bc

import (
"crypto/rand"
"math"
"testing"

Expand Down Expand Up @@ -183,8 +184,38 @@ func TestOnlySpecifiedPathsStored(t *testing.T) {
totalHashes += len(level)
}
// number of levels plus the txid itself.
l := int(math.Log2(float64(len(blockTxExample)))) + 1
l := int(math.Ceil(math.Log2(float64(len(blockTxExample))))) + 1
require.Equal(t, l, totalHashes)
}

}

func BenchmarkNewBUMPFromMerkleTreeAndIndex(b *testing.B) {
transactions := 100000
// test how quickly we can calculate the BUMP Merkle Paths from a block of 100,000 random txids
chainHashBlock := make([]*chainhash.Hash, 0)
for i := 0; i < transactions; i++ {
bytes := make([]byte, 32)
_, _ = rand.Read(bytes)
hash, err := chainhash.NewHash(bytes)
if err != nil {
b.Fatal(err)
}
chainHashBlock = append(chainHashBlock, hash)
}
merkles := BuildMerkleTreeStoreChainHash(chainHashBlock)

b.ResetTimer()

for idx := 0; idx < transactions; idx++ {
bump, err := NewBUMPFromMerkleTreeAndIndex(850000, merkles, uint64(idx))
require.NoError(b, err)
totalHashes := 0
for _, level := range bump.Path {
totalHashes += len(level)
}
// number of levels plus the txid itself.
l := int(math.Ceil(math.Log2(float64(transactions)))) + 1
require.Equal(b, l, totalHashes)
}
}
Loading
Loading