Skip to content
This repository has been archived by the owner on Apr 2, 2024. It is now read-only.

fix(BUX-250): BUMP-related fixes found during testing #466

Merged
merged 8 commits into from
Nov 8, 2023
20 changes: 2 additions & 18 deletions beef_tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,7 @@ import (
)

func Test_ToBeefHex(t *testing.T) {
t.Run("all parents txs are already mined", func(t *testing.T) {
// given
ctx, client, deferMe := initSimpleTestCase(t)
defer deferMe()

ancestorTx := addGrandpaTx(ctx, t, client)
minedParentTx := createTxWithDraft(ctx, t, client, ancestorTx, true)

newTx := createTxWithDraft(ctx, t, client, minedParentTx, false)

// when
hex, err := ToBeefHex(ctx, newTx)

// then
assert.NoError(t, err)
assert.NotEmpty(t, hex)
})
// TOOD: prepare tests in BUX-168

t.Run("some parents txs are not mined yet", func(t *testing.T) {
// Error expected! this should be changed in the future. right now the test case has been written to make sure the system doesn't panic in such a situation
Expand Down Expand Up @@ -64,7 +48,7 @@ func addGrandpaTx(ctx context.Context, t *testing.T, client ClientInterface) *Tr
},
}
grandpaTx.MerkleProof = MerkleProof(grandpaTxMp)
grandpaTx.BUMP = grandpaTx.MerkleProof.ToBUMP()
grandpaTx.BUMP = grandpaTx.MerkleProof.ToBUMP(grandpaTx.BlockHeight)
err := grandpaTx.Save(ctx)
require.NoError(t, err)

Expand Down
3 changes: 1 addition & 2 deletions db_model_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ func (m *Transaction) migrateBUMP() error {
return err
}
for _, tx := range txs {
bump := tx.MerkleProof.ToBUMP()
bump.BlockHeight = tx.BlockHeight
bump := tx.MerkleProof.ToBUMP(tx.BlockHeight)
tx.BUMP = bump
_ = tx.Save(ctx)
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 3 additions & 14 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

153 changes: 133 additions & 20 deletions model_bump.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (
"reflect"
"sort"

"github.com/libsv/go-bc"
"github.com/libsv/go-bt/v2"
)

const maxBumpHeight = 64

// BUMPs represents a slice of BUMPs - BSV Unified Merkle Paths
type BUMPs []BUMP
type BUMPs []*BUMP

// BUMP represents BUMP (BSV Unified Merkle Path) format
type BUMP struct {
Expand All @@ -29,43 +30,64 @@ type BUMP struct {
// BUMPLeaf represents each BUMP path element
type BUMPLeaf struct {
Offset uint64 `json:"offset,string"`
Hash string `json:"hash"`
Hash string `json:"hash,omitempty"`
TxID bool `json:"txid,omitempty"`
Duplicate bool `json:"duplicate,omitempty"`
}

// CalculateMergedBUMP calculates Merged BUMP from a slice of Merkle Proofs
func CalculateMergedBUMP(mp []MerkleProof) (BUMP, error) {
bump := BUMP{}

if len(mp) == 0 || mp == nil {
return bump, nil
func CalculateMergedBUMP(bumps []BUMP) (*BUMP, error) {
if len(bumps) == 0 || bumps == nil {
return nil, nil
}

height := len(mp[0].Nodes)
if height > maxBumpHeight {
return bump,
blockHeight := bumps[0].BlockHeight
bumpHeight := len(bumps[0].Path)
if bumpHeight > maxBumpHeight {
return nil,
fmt.Errorf("BUMP cannot be higher than %d", maxBumpHeight)
}

for _, m := range mp {
if height != len(m.Nodes) {
return bump,
for _, b := range bumps {
if bumpHeight != len(b.Path) {
return nil,
errors.New("Merged BUMP cannot be obtained from Merkle Proofs of different heights")
}
if b.BlockHeight != blockHeight {
return nil,
errors.New("BUMPs have different block heights. Cannot merge BUMPs from different blocks")
}
if len(b.Path) == 0 {
return nil,
errors.New("Empty BUMP given")
}
}

bump.Path = make([][]BUMPLeaf, height)
bump.allNodes = make([]map[uint64]bool, height)
bump := BUMP{BlockHeight: blockHeight}
bump.Path = make([][]BUMPLeaf, bumpHeight)
bump.allNodes = make([]map[uint64]bool, bumpHeight)
for i := range bump.allNodes {
bump.allNodes[i] = make(map[uint64]bool, 0)
}

for _, m := range mp {
bumpToAdd := m.ToBUMP()
err := bump.add(bumpToAdd)
merkleRoot, err := bumps[0].calculateMerkleRoot()
if err != nil {
return nil, err
}

for _, b := range bumps {
mr, err := b.calculateMerkleRoot()
if err != nil {
return BUMP{}, err
return nil, err
}

if merkleRoot != mr {
return nil, errors.New("BUMPs have different merkle roots")
}

err = bump.add(b)
if err != nil {
return nil, err
}
}

Expand All @@ -75,7 +97,7 @@ func CalculateMergedBUMP(mp []MerkleProof) (BUMP, error) {
})
}

return bump, nil
return &bump, nil
}

func (bump *BUMP) add(b BUMP) error {
Expand Down Expand Up @@ -104,6 +126,97 @@ func (bump *BUMP) add(b BUMP) error {
return nil
}

func (b *BUMP) calculateMerkleRoot() (string, error) {
merkleRoot := ""

for _, bumpPathElement := range b.Path[0] {
if bumpPathElement.TxID {
calcMerkleRoot, err := calculateMerkleRoot(bumpPathElement, b)
if err != nil {
return "", err
}

if merkleRoot == "" {
merkleRoot = calcMerkleRoot
continue
}

if calcMerkleRoot != merkleRoot {
return "", errors.New("different merkle roots for the same block")
}
}
}
return merkleRoot, nil
}

// calculateMerkleRoots will calculate one merkle root for tx in the BUMPLeaf
func calculateMerkleRoot(baseLeaf BUMPLeaf, bump *BUMP) (string, error) {
calculatedHash := baseLeaf.Hash
offset := baseLeaf.Offset

for _, bLevel := range bump.Path {
newOffset := getOffsetPair(offset)
leafInPair := findLeafByOffset(newOffset, bLevel)
if leafInPair == nil {
return "", errors.New("could not find pair")
}

leftNode, rightNode := prepareNodes(baseLeaf, offset, *leafInPair, newOffset)

str, err := bc.MerkleTreeParentStr(leftNode, rightNode)
if err != nil {
return "", err
}
calculatedHash = str

offset = offset / 2

baseLeaf = BUMPLeaf{
Hash: calculatedHash,
Offset: offset,
}
}

return calculatedHash, nil
}

func findLeafByOffset(offset uint64, bumpLeaves []BUMPLeaf) *BUMPLeaf {
for _, bumpTx := range bumpLeaves {
if bumpTx.Offset == offset {
return &bumpTx
}
}
return nil
}

func getOffsetPair(offset uint64) uint64 {
if offset%2 == 0 {
return offset + 1
}
return offset - 1
}

func prepareNodes(baseLeaf BUMPLeaf, offset uint64, leafInPair BUMPLeaf, newOffset uint64) (string, string) {
var baseLeafHash, pairLeafHash string

if baseLeaf.Duplicate {
baseLeafHash = leafInPair.Hash
} else {
baseLeafHash = baseLeaf.Hash
}

if leafInPair.Duplicate {
pairLeafHash = baseLeaf.Hash
} else {
pairLeafHash = leafInPair.Hash
}

if newOffset > offset {
return baseLeafHash, pairLeafHash
}
return pairLeafHash, baseLeafHash
}

// Bytes returns BUMPs bytes
func (bumps *BUMPs) Bytes() []byte {
var buff bytes.Buffer
Expand Down
Loading