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

Commit

Permalink
Merge pull request #484 from BuxOrg/bux-166-merkle-path-bump
Browse files Browse the repository at this point in the history
feat(BUX-166): MerkleProof from mAPI and MerklePath from Arc unified to BUMP in Transaction model
  • Loading branch information
kuba-4chain authored Nov 29, 2023
2 parents ae75199 + 9ed0538 commit b65bc68
Show file tree
Hide file tree
Showing 11 changed files with 238 additions and 309 deletions.
12 changes: 0 additions & 12 deletions chainstate/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"time"

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

// Chainstate configuration defaults
Expand Down Expand Up @@ -52,17 +51,6 @@ const (
ProviderPulse = "pulse" // MerkleProof provider
)

// 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
MerkleProof *bc.MerkleProof `json:"merkle_proof,omitempty"` // mAPI 1.5 ONLY. Should be also supported by Arc in future
}

// 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)
Expand Down
11 changes: 6 additions & 5 deletions chainstate/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import (
"sync"
"time"

"github.com/BuxOrg/bux/utils"
"github.com/tonicpow/go-minercraft/v2"

"github.com/BuxOrg/bux/utils"
)

// query will try ALL providers in order and return the first "valid" response based on requirements
func (c *Client) query(ctx context.Context, id string, requiredIn RequiredIn,
timeout time.Duration) *TransactionInfo {

timeout time.Duration,
) *TransactionInfo {
// Create a context (to cancel or timeout)
ctxWithCancel, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
Expand Down Expand Up @@ -50,8 +51,8 @@ func (c *Client) query(ctx context.Context, id string, requiredIn RequiredIn,

// fastestQuery will try ALL providers on once and return the fastest "valid" response based on requirements
func (c *Client) fastestQuery(ctx context.Context, id string, requiredIn RequiredIn,
timeout time.Duration) *TransactionInfo {

timeout time.Duration,
) *TransactionInfo {
// The channel for the internal results
resultsChannel := make(
chan *TransactionInfo,
Expand Down
22 changes: 22 additions & 0 deletions chainstate/transaction_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package chainstate

import (
"github.com/libsv/go-bc"
)

// 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
MerkleProof *bc.MerkleProof `json:"merkle_proof,omitempty"` // mAPI 1.5 ONLY. Should be also supported by Arc in future
}

// Validate validates TransactionInfo by checking if it contains
// BlockHash and MerkleProof (from mAPI) or MerklePath (from Arc)
func (t *TransactionInfo) Valid() bool {
return !(t.BlockHash == "" || t.MerkleProof == nil || t.MerkleProof.TxOrID == "" || len(t.MerkleProof.Nodes) == 0)
}
19 changes: 0 additions & 19 deletions db_model_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,6 @@ func (m *Transaction) Migrate(client datastore.ClientInterface) error {
}
}

err := m.migrateBUMP()
if err != nil {
return err
}

return client.IndexMetadata(tableName, xPubMetadataField)
}

Expand Down Expand Up @@ -215,17 +210,3 @@ func (m *Transaction) migrateMySQL(client datastore.ClientInterface, tableName s

return nil
}

func (m *Transaction) migrateBUMP() error {
ctx := context.Background()
txs, err := getTransactionsToCalculateBUMP(ctx, nil, WithClient(m.client))
if err != nil {
return err
}
for _, tx := range txs {
bump := tx.MerkleProof.ToBUMP(tx.BlockHeight)
tx.BUMP = bump
_ = tx.Save(ctx)
}
return nil
}
67 changes: 67 additions & 0 deletions model_bump.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ func getOffsetPair(offset uint64) uint64 {
return offset - 1
}

func getParentOffset(offset uint64) uint64 {
return getOffsetPair(offset / 2)
}

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

Expand Down Expand Up @@ -342,3 +346,66 @@ func (bumps BUMPs) Value() (driver.Value, error) {

return string(marshal), nil
}

// MerkleProofToBUMP transforms Merkle Proof to BUMP
func MerkleProofToBUMP(merkleProof *bc.MerkleProof, blockHeight uint64) BUMP {
bump := BUMP{BlockHeight: blockHeight}

height := len(merkleProof.Nodes)
if height == 0 {
return bump
}

offset := merkleProof.Index
pairOffset := getOffsetPair(offset)

txIDPath1 := BUMPLeaf{
Offset: offset,
Hash: merkleProof.TxOrID,
TxID: true,
}
txIDPath2 := createLeaf(getOffsetPair(offset), merkleProof.Nodes[0])

path := sortAndAddToPath(txIDPath1, offset, txIDPath2, pairOffset)

for i := 1; i < height; i++ {
p := make([]BUMPLeaf, 0)
offset = getParentOffset(offset)

leaf := createLeaf(offset, merkleProof.Nodes[i])

p = append(p, leaf)
path = append(path, p)
}
bump.Path = path
return bump
}

func sortAndAddToPath(txIDPath1 BUMPLeaf, offset uint64, txIDPath2 BUMPLeaf, pairOffset uint64) [][]BUMPLeaf {
path := make([][]BUMPLeaf, 0)
txIDPath := make([]BUMPLeaf, 2)

if offset < pairOffset {
txIDPath[0] = txIDPath1
txIDPath[1] = txIDPath2
} else {
txIDPath[0] = txIDPath2
txIDPath[1] = txIDPath1
}

path = append(path, txIDPath)
return path
}

func createLeaf(offset uint64, node string) BUMPLeaf {
leaf := BUMPLeaf{Offset: offset}

isDuplicate := node == "*"
if !isDuplicate {
leaf.Hash = node
} else {
leaf.Duplicate = true
}

return leaf
}
125 changes: 123 additions & 2 deletions model_bump_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package bux
import (
"testing"

"github.com/libsv/go-bc"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -568,7 +569,7 @@ func TestBUMPModel_CalculateMergedBUMPAndHex(t *testing.T) {

t.Run("Real Merkle Proof", func(t *testing.T) {
// given
merkleProof := []MerkleProof{
merkleProof := []bc.MerkleProof{
{
Index: 1153,
TxOrID: "2130b63dcbfe1356a30137fe9578691f59c6cf42d5e8928a800619de7f8e14da",
Expand Down Expand Up @@ -797,7 +798,7 @@ func TestBUMPModel_CalculateMergedBUMPAndHex(t *testing.T) {
// when
bumps := make([]BUMP, 0)
for _, mp := range merkleProof {
bumps = append(bumps, mp.ToBUMP(0))
bumps = append(bumps, MerkleProofToBUMP(&mp, 0))
}
bump, err := CalculateMergedBUMP(bumps)
actualHex := bump.Hex()
Expand All @@ -808,3 +809,123 @@ func TestBUMPModel_CalculateMergedBUMPAndHex(t *testing.T) {
assert.Equal(t, expectedHex, actualHex)
})
}

// TestBUMPModel_MerkleProofToBUMP will test the method MerkleProofToBUMP()
func TestBUMPModel_MerkleProofToBUMP(t *testing.T) {
t.Parallel()

t.Run("Valid Merkle Proof #1", func(t *testing.T) {
// given
blockHeight := uint64(0)
mp := bc.MerkleProof{
Index: 1,
TxOrID: "txId",
Nodes: []string{"node0", "node1", "node2", "node3"},
}
expectedBUMP := BUMP{
BlockHeight: blockHeight,
Path: [][]BUMPLeaf{
{
{Offset: 0, Hash: "node0"},
{Offset: 1, Hash: "txId", TxID: true},
},
{
{Offset: 1, Hash: "node1"},
},
{
{Offset: 1, Hash: "node2"},
},
{
{Offset: 1, Hash: "node3"},
},
},
}

// when
actualBUMP := MerkleProofToBUMP(&mp, blockHeight)

// then
assert.Equal(t, expectedBUMP, actualBUMP)
})

t.Run("Valid Merkle Proof #2", func(t *testing.T) {
// given
blockHeight := uint64(0)
mp := bc.MerkleProof{
Index: 14,
TxOrID: "txId",
Nodes: []string{"node0", "node1", "node2", "node3", "node4"},
}
expectedBUMP := BUMP{
BlockHeight: blockHeight,
Path: [][]BUMPLeaf{
{
{Offset: 14, Hash: "txId", TxID: true},
{Offset: 15, Hash: "node0"},
},
{
{Offset: 6, Hash: "node1"},
},
{
{Offset: 2, Hash: "node2"},
},
{
{Offset: 0, Hash: "node3"},
},
{
{Offset: 1, Hash: "node4"},
},
},
}

// when
actualBUMP := MerkleProofToBUMP(&mp, blockHeight)

// then
assert.Equal(t, expectedBUMP, actualBUMP)
})

t.Run("Valid Merkle Proof #3 - with *", func(t *testing.T) {
// given
blockHeight := uint64(0)
mp := bc.MerkleProof{
Index: 14,
TxOrID: "txId",
Nodes: []string{"*", "node1", "node2", "node3", "node4"},
}
expectedBUMP := BUMP{
BlockHeight: blockHeight,
Path: [][]BUMPLeaf{
{
{Offset: 14, Hash: "txId", TxID: true},
{Offset: 15, Duplicate: true},
},
{
{Offset: 6, Hash: "node1"},
},
{
{Offset: 2, Hash: "node2"},
},
{
{Offset: 0, Hash: "node3"},
},
{
{Offset: 1, Hash: "node4"},
},
},
}

// when
actualBUMP := MerkleProofToBUMP(&mp, blockHeight)

// then
assert.Equal(t, expectedBUMP, actualBUMP)
})

t.Run("Empty Merkle Proof", func(t *testing.T) {
blockHeight := uint64(0)
mp := bc.MerkleProof{}
actualBUMP := MerkleProofToBUMP(&mp, blockHeight)
assert.Equal(t, BUMP{BlockHeight: blockHeight}, actualBUMP)
})
}
Loading

0 comments on commit b65bc68

Please sign in to comment.