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

Commit

Permalink
feat: Retrieveing Merkle proofs from mAPI and Calculation of Compound…
Browse files Browse the repository at this point in the history
… Merkle Path
  • Loading branch information
vitalibalashka committed Sep 8, 2023
1 parent 7567cd0 commit 582fa40
Show file tree
Hide file tree
Showing 10 changed files with 604 additions and 39 deletions.
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

0 comments on commit 582fa40

Please sign in to comment.