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

feat(BUX-294): Replacing CMPSlice with BUMPPaths in DraftTransaction #448

Merged
merged 6 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
156 changes: 147 additions & 9 deletions model_bump.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,165 @@ package bux
import (
"bytes"
"database/sql/driver"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"

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

// BUMP represents BUMP format
// BUMPPaths represents a slice of BUMPs (BSV Unified Merkle Paths)
type BUMPPaths []BUMP

// BUMP represents BUMP (BSV Unified Merkle Path) format
type BUMP struct {
BlockHeight uint64 `json:"blockHeight,string"`
Path []BUMPPathMap `json:"path"`
BlockHeight uint64 `json:"blockHeight,string"`
Path [][]BUMPNode `json:"path"`
// private field for storing already used offsets to avoid duplicate nodes
allNodes []map[uint64]bool
}

// BUMPPathMap represents map with pathes
type BUMPPathMap map[string]BUMPPathElement

// BUMPPathElement represents each BUMP path element
type BUMPPathElement struct {
Hash string `json:"hash,omitempty"`
// BUMPLeaf represents each BUMP path element
type BUMPNode struct {
Offset uint64 `json:"offset,string"`
Hash string `json:"hash"`
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
}

height := len(mp[0].Nodes)
if height > maxCmpHeight {
return bump,
fmt.Errorf("BUMP cannot be higher than %d", maxCmpHeight)
}

for _, m := range mp {
if height != len(m.Nodes) {
return bump,
errors.New("Merged BUMP cannot be obtained from Merkle Proofs of different heights")
}
}

bump.Path = make([][]BUMPNode, height)
bump.allNodes = make([]map[uint64]bool, height)
for i := range bump.allNodes {
bump.allNodes[i] = make(map[uint64]bool, 0)
}

for _, m := range mp {
bumpToAdd := m.ToBUMP()
err := bump.add(bumpToAdd)
if err != nil {
return BUMP{}, err
}
}

for _, p := range bump.Path {
sort.Slice(p, func(i, j int) bool {
return p[i].Offset < p[j].Offset
})
}

return bump, nil
}

func (bump *BUMP) add(b BUMP) error {
if len(bump.Path) != len(b.Path) {
return errors.New("BUMPs with different heights cannot be merged")
}

for i := range b.Path {
for _, v := range b.Path[i] {
_, value := bump.allNodes[i][v.Offset]
if !value {
bump.Path[i] = append(bump.Path[i], v)
bump.allNodes[i][v.Offset] = true
continue
}
if i == 0 && value && v.TxId {
for j := range bump.Path[i] {
if bump.Path[i][j].Offset == v.Offset {
bump.Path[i][j] = v
}
}
}
kuba-4chain marked this conversation as resolved.
Show resolved Hide resolved
}
}

return nil
}

// Bytes returns CMPSlice bytes
wregulski marked this conversation as resolved.
Show resolved Hide resolved
func (bumpPaths *BUMPPaths) Bytes() []byte {
var buff bytes.Buffer

for _, bump := range *bumpPaths {
bytes, _ := hex.DecodeString(bump.Hex())
buff.Write(bytes)
}

return buff.Bytes()
}

// Hex returns BUMP in hex format
func (bump *BUMP) Hex() string {
return bump.bytesBuffer().String()
}

func (bump *BUMP) bytesBuffer() *bytes.Buffer {
var buff bytes.Buffer
buff.WriteString(hex.EncodeToString(bt.VarInt(bump.BlockHeight).Bytes()))

height := len(bump.Path)
buff.WriteString(leadingZeroInt(height))

for i := 0; i < height; i++ {
nodes := bump.Path[i]

nLeafs := len(nodes)
buff.WriteString(hex.EncodeToString(bt.VarInt(nLeafs).Bytes()))
for _, n := range nodes {
buff.WriteString(hex.EncodeToString(bt.VarInt(n.Offset).Bytes()))
buff.WriteString(fmt.Sprintf("%02x", flags(n.TxId, n.Duplicate)))
decodedHex, _ := hex.DecodeString(n.Hash)
buff.WriteString(hex.EncodeToString(bt.ReverseBytes(decodedHex)))
}
}
return &buff
}

// In case the offset or height is less than 10, they must be written with a leading zero
func leadingZeroInt(i int) string {
return fmt.Sprintf("%02x", i)
}

func flags(txId, duplicate bool) byte {
var (
dataFlag byte = 00
duplicateFlag byte = 01
txIdFlag byte = 02
)

if duplicate {
return duplicateFlag
}
if txId {
return txIdFlag
}
return dataFlag
}

// Scan scan value into Json, implements sql.Scanner interface
func (m *BUMP) Scan(value interface{}) error {
if value == nil {
Expand Down
Loading
Loading