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

feat(BUX-5, BUX-176): Merkle Proofs and Compound Merkle Path #381

Merged
merged 1 commit into from
Sep 11, 2023
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
30 changes: 15 additions & 15 deletions chainstate/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"time"

"github.com/BuxOrg/bux/utils"
"github.com/libsv/go-bc"
)

// Chainstate configuration defaults
Expand Down Expand Up @@ -53,20 +54,19 @@ const (

// TransactionInfo is the universal information about the transaction found from a chain provider
type TransactionInfo struct {
BlockHash string `json:"block_hash,omitempty"` // mAPI, WOC
BlockHeight int64 `json:"block_height"` // mAPI, WOC
Confirmations int64 `json:"confirmations,omitempty"` // mAPI, WOC
ID string `json:"id"` // Transaction ID (Hex)
MinerID string `json:"miner_id,omitempty"` // mAPI ONLY - miner_id found
Provider string `json:"provider,omitempty"` // Provider is our internal source
BlockHash string `json:"block_hash,omitempty"` // mAPI, WOC
BlockHeight int64 `json:"block_height"` // mAPI, WOC
Confirmations int64 `json:"confirmations,omitempty"` // mAPI, WOC
ID string `json:"id"` // Transaction ID (Hex)
MinerID string `json:"miner_id,omitempty"` // mAPI ONLY - miner_id found
Provider string `json:"provider,omitempty"` // Provider is our internal source
MerkleProof *bc.MerkleProof `json:"merkle_proof,omitempty"` // mAPI 1.5 ONLY. Should be also supported by Arc in future
}

var (
// DefaultFee is used when a fee has not been set by the user
// This default is currently accepted by all BitcoinSV miners (50/1000) (7.27.23)
// Actual TAAL FeeUnit - 1/1000, GorillaPool - 50/1000 (7.27.23)
DefaultFee = &utils.FeeUnit{
Satoshis: 1,
Bytes: 20,
}
)
// DefaultFee is used when a fee has not been set by the user
// This default is currently accepted by all BitcoinSV miners (50/1000) (7.27.23)
// Actual TAAL FeeUnit - 1/1000, GorillaPool - 50/1000 (7.27.23)
var DefaultFee = &utils.FeeUnit{
Satoshis: 1,
Bytes: 20,
}
3 changes: 2 additions & 1 deletion chainstate/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func (c *Client) fastestQuery(ctx context.Context, id string, requiredIn Require
// queryMinercraft will submit a query transaction request to a miner using Minercraft(mAPI or Arc)
func queryMinercraft(ctx context.Context, client ClientInterface, miner *minercraft.Miner, id string) (*TransactionInfo, error) {
client.DebugLog("executing request in minercraft using miner: " + miner.Name)
if resp, err := client.Minercraft().QueryTransaction(ctx, miner, id); err != nil {
if resp, err := client.Minercraft().QueryTransaction(ctx, miner, id, minercraft.WithQueryMerkleProof()); err != nil {
client.DebugLog("error executing request in minercraft using miner: " + miner.Name + " failed: " + err.Error())
return nil, err
} else if resp != nil && resp.Query.ReturnResult == mAPISuccess && strings.EqualFold(resp.Query.TxID, id) {
Expand All @@ -170,6 +170,7 @@ func queryMinercraft(ctx context.Context, client ClientInterface, miner *minercr
ID: resp.Query.TxID,
MinerID: resp.Query.MinerID,
Provider: miner.Name,
MerkleProof: resp.Query.MerkleProof,
}, nil
}
return nil, ErrTransactionIDMismatch
Expand Down
127 changes: 127 additions & 0 deletions model_compound_merkle_path.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package bux

import (
"bytes"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"reflect"
"sort"
)

// CompoundMerklePath represents Compound Merkle Path type
type CompoundMerklePath []map[string]uint64

// CMPSlice represents slice of Compound Merkle Pathes
// There must be several CMPs in case if utxos from different blocks is used in tx
type CMPSlice []CompoundMerklePath

type nodeOffset struct {
node string
offset uint64
}

// Hex returns CMP in hex format
func (cmp *CompoundMerklePath) Hex() string {
var hex bytes.Buffer
hex.WriteString(leadingZeroInt(len(*cmp)))

for _, m := range *cmp {
hex.WriteString(leadingZeroInt(len(m)))
sortedNodes := sortByOffset(m)
for _, n := range sortedNodes {
hex.WriteString(leadingZeroInt(int(n.offset)))
hex.WriteString(n.node)
}
}
return hex.String()
}

func sortByOffset(m map[string]uint64) []nodeOffset {
n := make([]nodeOffset, 0)
for node, offset := range m {
n = append(n, nodeOffset{node, offset})
}
sort.Slice(n, func(i, j int) bool {
return n[i].offset < n[j].offset
})
return n
}

// CalculateCompoundMerklePath calculates CMP from a slice of Merkle Proofs
func CalculateCompoundMerklePath(mp []MerkleProof) (CompoundMerklePath, error) {
if len(mp) == 0 || mp == nil {
return CompoundMerklePath{}, nil
}
height := len(mp[0].Nodes)
for _, m := range mp {
if height != len(m.Nodes) {
return nil,
errors.New("Compound Merkle Path cannot be obtained from Merkle Proofs of different heights")
}
}
cmp := make(CompoundMerklePath, height)
for _, m := range mp {
cmpToAdd := m.ToCompoundMerklePath()
err := cmp.add(cmpToAdd)
if err != nil {
return CompoundMerklePath{}, err
}
}
return cmp, nil
}

// 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("%02d", i)
}

func (cmp *CompoundMerklePath) add(c CompoundMerklePath) error {
if len(*cmp) != len(c) {
return errors.New("Compound Merkle Path with different height cannot be added")
}
for i := range c {
for k, v := range c[i] {
if (*cmp)[i] == nil {
(*cmp)[i] = c[i]
break
}
(*cmp)[i][k] = v
}
}
return nil
}

// Scan scan value into Json, implements sql.Scanner interface
func (cmps *CMPSlice) Scan(value interface{}) error {
if value == nil {
return nil
}

xType := fmt.Sprintf("%T", value)
var byteValue []byte
if xType == ValueTypeString {
byteValue = []byte(value.(string))
} else {
byteValue = value.([]byte)
}
if bytes.Equal(byteValue, []byte("")) || bytes.Equal(byteValue, []byte("\"\"")) {
return nil
}

return json.Unmarshal(byteValue, &cmps)
}

// Value return json value, implement driver.Valuer interface
func (cmps CMPSlice) Value() (driver.Value, error) {
if reflect.DeepEqual(cmps, CMPSlice{}) {
return nil, nil
}
marshal, err := json.Marshal(cmps)
if err != nil {
return nil, err
}

return string(marshal), nil
}
Loading