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 #104 from BuxOrg/siggi/set-change-address-optimisa…
Browse files Browse the repository at this point in the history
…tion

Added some tests and benchmarks, changed setAddress on draft transaction to only derive what is needed
  • Loading branch information
mergify[bot] authored May 13, 2022
2 parents 822676b + a733abd commit 8aef286
Show file tree
Hide file tree
Showing 7 changed files with 305 additions and 12 deletions.
124 changes: 124 additions & 0 deletions action_transaction_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package bux

import (
"context"
"fmt"
"testing"

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

// BenchmarkAction_Transaction_recordTransaction will benchmark the method RecordTransaction()
func BenchmarkAction_Transaction_recordTransaction(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
ctx, client, xPub, config, err := initBenchmarkData(b)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
b.Fail()
}

var draftTransaction *DraftTransaction
if draftTransaction, err = client.NewTransaction(ctx, xPub.rawXpubKey, config, client.DefaultModelOptions()...); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
b.Fail()
}

var xPriv *bip32.ExtendedKey
if xPriv, err = bip32.NewKeyFromString(testXPriv); err != nil {
return
}

var hexString string
if hexString, err = draftTransaction.SignInputs(xPriv); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
b.Fail()
}

b.StartTimer()
if _, err = client.RecordTransaction(ctx, xPub.rawXpubKey, hexString, draftTransaction.ID, client.DefaultModelOptions()...); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
b.Fail()
}
}
}

// BenchmarkTransaction_newTransaction will benchmark the method newTransaction()
func BenchmarkAction_Transaction_newTransaction(b *testing.B) {

b.ResetTimer()
for i := 0; i < b.N; i++ {
b.StopTimer()
ctx, client, xPub, config, err := initBenchmarkData(b)
if err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
b.Fail()
}

b.StartTimer()
if _, err = client.NewTransaction(ctx, xPub.rawXpubKey, config, client.DefaultModelOptions()...); err != nil {
fmt.Printf("ERROR: %s\n", err.Error())
b.Fail()
}
}
}

func initBenchmarkData(b *testing.B) (context.Context, ClientInterface, *Xpub, *TransactionConfig, error) {
ctx, client, _ := CreateBenchmarkSQLiteClient(b, false, true,
WithCustomTaskManager(&taskManagerMockBase{}),
WithFreeCache(),
WithIUCDisabled(),
)

opts := append(client.DefaultModelOptions(), New())
xPub, err := client.NewXpub(ctx, testXPub, opts...)
if err != nil {
b.Fail()
}
destination := newDestination(xPub.GetID(), testLockingScript, opts...)
if err = destination.Save(ctx); err != nil {
b.Fail()
}

utxo := newUtxo(xPub.GetID(), testTxID, testLockingScript, 1, 122500, opts...)
if err = utxo.Save(ctx); err != nil {
b.Fail()
}
utxo = newUtxo(xPub.GetID(), testTxID, testLockingScript, 2, 122500, opts...)
if err = utxo.Save(ctx); err != nil {
b.Fail()
}
utxo = newUtxo(xPub.GetID(), testTxID, testLockingScript, 3, 122500, opts...)
if err = utxo.Save(ctx); err != nil {
b.Fail()
}
utxo = newUtxo(xPub.GetID(), testTxID, testLockingScript, 4, 122500, opts...)
if err = utxo.Save(ctx); err != nil {
b.Fail()
}

config := &TransactionConfig{
FeeUnit: &utils.FeeUnit{
Satoshis: 5,
Bytes: 100,
},
Outputs: []*TransactionOutput{{
OpReturn: &OpReturn{
Map: &MapProtocol{
App: "getbux.io",
Type: "blast",
Keys: map[string]interface{}{
"bux": "blasting",
},
},
},
}},
ChangeDestinationsStrategy: ChangeStrategyRandom,
ChangeNumberOfDestinations: 2,
}

return ctx, client, xPub, config, err
}
24 changes: 24 additions & 0 deletions bux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,30 @@ func CreateTestSQLiteClient(t *testing.T, debug, shared bool, clientOpts ...Clie
return ctx, client, f
}

// CreateBenchmarkSQLiteClient will create a test client for SQLite
//
// NOTE: you need to close the client using the returned defer func
func CreateBenchmarkSQLiteClient(b *testing.B, debug, shared bool, clientOpts ...ClientOps) (context.Context, ClientInterface, func()) {
ctx := context.Background()

// Set the default options, add migrate models
opts := DefaultClientOpts(debug, shared)
opts = append(opts, WithAutoMigrate(BaseModels...))
opts = append(opts, clientOpts...)

// Create the client
client, err := NewClient(ctx, opts...)
if err != nil {
b.Fail()
}

// Create a defer function
f := func() {
_ = client.Close(context.Background())
}
return ctx, client, f
}

// CloseClient is function used in the "defer()" function
func CloseClient(ctx context.Context, t *testing.T, client ClientInterface) {
require.NoError(t, client.Close(ctx))
Expand Down
13 changes: 2 additions & 11 deletions model_destinations.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,21 +381,12 @@ func (m *Destination) setAddress(rawXpubKey string) error {
m.XpubID = utils.Hash(rawXpubKey)

// Derive the address to ensure it is correct
var internal, external string
if external, internal, err = utils.DeriveAddresses(
hdKey, m.Num,
if m.Address, err = utils.DeriveAddress(
hdKey, m.Chain, m.Num,
); err != nil {
return err
}

if m.Chain == utils.ChainExternal {
// Set to external
m.Address = external
} else {
// Default is internal
m.Address = internal
}

return nil
}

Expand Down
37 changes: 36 additions & 1 deletion model_destinations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,42 @@ func TestDestination_setLockingScriptForAddress(t *testing.T) {

// TestDestination_setAddress will test the method setAddress()
func TestDestination_setAddress(t *testing.T) {
// finish test

t.Run("internal 1", func(t *testing.T) {
destination := newDestination(testXPubID, testLockingScript)
destination.Chain = utils.ChainInternal
destination.Num = 1
err := destination.setAddress(testXPub)
require.NoError(t, err)
assert.Equal(t, "1PQW54xMn5KA6uK7wgfzN4y7ZXMi6o7Qtm", destination.Address)
})

t.Run("external 1", func(t *testing.T) {
destination := newDestination(testXPubID, testLockingScript)
destination.Chain = utils.ChainExternal
destination.Num = 1
err := destination.setAddress(testXPub)
require.NoError(t, err)
assert.Equal(t, "16fq7PmmXXbFUG5maT5Xvr2zDBUgN1xdMF", destination.Address)
})

t.Run("internal 2", func(t *testing.T) {
destination := newDestination(testXPubID, testLockingScript)
destination.Chain = utils.ChainInternal
destination.Num = 2
err := destination.setAddress(testXPub)
require.NoError(t, err)
assert.Equal(t, "13St2SHkw1b8ZuaExyMf6ZMEzNjYbWRqL4", destination.Address)
})

t.Run("external 2", func(t *testing.T) {
destination := newDestination(testXPubID, testLockingScript)
destination.Chain = utils.ChainExternal
destination.Num = 2
err := destination.setAddress(testXPub)
require.NoError(t, err)
assert.Equal(t, "19jswATg9vBFta1aRnEjPHa2KMwafkmANj", destination.Address)
})
}

// TestDestination_getDestinationByID will test the method getDestinationByID()
Expand Down
1 change: 1 addition & 0 deletions model_xpubs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
testExternalAddress = "1CfaQw9udYNPccssFJFZ94DN8MqNZm9nGt"
testDraftID = "z50bb0d4eda0636aae1709e7e7080485a4d00af3ca2962c6e677cf5b53dgab9l"
testReferenceID = "example-reference-id"
testXPriv = "xprv9s21ZrQH143K3N6qVJQAu4EP51qMcyrKYJLkLgmYXgz58xmVxVLSsbx2DfJUtjcnXK8NdvkHMKfmmg5AJT2nqqRWUrjSHX29qEJwBgBPkJQ"
testXPub = "xpub661MyMwAqRbcFrBJbKwBGCB7d3fr2SaAuXGM95BA62X41m6eW2ehRQGW4xLi9wkEXUGnQZYxVVj4PxXnyrLk7jdqvBAs1Qq9gf6ykMvjR7J"
testXPubID = "1a0b10d4eda0636aae1709e7e7080485a4d99af3ca2962c6e677cf5b53d8ab8c"
)
Expand Down
29 changes: 29 additions & 0 deletions utils/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/bitcoinschema/go-bitcoin/v2"
"github.com/libsv/go-bk/bec"
"github.com/libsv/go-bk/bip32"
"github.com/libsv/go-bt/v2/bscript"
)

// DeriveChildKeyFromHex derive the child extended key from the hex string
Expand Down Expand Up @@ -57,6 +58,34 @@ func ValidateXPub(rawKey string) (*bip32.ExtendedKey, error) {
return hdKey, nil
}

// DeriveAddress will derive the given address from a key
func DeriveAddress(hdKey *bip32.ExtendedKey, chain uint32, num uint32) (address string, err error) {

// Don't panic
if hdKey == nil {
return "", ErrHDKeyNil
}

var child *bip32.ExtendedKey
if child, err = bitcoin.GetHDKeyByPath(hdKey, chain, num); err != nil {
return "", err
}

var pubKey *bec.PublicKey
if pubKey, err = child.ECPubKey(); err != nil {
// Should never error since the previous method ensures a valid hdKey
return "", err
}

var addressScript *bscript.Address
if addressScript, err = bitcoin.GetAddressFromPubKey(pubKey, true); err != nil {
// Should never error if the pubKeys are valid keys
return "", err
}

return addressScript.AddressString, nil
}

// DeriveAddresses will derive the internal and external address from a key
func DeriveAddresses(hdKey *bip32.ExtendedKey, num uint32) (external, internal string, err error) {

Expand Down
89 changes: 89 additions & 0 deletions utils/keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package utils

import (
"testing"

"github.com/libsv/go-bk/bip32"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const (
testXPub = "xpub661MyMwAqRbcFrBJbKwBGCB7d3fr2SaAuXGM95BA62X41m6eW2ehRQGW4xLi9wkEXUGnQZYxVVj4PxXnyrLk7jdqvBAs1Qq9gf6ykMvjR7J"
)

// Test_DeriveAddresses will test the method DeriveAddresses()
func Test_DeriveAddresses(t *testing.T) {

xPub, errX := bip32.NewKeyFromString(testXPub)
require.NoError(t, errX)

t.Run("DeriveAddresses 1", func(t *testing.T) {
external, internal, err := DeriveAddresses(xPub, 1)
require.NoError(t, err)
assert.Equal(t, "1PQW54xMn5KA6uK7wgfzN4y7ZXMi6o7Qtm", internal)
assert.Equal(t, "16fq7PmmXXbFUG5maT5Xvr2zDBUgN1xdMF", external)
})

t.Run("DeriveAddresses 2", func(t *testing.T) {
external, internal, err := DeriveAddresses(xPub, 2)
require.NoError(t, err)
assert.Equal(t, "13St2SHkw1b8ZuaExyMf6ZMEzNjYbWRqL4", internal)
assert.Equal(t, "19jswATg9vBFta1aRnEjPHa2KMwafkmANj", external)
})
}

// Test_DeriveAddress will test the method DeriveAddress()
func Test_DeriveAddress(t *testing.T) {

xPub, errX := bip32.NewKeyFromString(testXPub)
require.NoError(t, errX)

t.Run("DeriveAddresses 1", func(t *testing.T) {
internal, err := DeriveAddress(xPub, ChainInternal, 1)
require.NoError(t, err)
var external string
external, err = DeriveAddress(xPub, ChainExternal, 1)
require.NoError(t, err)
assert.Equal(t, "1PQW54xMn5KA6uK7wgfzN4y7ZXMi6o7Qtm", internal)
assert.Equal(t, "16fq7PmmXXbFUG5maT5Xvr2zDBUgN1xdMF", external)
})

t.Run("DeriveAddresses 2", func(t *testing.T) {
internal, err := DeriveAddress(xPub, ChainInternal, 2)
require.NoError(t, err)
var external string
external, err = DeriveAddress(xPub, ChainExternal, 2)
require.NoError(t, err)
assert.Equal(t, "13St2SHkw1b8ZuaExyMf6ZMEzNjYbWRqL4", internal)
assert.Equal(t, "19jswATg9vBFta1aRnEjPHa2KMwafkmANj", external)
})
}

// Benchmark_DeriveAddresses will benchmark the method DeriveAddresses()
func Benchmark_DeriveAddresses(b *testing.B) {

xPub, errX := bip32.NewKeyFromString(testXPub)
if errX != nil {
b.Fail()
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _, _ = DeriveAddresses(xPub, uint32(i))
}
}

// Benchmark_DeriveAddress will benchmark the method DeriveAddress()
func Benchmark_DeriveAddress(b *testing.B) {

xPub, errX := bip32.NewKeyFromString(testXPub)
if errX != nil {
b.Fail()
}

b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = DeriveAddress(xPub, ChainInternal, uint32(i))
}
}

0 comments on commit 8aef286

Please sign in to comment.