From 94bfa8dbfcce5cfe0ee99fa235bed41dd8f710ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 09:19:59 +0100 Subject: [PATCH 01/24] Added utility AutoFund func to abstract funding flow --- txinput.go | 59 +++++++++++++ txinput_test.go | 219 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 278 insertions(+) diff --git a/txinput.go b/txinput.go index 0a1b26bd..ad36a7e0 100644 --- a/txinput.go +++ b/txinput.go @@ -2,7 +2,9 @@ package bt import ( "bytes" + "context" "encoding/binary" + "errors" "fmt" "github.com/libsv/go-bk/crypto" @@ -10,6 +12,21 @@ import ( "github.com/libsv/go-bt/v2/bscript" ) +// FundIteratorFunc is used for AutoFund. It expect bt.FundIteration to be returned +// containing relevant input information, and a bool informing if the retrieval was successful. +// +// It is expected that the boolean return value should return false once funds are depleted. +type FundIteratorFunc func() (*FundIteration, bool) + +// FundIteration contains information relating to the current fund. Its fields are intended +// for use with tx.From(...). +type FundIteration struct { + PreviousTxID string + PreviousTxOutIndex uint32 + PreviousTxScript string + PreviousTxSatoshis uint64 +} + // NewInputFromBytes returns a transaction input from the bytes provided. func NewInputFromBytes(bytes []byte) (*Input, int, error) { if len(bytes) < 36 { @@ -88,6 +105,48 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat return nil } +// AutoFund continuously calls the provided bt.FundIteratorFunc, adding each returned iteration +// as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. +// +// After completion, the receiver is ready for Change to be called, and then signed. +// Note, this function works under the assumption that receiving *bt.Tx has outputs which need covered. +// +// Example usage, for when working with a list: +// tx.AutoFund(ctx, bt.NewFeeQuote(), func() bt.FundIteratorFunc { +// idx := 0 +// return func() (*bt.Iteration, bool) { +// if idx >= len(test.funds) { +// return &bt.FundIteration{}, false +// } +// defer func() { idx++ }() +// return &bt.FundIteration{ +// PreviousTxID: funds[idx].TxID, +// PreviousTxScript: funds[idx].Script, +// PreviousTxOutIndex: funds[idx].OutIndex, +// PreviousTxSatoshis: funds[idx].Satoshis, +// }, true +// } +// }) +func (tx *Tx) AutoFund(ctx context.Context, fq *FeeQuote, fn FundIteratorFunc) (err error) { + var feesPaid bool + for itr, ok := fn(); !feesPaid && ok; itr, ok = fn() { + if err = tx.From(itr.PreviousTxID, itr.PreviousTxOutIndex, + itr.PreviousTxScript, itr.PreviousTxSatoshis); err != nil { + return err + } + + feesPaid, err = tx.EstimateIsFeePaidEnough(fq) + if err != nil { + return err + } + } + if !feesPaid { + return errors.New("insufficient funds from iterator") + } + + return nil +} + // InputCount returns the number of transaction Inputs. func (tx *Tx) InputCount() int { return len(tx.Inputs) diff --git a/txinput_test.go b/txinput_test.go index b575cd94..af77e2de 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -1,7 +1,9 @@ package bt_test import ( + "context" "encoding/hex" + "errors" "testing" "github.com/libsv/go-bt/v2" @@ -79,3 +81,220 @@ func TestTx_From(t *testing.T) { assert.Equal(t, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", inputs[0].PreviousTxScript.String()) }) } + +func TestTx_AutoFund(t *testing.T) { + tests := map[string]struct { + tx *bt.Tx + funds []bt.FundIteration + expTotalInputs int + expErr error + }{ + "tx with exact inputs and surplus funds is covered": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) + return tx + }(), + funds: []bt.FundIteration{{ + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }}, + expTotalInputs: 2, + }, + "tx with extra inputs and surplus funds is covered with minimum needed inputs": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) + return tx + }(), + funds: []bt.FundIteration{{ + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }}, + expTotalInputs: 2, + }, + "tx with exact input satshis is covered": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) + return tx + }(), + funds: []bt.FundIteration{{ + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 670, + }}, + expTotalInputs: 2, + }, + "tx with large amount of satoshis is covered": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) + return tx + }(), + funds: []bt.FundIteration{{ + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 500, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 1, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 670, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 2, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 700, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 3, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 4, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 5, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 6, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 7, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 650, + }}, + expTotalInputs: 7, + }, + "iterator with no funds error": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) + return tx + }(), + funds: []bt.FundIteration{}, + expErr: errors.New("insufficient funds from iterator"), + }, + "iterator with insufficient funds errors": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 25400)) + return tx + }(), + funds: []bt.FundIteration{{ + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 0, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 500, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 1, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 670, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 2, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 700, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 3, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 4, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 5, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 6, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 1000, + }, { + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 7, + PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 650, + }}, + expErr: errors.New("insufficient funds from iterator"), + }, + "invalid tx script errors": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) + return tx + }(), + funds: []bt.FundIteration{{ + PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + PreviousTxOutIndex: 7, + PreviousTxScript: "ohhellotherea45ae401651fdbdf59a76ad43d1862534088ac", + PreviousTxSatoshis: 650, + }}, + expErr: errors.New("encoding/hex: invalid byte: U+006F 'o'"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + err := test.tx.AutoFund(context.Background(), bt.NewFeeQuote(), func() bt.FundIteratorFunc { + idx := 0 + return func() (*bt.FundIteration, bool) { + if idx == len(test.funds) { + return nil, false + } + defer func() { idx++ }() + return &test.funds[idx], true + } + }()) + + if test.expErr != nil { + assert.Error(t, err) + assert.EqualError(t, err, test.expErr.Error()) + return + } + + assert.NoError(t, err) + assert.Equal(t, test.expTotalInputs, test.tx.InputCount()) + }) + } +} From 644ae935c7472ada769164babd67d322dec22afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 09:23:48 +0100 Subject: [PATCH 02/24] updated doc string --- txinput.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/txinput.go b/txinput.go index ad36a7e0..993eca9b 100644 --- a/txinput.go +++ b/txinput.go @@ -108,7 +108,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // AutoFund continuously calls the provided bt.FundIteratorFunc, adding each returned iteration // as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. // -// After completion, the receiver is ready for Change to be called, and then signed. +// After completion, the receiver is ready for `Change(...)` to be called, and then be signed. // Note, this function works under the assumption that receiving *bt.Tx has outputs which need covered. // // Example usage, for when working with a list: From a67e50edd8336ef10220466c68e87a64a5ec89b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 09:41:43 +0100 Subject: [PATCH 03/24] fixed error in autofund example --- txinput.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/txinput.go b/txinput.go index 993eca9b..0276ccad 100644 --- a/txinput.go +++ b/txinput.go @@ -126,7 +126,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // PreviousTxSatoshis: funds[idx].Satoshis, // }, true // } -// }) +// }()) func (tx *Tx) AutoFund(ctx context.Context, fq *FeeQuote, fn FundIteratorFunc) (err error) { var feesPaid bool for itr, ok := fn(); !feesPaid && ok; itr, ok = fn() { From be5c4ba1b3e4e51f6b7d2aed14b72a9e0880be89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 10:57:44 +0100 Subject: [PATCH 04/24] added context to fund iteration func --- txinput.go | 6 +++--- txinput_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/txinput.go b/txinput.go index 0276ccad..1e2442ec 100644 --- a/txinput.go +++ b/txinput.go @@ -16,7 +16,7 @@ import ( // containing relevant input information, and a bool informing if the retrieval was successful. // // It is expected that the boolean return value should return false once funds are depleted. -type FundIteratorFunc func() (*FundIteration, bool) +type FundIteratorFunc func(ctx context.Context) (*FundIteration, bool) // FundIteration contains information relating to the current fund. Its fields are intended // for use with tx.From(...). @@ -114,7 +114,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // Example usage, for when working with a list: // tx.AutoFund(ctx, bt.NewFeeQuote(), func() bt.FundIteratorFunc { // idx := 0 -// return func() (*bt.Iteration, bool) { +// return func(ctx context.Context) (*bt.Iteration, bool) { // if idx >= len(test.funds) { // return &bt.FundIteration{}, false // } @@ -129,7 +129,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // }()) func (tx *Tx) AutoFund(ctx context.Context, fq *FeeQuote, fn FundIteratorFunc) (err error) { var feesPaid bool - for itr, ok := fn(); !feesPaid && ok; itr, ok = fn() { + for itr, ok := fn(ctx); !feesPaid && ok; itr, ok = fn(ctx) { if err = tx.From(itr.PreviousTxID, itr.PreviousTxOutIndex, itr.PreviousTxScript, itr.PreviousTxSatoshis); err != nil { return err diff --git a/txinput_test.go b/txinput_test.go index af77e2de..fdaff614 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -278,7 +278,7 @@ func TestTx_AutoFund(t *testing.T) { t.Run(name, func(t *testing.T) { err := test.tx.AutoFund(context.Background(), bt.NewFeeQuote(), func() bt.FundIteratorFunc { idx := 0 - return func() (*bt.FundIteration, bool) { + return func(ctx context.Context) (*bt.FundIteration, bool) { if idx == len(test.funds) { return nil, false } From 68fdc0b4cc4cf9e35e656de43d59de83874721f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 10:59:45 +0100 Subject: [PATCH 05/24] renamed funditeration to fund --- txinput.go | 6 +++--- txinput_test.go | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/txinput.go b/txinput.go index 1e2442ec..d0c45b5a 100644 --- a/txinput.go +++ b/txinput.go @@ -16,11 +16,11 @@ import ( // containing relevant input information, and a bool informing if the retrieval was successful. // // It is expected that the boolean return value should return false once funds are depleted. -type FundIteratorFunc func(ctx context.Context) (*FundIteration, bool) +type FundIteratorFunc func(ctx context.Context) (*Fund, bool) -// FundIteration contains information relating to the current fund. Its fields are intended +// Fund contains information relating to the current fund. Its fields are intended // for use with tx.From(...). -type FundIteration struct { +type Fund struct { PreviousTxID string PreviousTxOutIndex uint32 PreviousTxScript string diff --git a/txinput_test.go b/txinput_test.go index fdaff614..8951c774 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -85,7 +85,7 @@ func TestTx_From(t *testing.T) { func TestTx_AutoFund(t *testing.T) { tests := map[string]struct { tx *bt.Tx - funds []bt.FundIteration + funds []bt.Fund expTotalInputs int expErr error }{ @@ -95,7 +95,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.FundIteration{{ + funds: []bt.Fund{{ PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", PreviousTxOutIndex: 0, PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", @@ -114,7 +114,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.FundIteration{{ + funds: []bt.Fund{{ PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", PreviousTxOutIndex: 0, PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", @@ -138,7 +138,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.FundIteration{{ + funds: []bt.Fund{{ PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", PreviousTxOutIndex: 0, PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", @@ -157,7 +157,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) return tx }(), - funds: []bt.FundIteration{{ + funds: []bt.Fund{{ PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", PreviousTxOutIndex: 0, PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", @@ -206,7 +206,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.FundIteration{}, + funds: []bt.Fund{}, expErr: errors.New("insufficient funds from iterator"), }, "iterator with insufficient funds errors": { @@ -215,7 +215,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 25400)) return tx }(), - funds: []bt.FundIteration{{ + funds: []bt.Fund{{ PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", PreviousTxOutIndex: 0, PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", @@ -264,7 +264,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.FundIteration{{ + funds: []bt.Fund{{ PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", PreviousTxOutIndex: 7, PreviousTxScript: "ohhellotherea45ae401651fdbdf59a76ad43d1862534088ac", @@ -278,7 +278,7 @@ func TestTx_AutoFund(t *testing.T) { t.Run(name, func(t *testing.T) { err := test.tx.AutoFund(context.Background(), bt.NewFeeQuote(), func() bt.FundIteratorFunc { idx := 0 - return func(ctx context.Context) (*bt.FundIteration, bool) { + return func(ctx context.Context) (*bt.Fund, bool) { if idx == len(test.funds) { return nil, false } From 92a56338fb75c8e4c2a4b8f407f4301326b13d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 11:06:04 +0100 Subject: [PATCH 06/24] renamed funditeratorfunc to fundgetterfunc --- txinput.go | 6 +++--- txinput_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/txinput.go b/txinput.go index d0c45b5a..b68da331 100644 --- a/txinput.go +++ b/txinput.go @@ -12,11 +12,11 @@ import ( "github.com/libsv/go-bt/v2/bscript" ) -// FundIteratorFunc is used for AutoFund. It expect bt.FundIteration to be returned +// FundGetterFunc is used for AutoFund. It expect bt.FundIteration to be returned // containing relevant input information, and a bool informing if the retrieval was successful. // // It is expected that the boolean return value should return false once funds are depleted. -type FundIteratorFunc func(ctx context.Context) (*Fund, bool) +type FundGetterFunc func(ctx context.Context) (*Fund, bool) // Fund contains information relating to the current fund. Its fields are intended // for use with tx.From(...). @@ -127,7 +127,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // }, true // } // }()) -func (tx *Tx) AutoFund(ctx context.Context, fq *FeeQuote, fn FundIteratorFunc) (err error) { +func (tx *Tx) AutoFund(ctx context.Context, fq *FeeQuote, fn FundGetterFunc) (err error) { var feesPaid bool for itr, ok := fn(ctx); !feesPaid && ok; itr, ok = fn(ctx) { if err = tx.From(itr.PreviousTxID, itr.PreviousTxOutIndex, diff --git a/txinput_test.go b/txinput_test.go index 8951c774..a79c4409 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -276,7 +276,7 @@ func TestTx_AutoFund(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - err := test.tx.AutoFund(context.Background(), bt.NewFeeQuote(), func() bt.FundIteratorFunc { + err := test.tx.AutoFund(context.Background(), bt.NewFeeQuote(), func() bt.FundGetterFunc { idx := 0 return func(ctx context.Context) (*bt.Fund, bool) { if idx == len(test.funds) { From 0685253b308cb287fa6fc8a3e808b5bb7621bdab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 12:27:10 +0100 Subject: [PATCH 07/24] fundgetterfunc now returns an error instead of a bool --- txinput.go | 48 +++++++----- txinput_test.go | 200 ++++++++++++++++++++++++------------------------ 2 files changed, 130 insertions(+), 118 deletions(-) diff --git a/txinput.go b/txinput.go index b68da331..a61aabde 100644 --- a/txinput.go +++ b/txinput.go @@ -12,19 +12,22 @@ import ( "github.com/libsv/go-bt/v2/bscript" ) -// FundGetterFunc is used for AutoFund. It expect bt.FundIteration to be returned -// containing relevant input information, and a bool informing if the retrieval was successful. +// ErrNoFund signals the FundGetterFunc has reached the end of its funds. +var ErrNoFund = errors.New("no remainings funds") + +// FundGetterFunc is used for FromFunds. It expects *bt.Fund to be returned containing +// relevant input information, and an err informing any retrieval errors. // -// It is expected that the boolean return value should return false once funds are depleted. -type FundGetterFunc func(ctx context.Context) (*Fund, bool) +// It is expected that bt.ErrNoFund will be returned once the fund source is depleted. +type FundGetterFunc func(ctx context.Context) (*Fund, error) // Fund contains information relating to the current fund. Its fields are intended // for use with tx.From(...). type Fund struct { - PreviousTxID string - PreviousTxOutIndex uint32 - PreviousTxScript string - PreviousTxSatoshis uint64 + TxID string + OutIndex uint32 + LockingScript string + Satoshis uint64 } // NewInputFromBytes returns a transaction input from the bytes provided. @@ -105,21 +108,21 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat return nil } -// AutoFund continuously calls the provided bt.FundIteratorFunc, adding each returned iteration +// FromFunds continuously calls the provided bt.FundIteratorFunc, adding each returned iteration // as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. // // After completion, the receiver is ready for `Change(...)` to be called, and then be signed. // Note, this function works under the assumption that receiving *bt.Tx has outputs which need covered. // // Example usage, for when working with a list: -// tx.AutoFund(ctx, bt.NewFeeQuote(), func() bt.FundIteratorFunc { +// tx.FromFunds(ctx, bt.NewFeeQuote(), func() bt.FundIteratorFunc { // idx := 0 -// return func(ctx context.Context) (*bt.Iteration, bool) { -// if idx >= len(test.funds) { -// return &bt.FundIteration{}, false +// return func(ctx context.Context) (*bt.Fund, error) { +// if idx >= len(funds) { +// return nil, bt.ErrNoFund // } // defer func() { idx++ }() -// return &bt.FundIteration{ +// return &bt.Fund{ // PreviousTxID: funds[idx].TxID, // PreviousTxScript: funds[idx].Script, // PreviousTxOutIndex: funds[idx].OutIndex, @@ -127,11 +130,20 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // }, true // } // }()) -func (tx *Tx) AutoFund(ctx context.Context, fq *FeeQuote, fn FundGetterFunc) (err error) { +func (tx *Tx) FromFunds(ctx context.Context, fq *FeeQuote, next FundGetterFunc) (err error) { var feesPaid bool - for itr, ok := fn(ctx); !feesPaid && ok; itr, ok = fn(ctx) { - if err = tx.From(itr.PreviousTxID, itr.PreviousTxOutIndex, - itr.PreviousTxScript, itr.PreviousTxSatoshis); err != nil { + for !feesPaid { + fund, err := next(ctx) + if err != nil { + if err == ErrNoFund { + break + } + + return err + } + + if err = tx.From(fund.TxID, fund.OutIndex, + fund.LockingScript, fund.Satoshis); err != nil { return err } diff --git a/txinput_test.go b/txinput_test.go index a79c4409..a2d5d8fd 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -96,15 +96,15 @@ func TestTx_AutoFund(t *testing.T) { return tx }(), funds: []bt.Fund{{ - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }}, expTotalInputs: 2, }, @@ -115,20 +115,20 @@ func TestTx_AutoFund(t *testing.T) { return tx }(), funds: []bt.Fund{{ - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }}, expTotalInputs: 2, }, @@ -139,15 +139,15 @@ func TestTx_AutoFund(t *testing.T) { return tx }(), funds: []bt.Fund{{ - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 670, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 670, }}, expTotalInputs: 2, }, @@ -158,45 +158,45 @@ func TestTx_AutoFund(t *testing.T) { return tx }(), funds: []bt.Fund{{ - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 500, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 500, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 1, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 670, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 1, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 670, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 2, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 700, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 2, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 700, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 3, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 3, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 4, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 4, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 5, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 5, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 6, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 6, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 7, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 650, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 7, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 650, }}, expTotalInputs: 7, }, @@ -216,45 +216,45 @@ func TestTx_AutoFund(t *testing.T) { return tx }(), funds: []bt.Fund{{ - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 0, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 500, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 0, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 500, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 1, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 670, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 1, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 670, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 2, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 700, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 2, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 700, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 3, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 3, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 4, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 4, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 5, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 5, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 6, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 1000, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 6, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 1000, }, { - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 7, - PreviousTxScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 650, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 7, + LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 650, }}, expErr: errors.New("insufficient funds from iterator"), }, @@ -265,10 +265,10 @@ func TestTx_AutoFund(t *testing.T) { return tx }(), funds: []bt.Fund{{ - PreviousTxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - PreviousTxOutIndex: 7, - PreviousTxScript: "ohhellotherea45ae401651fdbdf59a76ad43d1862534088ac", - PreviousTxSatoshis: 650, + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 7, + LockingScript: "ohhellotherea45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 650, }}, expErr: errors.New("encoding/hex: invalid byte: U+006F 'o'"), }, @@ -276,14 +276,14 @@ func TestTx_AutoFund(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - err := test.tx.AutoFund(context.Background(), bt.NewFeeQuote(), func() bt.FundGetterFunc { + err := test.tx.FromFunds(context.Background(), bt.NewFeeQuote(), func() bt.FundGetterFunc { idx := 0 - return func(ctx context.Context) (*bt.Fund, bool) { + return func(ctx context.Context) (*bt.Fund, error) { if idx == len(test.funds) { - return nil, false + return nil, bt.ErrNoFund } defer func() { idx++ }() - return &test.funds[idx], true + return &test.funds[idx], nil } }()) From d053f1ac70b278a3cea2116a3cae850e5cee4e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 12:28:53 +0100 Subject: [PATCH 08/24] fixed doc string --- txinput.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/txinput.go b/txinput.go index a61aabde..6cacbd64 100644 --- a/txinput.go +++ b/txinput.go @@ -108,14 +108,15 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat return nil } -// FromFunds continuously calls the provided bt.FundIteratorFunc, adding each returned iteration +// FromFunds continuously calls the provided bt.FundGetterFunc, adding each returned iteration // as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. // // After completion, the receiver is ready for `Change(...)` to be called, and then be signed. -// Note, this function works under the assumption that receiving *bt.Tx has outputs which need covered. +// Note, this function works under the assumption that receiver *bt.Tx alread has all the outputs +// which need covered. // // Example usage, for when working with a list: -// tx.FromFunds(ctx, bt.NewFeeQuote(), func() bt.FundIteratorFunc { +// tx.FromFunds(ctx, bt.NewFeeQuote(), func() bt.FundGetterFunc { // idx := 0 // return func(ctx context.Context) (*bt.Fund, error) { // if idx >= len(funds) { From e3c93b765c272de251a8985920d7b774b7b1912d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 12:29:32 +0100 Subject: [PATCH 09/24] fixed doc string --- txinput.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/txinput.go b/txinput.go index 6cacbd64..260803c7 100644 --- a/txinput.go +++ b/txinput.go @@ -124,10 +124,10 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // } // defer func() { idx++ }() // return &bt.Fund{ -// PreviousTxID: funds[idx].TxID, -// PreviousTxScript: funds[idx].Script, -// PreviousTxOutIndex: funds[idx].OutIndex, -// PreviousTxSatoshis: funds[idx].Satoshis, +// TxID: funds[idx].TxID, +// LockingScript: funds[idx].Script, +// OutIndex: funds[idx].OutIndex, +// Satoshis: funds[idx].Satoshis, // }, true // } // }()) From e0fc927d679b9f8684fa28c40705d4931081473f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 12:36:17 +0100 Subject: [PATCH 10/24] fixed linter and added test --- txinput.go | 5 ++--- txinput_test.go | 26 ++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/txinput.go b/txinput.go index 260803c7..840a43d7 100644 --- a/txinput.go +++ b/txinput.go @@ -136,15 +136,14 @@ func (tx *Tx) FromFunds(ctx context.Context, fq *FeeQuote, next FundGetterFunc) for !feesPaid { fund, err := next(ctx) if err != nil { - if err == ErrNoFund { + if errors.Is(err, ErrNoFund) { break } return err } - if err = tx.From(fund.TxID, fund.OutIndex, - fund.LockingScript, fund.Satoshis); err != nil { + if err = tx.From(fund.TxID, fund.OutIndex, fund.LockingScript, fund.Satoshis); err != nil { return err } diff --git a/txinput_test.go b/txinput_test.go index a2d5d8fd..7f764d4a 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -86,6 +86,7 @@ func TestTx_AutoFund(t *testing.T) { tests := map[string]struct { tx *bt.Tx funds []bt.Fund + fundGetterFunc bt.FundGetterFunc expTotalInputs int expErr error }{ @@ -272,11 +273,28 @@ func TestTx_AutoFund(t *testing.T) { }}, expErr: errors.New("encoding/hex: invalid byte: U+006F 'o'"), }, + "error is returned to the user": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) + return tx + }(), + fundGetterFunc: func(context.Context) (*bt.Fund, error) { + return nil, errors.New("custom error") + }, + funds: []bt.Fund{{ + TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + OutIndex: 7, + LockingScript: "ohhellotherea45ae401651fdbdf59a76ad43d1862534088ac", + Satoshis: 650, + }}, + expErr: errors.New("custom error"), + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - err := test.tx.FromFunds(context.Background(), bt.NewFeeQuote(), func() bt.FundGetterFunc { + fgFn := func() bt.FundGetterFunc { idx := 0 return func(ctx context.Context) (*bt.Fund, error) { if idx == len(test.funds) { @@ -285,8 +303,12 @@ func TestTx_AutoFund(t *testing.T) { defer func() { idx++ }() return &test.funds[idx], nil } - }()) + }() + if test.fundGetterFunc != nil { + fgFn = test.fundGetterFunc + } + err := test.tx.FromFunds(context.Background(), bt.NewFeeQuote(), fgFn) if test.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, test.expErr.Error()) From 7094e5b218b9d21b9d0041a0c3718eeb7a05e2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 14:41:30 +0100 Subject: [PATCH 11/24] fund getter replaced with input getter. added utility input builder func --- txinput.go | 81 +++++++++---------- txinput_test.go | 207 ++++++++++++++---------------------------------- 2 files changed, 96 insertions(+), 192 deletions(-) diff --git a/txinput.go b/txinput.go index 840a43d7..2f7891bc 100644 --- a/txinput.go +++ b/txinput.go @@ -12,23 +12,14 @@ import ( "github.com/libsv/go-bt/v2/bscript" ) -// ErrNoFund signals the FundGetterFunc has reached the end of its funds. -var ErrNoFund = errors.New("no remainings funds") +// ErrNoInput signals the FundGetterFunc has reached the end of its funds. +var ErrNoInput = errors.New("no remainings inputs") -// FundGetterFunc is used for FromFunds. It expects *bt.Fund to be returned containing +// InputGetterFunc is used for FromFunds. It expects *bt.Fund to be returned containing // relevant input information, and an err informing any retrieval errors. // -// It is expected that bt.ErrNoFund will be returned once the fund source is depleted. -type FundGetterFunc func(ctx context.Context) (*Fund, error) - -// Fund contains information relating to the current fund. Its fields are intended -// for use with tx.From(...). -type Fund struct { - TxID string - OutIndex uint32 - LockingScript string - Satoshis uint64 -} +// It is expected that bt.ErrNoInput will be returned once the fund source is depleted. +type InputGetterFunc func(ctx context.Context) (*Input, error) // NewInputFromBytes returns a transaction input from the bytes provided. func NewInputFromBytes(bytes []byte) (*Input, int, error) { @@ -54,6 +45,28 @@ func NewInputFromBytes(bytes []byte) (*Input, int, error) { }, totalLength, nil } +// NewInputFrom builds and returns a new input from the specified UTXO fields, using the default +// finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually +// afterwards. +func NewInputFrom(prevTxID string, vout uint32, prevTxLockingScript string, satoshis uint64) (*Input, error) { + pts, err := bscript.NewFromHexString(prevTxLockingScript) + if err != nil { + return nil, err + } + + i := &Input{ + PreviousTxOutIndex: vout, + PreviousTxSatoshis: satoshis, + PreviousTxScript: pts, + SequenceNumber: DefaultSequenceNumber, // use default finalised sequence number + } + if err := i.PreviousTxIDAddStr(prevTxID); err != nil { + return nil, err + } + + return i, nil +} + // TotalInputSatoshis returns the total Satoshis inputted to the transaction. func (tx *Tx) TotalInputSatoshis() (total uint64) { for _, in := range tx.Inputs { @@ -89,26 +102,16 @@ func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { // finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually // afterwards. func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, satoshis uint64) error { - pts, err := bscript.NewFromHexString(prevTxLockingScript) + i, err := NewInputFrom(prevTxID, vout, prevTxLockingScript, satoshis) if err != nil { return err } - i := &Input{ - PreviousTxOutIndex: vout, - PreviousTxSatoshis: satoshis, - PreviousTxScript: pts, - SequenceNumber: DefaultSequenceNumber, // use default finalised sequence number - } - if err := i.PreviousTxIDAddStr(prevTxID); err != nil { - return err - } tx.addInput(i) - return nil } -// FromFunds continuously calls the provided bt.FundGetterFunc, adding each returned iteration +// FromInputs continuously calls the provided bt.FundGetterFunc, adding each returned iteration // as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. // // After completion, the receiver is ready for `Change(...)` to be called, and then be signed. @@ -116,36 +119,28 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // which need covered. // // Example usage, for when working with a list: -// tx.FromFunds(ctx, bt.NewFeeQuote(), func() bt.FundGetterFunc { +// tx.FromInputs(ctx, bt.NewFeeQuote(), func() bt.InputGetterFunc { // idx := 0 -// return func(ctx context.Context) (*bt.Fund, error) { -// if idx >= len(funds) { -// return nil, bt.ErrNoFund +// return func(ctx context.Context) (*bt.Input, error) { +// if idx >= len(inputs) { +// return nil, bt.ErrNoInput // } // defer func() { idx++ }() -// return &bt.Fund{ -// TxID: funds[idx].TxID, -// LockingScript: funds[idx].Script, -// OutIndex: funds[idx].OutIndex, -// Satoshis: funds[idx].Satoshis, -// }, true +// return inputs[idx], true // } // }()) -func (tx *Tx) FromFunds(ctx context.Context, fq *FeeQuote, next FundGetterFunc) (err error) { +func (tx *Tx) FromInputs(ctx context.Context, fq *FeeQuote, next InputGetterFunc) (err error) { var feesPaid bool for !feesPaid { - fund, err := next(ctx) + input, err := next(ctx) if err != nil { - if errors.Is(err, ErrNoFund) { + if errors.Is(err, ErrNoInput) { break } return err } - - if err = tx.From(fund.TxID, fund.OutIndex, fund.LockingScript, fund.Satoshis); err != nil { - return err - } + tx.addInput(input) feesPaid, err = tx.EstimateIsFeePaidEnough(fq) if err != nil { diff --git a/txinput_test.go b/txinput_test.go index 7f764d4a..a1aa3267 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -85,8 +85,8 @@ func TestTx_From(t *testing.T) { func TestTx_AutoFund(t *testing.T) { tests := map[string]struct { tx *bt.Tx - funds []bt.Fund - fundGetterFunc bt.FundGetterFunc + inputs []*bt.Input + fundGetterFunc bt.InputGetterFunc expTotalInputs int expErr error }{ @@ -96,17 +96,13 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.Fund{{ - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }}, + inputs: func() []*bt.Input { + tx := bt.NewTx() + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + + return tx.Inputs + }(), expTotalInputs: 2, }, "tx with extra inputs and surplus funds is covered with minimum needed inputs": { @@ -115,22 +111,14 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.Fund{{ - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }}, + inputs: func() []*bt.Input { + tx := bt.NewTx() + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + + return tx.Inputs + }(), expTotalInputs: 2, }, "tx with exact input satshis is covered": { @@ -139,17 +127,13 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.Fund{{ - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 670, - }}, + inputs: func() []*bt.Input { + tx := bt.NewTx() + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 670)) + + return tx.Inputs + }(), expTotalInputs: 2, }, "tx with large amount of satoshis is covered": { @@ -158,47 +142,19 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) return tx }(), - funds: []bt.Fund{{ - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 500, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 1, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 670, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 2, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 700, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 3, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 4, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 5, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 6, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 7, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 650, - }}, + inputs: func() []*bt.Input { + tx := bt.NewTx() + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 500)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 670)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 700)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 650)) + + return tx.Inputs + }(), expTotalInputs: 7, }, "iterator with no funds error": { @@ -207,7 +163,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - funds: []bt.Fund{}, + inputs: []*bt.Input{}, expErr: errors.New("insufficient funds from iterator"), }, "iterator with insufficient funds errors": { @@ -216,62 +172,20 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 25400)) return tx }(), - funds: []bt.Fund{{ - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 0, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 500, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 1, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 670, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 2, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 700, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 3, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 4, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 5, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 6, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 1000, - }, { - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 7, - LockingScript: "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 650, - }}, - expErr: errors.New("insufficient funds from iterator"), - }, - "invalid tx script errors": { - tx: func() *bt.Tx { + inputs: func() []*bt.Input { tx := bt.NewTx() - assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) - return tx + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 500)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 670)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 700)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) + assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 650)) + + return tx.Inputs }(), - funds: []bt.Fund{{ - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 7, - LockingScript: "ohhellotherea45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 650, - }}, - expErr: errors.New("encoding/hex: invalid byte: U+006F 'o'"), + expErr: errors.New("insufficient funds from iterator"), }, "error is returned to the user": { tx: func() *bt.Tx { @@ -279,36 +193,31 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) return tx }(), - fundGetterFunc: func(context.Context) (*bt.Fund, error) { + fundGetterFunc: func(context.Context) (*bt.Input, error) { return nil, errors.New("custom error") }, - funds: []bt.Fund{{ - TxID: "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - OutIndex: 7, - LockingScript: "ohhellotherea45ae401651fdbdf59a76ad43d1862534088ac", - Satoshis: 650, - }}, + inputs: []*bt.Input{}, expErr: errors.New("custom error"), }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - fgFn := func() bt.FundGetterFunc { + fgFn := func() bt.InputGetterFunc { idx := 0 - return func(ctx context.Context) (*bt.Fund, error) { - if idx == len(test.funds) { - return nil, bt.ErrNoFund + return func(ctx context.Context) (*bt.Input, error) { + if idx == len(test.inputs) { + return nil, bt.ErrNoInput } defer func() { idx++ }() - return &test.funds[idx], nil + return test.inputs[idx], nil } }() if test.fundGetterFunc != nil { fgFn = test.fundGetterFunc } - err := test.tx.FromFunds(context.Background(), bt.NewFeeQuote(), fgFn) + err := test.tx.FromInputs(context.Background(), bt.NewFeeQuote(), fgFn) if test.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, test.expErr.Error()) From 82e1bf027314a8b96990d13749c292c3ccf621b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 14:45:26 +0100 Subject: [PATCH 12/24] removed all mention of funds --- txinput.go | 10 +++++----- txinput_test.go | 34 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/txinput.go b/txinput.go index 2f7891bc..1853f02f 100644 --- a/txinput.go +++ b/txinput.go @@ -12,13 +12,13 @@ import ( "github.com/libsv/go-bt/v2/bscript" ) -// ErrNoInput signals the FundGetterFunc has reached the end of its funds. +// ErrNoInput signals the InputGetterFunc has reached the end of its input. var ErrNoInput = errors.New("no remainings inputs") -// InputGetterFunc is used for FromFunds. It expects *bt.Fund to be returned containing +// InputGetterFunc is used for FromInputs. It expects *bt.Input to be returned containing // relevant input information, and an err informing any retrieval errors. // -// It is expected that bt.ErrNoInput will be returned once the fund source is depleted. +// It is expected that bt.ErrNoInput will be returned once the input source is depleted. type InputGetterFunc func(ctx context.Context) (*Input, error) // NewInputFromBytes returns a transaction input from the bytes provided. @@ -111,7 +111,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat return nil } -// FromInputs continuously calls the provided bt.FundGetterFunc, adding each returned iteration +// FromInputs continuously calls the provided bt.InputGetterFunc, adding each returned input // as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. // // After completion, the receiver is ready for `Change(...)` to be called, and then be signed. @@ -148,7 +148,7 @@ func (tx *Tx) FromInputs(ctx context.Context, fq *FeeQuote, next InputGetterFunc } } if !feesPaid { - return errors.New("insufficient funds from iterator") + return errors.New("insufficient inputs provided") } return nil diff --git a/txinput_test.go b/txinput_test.go index a1aa3267..5b836a31 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -82,15 +82,15 @@ func TestTx_From(t *testing.T) { }) } -func TestTx_AutoFund(t *testing.T) { +func TestTx_AddInputs(t *testing.T) { tests := map[string]struct { - tx *bt.Tx - inputs []*bt.Input - fundGetterFunc bt.InputGetterFunc - expTotalInputs int - expErr error + tx *bt.Tx + inputs []*bt.Input + inputGetterFunc bt.InputGetterFunc + expTotalInputs int + expErr error }{ - "tx with exact inputs and surplus funds is covered": { + "tx with exact inputs and surplus inputs is covered": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) @@ -105,7 +105,7 @@ func TestTx_AutoFund(t *testing.T) { }(), expTotalInputs: 2, }, - "tx with extra inputs and surplus funds is covered with minimum needed inputs": { + "tx with extra inputs and surplus inputs is covered with minimum needed inputs": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) @@ -157,16 +157,16 @@ func TestTx_AutoFund(t *testing.T) { }(), expTotalInputs: 7, }, - "iterator with no funds error": { + "getter with no inputs error": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), inputs: []*bt.Input{}, - expErr: errors.New("insufficient funds from iterator"), + expErr: errors.New("insufficient inputs provided"), }, - "iterator with insufficient funds errors": { + "getter with insufficient inputs errors": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 25400)) @@ -185,7 +185,7 @@ func TestTx_AutoFund(t *testing.T) { return tx.Inputs }(), - expErr: errors.New("insufficient funds from iterator"), + expErr: errors.New("insufficient inputs provided"), }, "error is returned to the user": { tx: func() *bt.Tx { @@ -193,7 +193,7 @@ func TestTx_AutoFund(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) return tx }(), - fundGetterFunc: func(context.Context) (*bt.Input, error) { + inputGetterFunc: func(context.Context) (*bt.Input, error) { return nil, errors.New("custom error") }, inputs: []*bt.Input{}, @@ -203,7 +203,7 @@ func TestTx_AutoFund(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - fgFn := func() bt.InputGetterFunc { + iptFn := func() bt.InputGetterFunc { idx := 0 return func(ctx context.Context) (*bt.Input, error) { if idx == len(test.inputs) { @@ -213,11 +213,11 @@ func TestTx_AutoFund(t *testing.T) { return test.inputs[idx], nil } }() - if test.fundGetterFunc != nil { - fgFn = test.fundGetterFunc + if test.inputGetterFunc != nil { + iptFn = test.inputGetterFunc } - err := test.tx.FromInputs(context.Background(), bt.NewFeeQuote(), fgFn) + err := test.tx.FromInputs(context.Background(), bt.NewFeeQuote(), iptFn) if test.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, test.expErr.Error()) From 8eff6b5d32808cfff3b9c14bc00e686b2fad22b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Thu, 16 Sep 2021 14:47:46 +0100 Subject: [PATCH 13/24] updated example in doc string --- txinput.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/txinput.go b/txinput.go index 1853f02f..14e8aedc 100644 --- a/txinput.go +++ b/txinput.go @@ -120,13 +120,13 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // // Example usage, for when working with a list: // tx.FromInputs(ctx, bt.NewFeeQuote(), func() bt.InputGetterFunc { -// idx := 0 +// i := 0 // return func(ctx context.Context) (*bt.Input, error) { -// if idx >= len(inputs) { +// if i >= len(utxos) { // return nil, bt.ErrNoInput // } -// defer func() { idx++ }() -// return inputs[idx], true +// defer func() { i++ }() +// return bt.NewInputFrom(utxos[i].TxID, utxo[i].Vout, utxos[i].Script, utxos[i].Satoshis), true // } // }()) func (tx *Tx) FromInputs(ctx context.Context, fq *FeeQuote, next InputGetterFunc) (err error) { From f50985de70f56869b84e96c47fdeccabf30fb5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Fri, 17 Sep 2021 17:23:40 +0100 Subject: [PATCH 14/24] fixed type in error string --- txinput.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/txinput.go b/txinput.go index 14e8aedc..e7e9097c 100644 --- a/txinput.go +++ b/txinput.go @@ -13,7 +13,7 @@ import ( ) // ErrNoInput signals the InputGetterFunc has reached the end of its input. -var ErrNoInput = errors.New("no remainings inputs") +var ErrNoInput = errors.New("no remaining inputs") // InputGetterFunc is used for FromInputs. It expects *bt.Input to be returned containing // relevant input information, and an err informing any retrieval errors. From bfe23db04ac31751e389e7f99e39ab1ad04056ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 09:58:34 +0100 Subject: [PATCH 15/24] Added utxo data type. changed tx.From signature. updated tests --- examples/create_tx/create_tx.go | 11 +- .../create_tx_with_opreturn.go | 11 +- tx.go | 17 + tx_test.go | 225 ++++++----- txchange_test.go | 120 +++--- txinput.go | 70 ++-- txinput_test.go | 361 ++++++++++++++---- txoutput_test.go | 32 +- txsign_test.go | 16 +- utxo.go | 9 + 10 files changed, 577 insertions(+), 295 deletions(-) create mode 100644 utxo.go diff --git a/examples/create_tx/create_tx.go b/examples/create_tx/create_tx.go index f79cfed8..b55fa986 100644 --- a/examples/create_tx/create_tx.go +++ b/examples/create_tx/create_tx.go @@ -11,11 +11,12 @@ import ( func main() { tx := bt.NewTx() - _ = tx.From( - "11b476ad8e0a48fcd40807a111a050af51114877e09283bfa7f3505081a1819d", - 0, - "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac6a0568656c6c6f", - 1500) + _ = tx.From(&bt.UTXO{ + TxID: "11b476ad8e0a48fcd40807a111a050af51114877e09283bfa7f3505081a1819d", + Vout: 0, + LockingScript: "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac6a0568656c6c6f", + Satoshis: 1500, + }) _ = tx.PayToAddress("1NRoySJ9Lvby6DuE2UQYnyT67AASwNZxGb", 1000) diff --git a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go index af9440d5..fe94dac0 100644 --- a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go +++ b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go @@ -11,11 +11,12 @@ import ( func main() { tx := bt.NewTx() - _ = tx.From( - "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", - 0, - "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac", - 1000) + _ = tx.From(&bt.UTXO{ + TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", + Vout: 0, + LockingScript: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac", + Satoshis: 1000, + }) _ = tx.PayToAddress("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 900) diff --git a/tx.go b/tx.go index 7d52e44e..32d65262 100644 --- a/tx.go +++ b/tx.go @@ -476,4 +476,21 @@ func (tx *Tx) feesPaid(size *TxSize, fees *FeeQuote) (*TxFees, error) { } resp.TotalFeePaid = resp.StdFeePaid + resp.DataFeePaid return resp, nil + +} + +func (tx *Tx) estimateDeficit(fees *FeeQuote) (uint64, error) { + totalInputSatoshis := tx.TotalInputSatoshis() + totalOutputSatoshis := tx.TotalOutputSatoshis() + + expFeesPaid, err := tx.EstimateFeesPaid(fees) + if err != nil { + return 0, err + } + + if totalInputSatoshis > totalOutputSatoshis+expFeesPaid.TotalFeePaid { + return 0, nil + } + + return totalOutputSatoshis + expFeesPaid.TotalFeePaid - totalInputSatoshis, nil } diff --git a/tx_test.go b/tx_test.go index 479027e7..9e811698 100644 --- a/tx_test.go +++ b/tx_test.go @@ -197,11 +197,12 @@ func TestTx_CreateTx(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From( + err := tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000) + 2000000, + }) assert.NoError(t, err) err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) @@ -223,11 +224,12 @@ func TestTx_HasDataOutputs(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From( + err := tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000) + 2000000, + }) assert.NoError(t, err) err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) @@ -255,11 +257,12 @@ func TestTx_HasDataOutputs(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From( + err := tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000) + 2000000, + }) assert.NoError(t, err) err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) @@ -280,9 +283,8 @@ func TestTx_HasDataOutputs(t *testing.T) { func TestTx_ToJson(t *testing.T) { tx, _ := bt.NewTxFromString("0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000") - bb, err := json.MarshalIndent(tx, "", "\t") + _, err := json.MarshalIndent(tx, "", "\t") assert.NoError(t, err) - fmt.Println(string(bb)) } func TestTx_JSON(t *testing.T) { @@ -293,11 +295,12 @@ func TestTx_JSON(t *testing.T) { "standard tx should marshal and unmarshall correctly": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000)) + 2000000, + })) assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var wif *WIF wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") @@ -311,11 +314,12 @@ func TestTx_JSON(t *testing.T) { }, "data tx should marshall correctly": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000)) + 2000000, + })) assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var wif *WIF wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") @@ -402,21 +406,24 @@ func TestTx_MarshallJSON(t *testing.T) { }, "transaction with multiple Inputs": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 10000)) - assert.NoError(t, tx.From( + 10000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 2, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 10000)) - assert.NoError(t, tx.From( + 10000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 114, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 10000)) + 10000, + })) assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var w *wif.WIF w, err := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") @@ -727,21 +734,24 @@ func TestTx_InputIdx(t *testing.T) { "tx with 3 Inputs and input idx 0 requested should return correct input": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 1000)) - assert.NoError(t, tx.From( + 1000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000)) - assert.NoError(t, tx.From( + 2000000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000)) + 2000000, + })) return tx }(), idx: 0, @@ -762,21 +772,24 @@ func TestTx_InputIdx(t *testing.T) { }, "tx with 3 Outputs and output idx 2 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 1000)) - assert.NoError(t, tx.From( + 1000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000)) - assert.NoError(t, tx.From( + 2000000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdac4", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 999)) + 999, + })) return tx }(), idx: 2, @@ -797,21 +810,24 @@ func TestTx_InputIdx(t *testing.T) { }, "tx with 3 Outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 1000)) - assert.NoError(t, tx.From( + 1000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 2000000)) - assert.NoError(t, tx.From( + 2000000, + })) + assert.NoError(t, tx.From(&bt.UTXO{ "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", - 999)) + 999, + })) return tx }(), idx: 5, @@ -911,8 +927,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { "unsigned transaction (1 input 1 P2PKHOutput + no change) paying less by 1 satoshi": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{ + "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000, + })) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 905)) return tx @@ -925,8 +943,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 1 P2PKHOutput + change) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) + assert.NoError(t, tx.From(&bt.UTXO{ + "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709, + })) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -954,8 +974,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834763)) + assert.NoError(t, tx.From(&bt.UTXO{ + "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834763, + })) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -970,8 +992,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should fail paying less by 1 sat": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834763)) + assert.NoError(t, tx.From(&bt.UTXO{ + "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834763, + })) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256560)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -990,8 +1014,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709)) + assert.NoError(t, tx.From(&bt.UTXO{ + "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709, + })) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1010,8 +1036,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{ + "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000, + })) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 904)) tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1029,8 +1057,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From("160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{ + "160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000, + })) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 894)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1051,8 +1081,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From("160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{ + "160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000, + })) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 895)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1092,8 +1124,8 @@ func Test_IsFeePaidEnough(t *testing.T) { "unsigned transaction (1 input 1 P2PKHOutput + no change) paying less by 1 satoshi": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 959)) return tx @@ -1106,8 +1138,8 @@ func Test_IsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 1 P2PKHOutput + change) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1135,8 +1167,8 @@ func Test_IsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -1151,8 +1183,8 @@ func Test_IsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should fail paying less by 1 sat": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256560)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -1171,8 +1203,8 @@ func Test_IsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1191,8 +1223,8 @@ func Test_IsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 904)) tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1210,8 +1242,8 @@ func Test_IsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From("160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000})) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 894)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1232,8 +1264,8 @@ func Test_IsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From("160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000})) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 895)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1273,8 +1305,8 @@ func Test_EstimateFeesPaid(t *testing.T) { "226B transaction (1 input 1 P2PKHOutput + no change) no data should return 113 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) return tx @@ -1291,8 +1323,8 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "226B transaction (1 input 1 P2PKHOutput + change) no data should return 113 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1311,8 +1343,8 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "214B unsigned transaction (1 input, 1 opreturn, no change) 10 byte of data should return 100 sats fee 6 data fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) return tx }(), @@ -1329,14 +1361,14 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "556B unsigned transaction (3 inputs + 2 outputs + no change) no data should return 261 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - err := tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) + err := tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) assert.NoError(t, err) - err = tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) + err = tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) assert.NoError(t, err) - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) return tx @@ -1354,14 +1386,14 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "556B unsigned transaction (3 inputs + 2 outputs + 1 change) no data should return 278 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - err := tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) + err := tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) assert.NoError(t, err) - err = tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) + err = tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) assert.NoError(t, err) - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote()) @@ -1380,12 +1412,12 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "565B unsigned transaction 100B data should return 63 sats std fee, 50 data fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100)) - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100)) - assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100})) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100})) + assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddOpReturnOutput(make([]byte, 0x64))) @@ -1428,11 +1460,11 @@ func TestTx_EstimateFeesPaidTotal(t *testing.T) { "Transaction with one input one output should return 96": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000)) + 1000})) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), @@ -1441,11 +1473,12 @@ func TestTx_EstimateFeesPaidTotal(t *testing.T) { }, "Transaction with one input 4 Outputs should return 147": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From( + assert.NoError(t, tx.From(&bt.UTXO{ "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2500)) + 2500, + })) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) diff --git a/txchange_test.go b/txchange_test.go index 06d7a0d4..f6446512 100644 --- a/txchange_test.go +++ b/txchange_test.go @@ -15,10 +15,10 @@ func TestTx_ChangeToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("", nil) @@ -29,10 +29,10 @@ func TestTx_ChangeToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", nil) @@ -43,10 +43,10 @@ func TestTx_ChangeToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", bt.NewFeeQuote()) @@ -69,10 +69,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err = tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -95,10 +95,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -129,10 +129,10 @@ func TestTx_Change(t *testing.T) { // utxo err := tx.From( - "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000) + &bt.UTXO{"b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000}) assert.NoError(t, err) // pay to @@ -177,10 +177,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -204,10 +204,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000000) @@ -238,10 +238,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 999995) @@ -274,17 +274,17 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - "9e88ca8eec0845e9e864c024bc5e6711e670932c9c7d929f9fccdb2c440ae28e", - 0, - "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", - 5689) + &bt.UTXO{"9e88ca8eec0845e9e864c024bc5e6711e670932c9c7d929f9fccdb2c440ae28e", + 0, + "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", + 5689}) assert.NoError(t, err) err = tx.From( - "4e25b077d4cbb955b5a215feb53f963cf04688ff1777b9bea097c7ddbdf7ea42", - 0, - "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", - 5689) + &bt.UTXO{"4e25b077d4cbb955b5a215feb53f963cf04688ff1777b9bea097c7ddbdf7ea42", + 0, + "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", + 5689}) assert.NoError(t, err) err = tx.ChangeToAddress("1BxGFoRPSFgYxoAStEncL6HuELqPkV3JVj", bt.NewFeeQuote()) @@ -318,10 +318,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000)) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000})) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000)) return tx }(), @@ -334,10 +334,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000)) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000})) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), @@ -350,10 +350,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2500)) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2500})) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) @@ -369,10 +369,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000)) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000})) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), diff --git a/txinput.go b/txinput.go index e7e9097c..4c3ad558 100644 --- a/txinput.go +++ b/txinput.go @@ -15,11 +15,11 @@ import ( // ErrNoInput signals the InputGetterFunc has reached the end of its input. var ErrNoInput = errors.New("no remaining inputs") -// InputGetterFunc is used for FromInputs. It expects *bt.Input to be returned containing +// UTXOGetterFunc is used for FromInputs. It expects *bt.UTXO to be returned containing // relevant input information, and an err informing any retrieval errors. // // It is expected that bt.ErrNoInput will be returned once the input source is depleted. -type InputGetterFunc func(ctx context.Context) (*Input, error) +type UTXOGetterFunc func(ctx context.Context, deficit uint64) ([]*UTXO, error) // NewInputFromBytes returns a transaction input from the bytes provided. func NewInputFromBytes(bytes []byte) (*Input, int, error) { @@ -45,28 +45,6 @@ func NewInputFromBytes(bytes []byte) (*Input, int, error) { }, totalLength, nil } -// NewInputFrom builds and returns a new input from the specified UTXO fields, using the default -// finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually -// afterwards. -func NewInputFrom(prevTxID string, vout uint32, prevTxLockingScript string, satoshis uint64) (*Input, error) { - pts, err := bscript.NewFromHexString(prevTxLockingScript) - if err != nil { - return nil, err - } - - i := &Input{ - PreviousTxOutIndex: vout, - PreviousTxSatoshis: satoshis, - PreviousTxScript: pts, - SequenceNumber: DefaultSequenceNumber, // use default finalised sequence number - } - if err := i.PreviousTxIDAddStr(prevTxID); err != nil { - return nil, err - } - - return i, nil -} - // TotalInputSatoshis returns the total Satoshis inputted to the transaction. func (tx *Tx) TotalInputSatoshis() (total uint64) { for _, in := range tx.Inputs { @@ -82,6 +60,7 @@ func (tx *Tx) addInput(input *Input) { // AddP2PKHInputsFromTx will add all Outputs of given previous transaction // that match a specific public key to your transaction. func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { + txID := pvsTx.TxID() for i, utxo := range pvsTx.Outputs { utxoPkHASH160, err := utxo.LockingScript.PublicKeyHash() if err != nil { @@ -89,7 +68,12 @@ func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { } if bytes.Equal(utxoPkHASH160, crypto.Hash160(matchPK)) { - if err := tx.From(pvsTx.TxID(), uint32(i), utxo.LockingScriptHexString(), utxo.Satoshis); err != nil { + if err := tx.From(&UTXO{ + TxID: txID, + Vout: uint32(i), + Satoshis: utxo.Satoshis, + LockingScript: utxo.LockingScriptHexString(), + }); err != nil { return err } } @@ -101,12 +85,22 @@ func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { // From adds a new input to the transaction from the specified UTXO fields, using the default // finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually // afterwards. -func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, satoshis uint64) error { - i, err := NewInputFrom(prevTxID, vout, prevTxLockingScript, satoshis) +func (tx *Tx) From(utxo *UTXO) error { + pts, err := bscript.NewFromHexString(utxo.LockingScript) if err != nil { return err } + i := &Input{ + PreviousTxOutIndex: utxo.Vout, + PreviousTxSatoshis: utxo.Satoshis, + PreviousTxScript: pts, + SequenceNumber: DefaultSequenceNumber, // use default finalised sequence number + } + if err := i.PreviousTxIDAddStr(utxo.TxID); err != nil { + return err + } + tx.addInput(i) return nil } @@ -129,10 +123,13 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat // return bt.NewInputFrom(utxos[i].TxID, utxo[i].Vout, utxos[i].Script, utxos[i].Satoshis), true // } // }()) -func (tx *Tx) FromInputs(ctx context.Context, fq *FeeQuote, next InputGetterFunc) (err error) { - var feesPaid bool - for !feesPaid { - input, err := next(ctx) +func (tx *Tx) FromInputs(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) error { + deficit, err := tx.estimateDeficit(fq) + if err != nil { + return err + } + for deficit != 0 { + utxos, err := next(ctx, 0) if err != nil { if errors.Is(err, ErrNoInput) { break @@ -140,14 +137,19 @@ func (tx *Tx) FromInputs(ctx context.Context, fq *FeeQuote, next InputGetterFunc return err } - tx.addInput(input) - feesPaid, err = tx.EstimateIsFeePaidEnough(fq) + for _, utxo := range utxos { + if err = tx.From(utxo); err != nil { + return err + } + } + + deficit, err = tx.estimateDeficit(fq) if err != nil { return err } } - if !feesPaid { + if deficit != 0 { return errors.New("insufficient inputs provided") } diff --git a/txinput_test.go b/txinput_test.go index 5b836a31..bc248807 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -33,11 +33,12 @@ func TestTx_InputCount(t *testing.T) { t.Run("get input count", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From( + err := tx.From(&bt.UTXO{ "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + 4000000, + }) assert.NoError(t, err) assert.Equal(t, 1, tx.InputCount()) }) @@ -47,29 +48,32 @@ func TestTx_From(t *testing.T) { t.Run("invalid locking script (hex decode failed)", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From( + err := tx.From(&bt.UTXO{ "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "0", - 4000000) + 4000000, + }) assert.Error(t, err) - err = tx.From( + err = tx.From(&bt.UTXO{ "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae4016", - 4000000) + 4000000, + }) assert.Error(t, err) }) t.Run("valid script and tx", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From( + err := tx.From(&bt.UTXO{ "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + 4000000, + }) assert.NoError(t, err) inputs := tx.Inputs @@ -82,13 +86,13 @@ func TestTx_From(t *testing.T) { }) } -func TestTx_AddInputs(t *testing.T) { +func TestTx_FromInputs(t *testing.T) { tests := map[string]struct { - tx *bt.Tx - inputs []*bt.Input - inputGetterFunc bt.InputGetterFunc - expTotalInputs int - expErr error + tx *bt.Tx + utxos []*bt.UTXO + utxoGetterFuncOverrider func([]*bt.UTXO) bt.UTXOGetterFunc + expTotalInputs int + expErr error }{ "tx with exact inputs and surplus inputs is covered": { tx: func() *bt.Tx { @@ -96,29 +100,70 @@ func TestTx_AddInputs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - inputs: func() []*bt.Input { - tx := bt.NewTx() - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - - return tx.Inputs - }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }}, expTotalInputs: 2, }, - "tx with extra inputs and surplus inputs is covered with minimum needed inputs": { + "tx with extra inputs and surplus inputs is covered with all inputs": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - inputs: func() []*bt.Input { + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }}, + expTotalInputs: 3, + }, + "tx with extra inputs and surplus inputs that returns correct amount is covered with minimum needed inputs": { + tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - - return tx.Inputs + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) + return tx }(), + utxoGetterFuncOverrider: func(utxos []*bt.UTXO) bt.UTXOGetterFunc { + return func(ctx context.Context, satoshis uint64) ([]*bt.UTXO, error) { + return utxos[:2], nil + } + }, + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }}, expTotalInputs: 2, }, "tx with exact input satshis is covered": { @@ -127,34 +172,123 @@ func TestTx_AddInputs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - inputs: func() []*bt.Input { - tx := bt.NewTx() - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 670)) - - return tx.Inputs - }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }}, expTotalInputs: 2, }, - "tx with large amount of satoshis is covered": { + "tx with large amount of satoshis is covered with all inputs": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) return tx }(), - inputs: func() []*bt.Input { + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 500, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 670, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 700, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 650, + }}, + expTotalInputs: 8, + }, + "tx with large amount of satoshis is covered with needed inputs": { + tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 500)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 670)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 700)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 650)) - - return tx.Inputs + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) + return tx }(), + utxoGetterFuncOverrider: func(utxos []*bt.UTXO) bt.UTXOGetterFunc { + utxosCopy := make([]*bt.UTXO, len(utxos)) + copy(utxosCopy, utxos) + return func(ctx context.Context, sat uint64) ([]*bt.UTXO, error) { + defer func() { utxosCopy = utxosCopy[1:] }() + return utxosCopy[:1], nil + } + }, + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 500, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 670, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 700, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 650, + }}, expTotalInputs: 7, }, "getter with no inputs error": { @@ -163,7 +297,7 @@ func TestTx_AddInputs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - inputs: []*bt.Input{}, + utxos: []*bt.UTXO{}, expErr: errors.New("insufficient inputs provided"), }, "getter with insufficient inputs errors": { @@ -172,19 +306,47 @@ func TestTx_AddInputs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 25400)) return tx }(), - inputs: func() []*bt.Input { - tx := bt.NewTx() - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 500)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 670)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 700)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 1000)) - assert.NoError(t, tx.From("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 650)) - - return tx.Inputs - }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 500, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 670, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 700, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 650, + }}, expErr: errors.New("insufficient inputs provided"), }, "error is returned to the user": { @@ -193,28 +355,85 @@ func TestTx_AddInputs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) return tx }(), - inputGetterFunc: func(context.Context) (*bt.Input, error) { - return nil, errors.New("custom error") + utxoGetterFuncOverrider: func([]*bt.UTXO) bt.UTXOGetterFunc { + return func(context.Context, uint64) ([]*bt.UTXO, error) { + return nil, errors.New("custom error") + } }, - inputs: []*bt.Input{}, expErr: errors.New("custom error"), }, + "tx with large amount of satoshis is covered, with multiple iterations": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) + return tx + }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 500, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 670, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 700, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 650, + }}, + utxoGetterFuncOverrider: func(utxos []*bt.UTXO) bt.UTXOGetterFunc { + idx := 0 + return func(context.Context, uint64) ([]*bt.UTXO, error) { + defer func() { idx++ }() + return utxos[idx : idx+1], nil + } + }, + expTotalInputs: 7, + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - iptFn := func() bt.InputGetterFunc { + iptFn := func() bt.UTXOGetterFunc { idx := 0 - return func(ctx context.Context) (*bt.Input, error) { - if idx == len(test.inputs) { + return func(ctx context.Context, deficit uint64) ([]*bt.UTXO, error) { + if idx == len(test.utxos) { return nil, bt.ErrNoInput } - defer func() { idx++ }() - return test.inputs[idx], nil + defer func() { idx += len(test.utxos) }() + return test.utxos, nil } }() - if test.inputGetterFunc != nil { - iptFn = test.inputGetterFunc + if test.utxoGetterFuncOverrider != nil { + iptFn = test.utxoGetterFuncOverrider(test.utxos) } err := test.tx.FromInputs(context.Background(), bt.NewFeeQuote(), iptFn) diff --git a/txoutput_test.go b/txoutput_test.go index 68e57ecb..8683a2f2 100644 --- a/txoutput_test.go +++ b/txoutput_test.go @@ -140,10 +140,10 @@ func TestTx_PayToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.PayToAddress("", 100) @@ -154,10 +154,10 @@ func TestTx_PayToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.PayToAddress("1234567", 100) @@ -168,10 +168,10 @@ func TestTx_PayToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.PayToAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", 100) @@ -206,10 +206,10 @@ func TestTx_PayTo(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.PayTo(test.script, 100) if test.err == nil { diff --git a/txsign_test.go b/txsign_test.go index 6a7b1073..5f5b907f 100644 --- a/txsign_test.go +++ b/txsign_test.go @@ -21,10 +21,10 @@ func TestTx_SignAuto(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -60,10 +60,10 @@ func TestTx_SignAuto(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000) + &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000}) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) diff --git a/utxo.go b/utxo.go new file mode 100644 index 00000000..b9f802d7 --- /dev/null +++ b/utxo.go @@ -0,0 +1,9 @@ +package bt + +// UTXO an unspent transaction output, used for creating inputs +type UTXO struct { + TxID string + Vout uint32 + LockingScript string + Satoshis uint64 +} From 0d6ea41a28c67ca12a660a194bc132c042131431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 12:34:22 +0100 Subject: [PATCH 16/24] fixed deficit error and added tests --- txinput.go | 40 +++++---- txinput_test.go | 231 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 251 insertions(+), 20 deletions(-) diff --git a/txinput.go b/txinput.go index 4c3ad558..041115e4 100644 --- a/txinput.go +++ b/txinput.go @@ -12,13 +12,13 @@ import ( "github.com/libsv/go-bt/v2/bscript" ) -// ErrNoInput signals the InputGetterFunc has reached the end of its input. -var ErrNoInput = errors.New("no remaining inputs") +// ErrNoUTXO signals the InputGetterFunc has reached the end of its input. +var ErrNoUTXO = errors.New("no remaining inputs") -// UTXOGetterFunc is used for FromInputs. It expects *bt.UTXO to be returned containing -// relevant input information, and an err informing any retrieval errors. +// UTXOGetterFunc is used for tx.FromInputs. It expects []*bt.UTXO to be returned containing +// utxos of which an input can be built. // -// It is expected that bt.ErrNoInput will be returned once the input source is depleted. +// It is expected that bt.ErrNoUTXO will be returned once the utxo source is depleted. type UTXOGetterFunc func(ctx context.Context, deficit uint64) ([]*UTXO, error) // NewInputFromBytes returns a transaction input from the bytes provided. @@ -105,7 +105,7 @@ func (tx *Tx) From(utxo *UTXO) error { return nil } -// FromInputs continuously calls the provided bt.InputGetterFunc, adding each returned input +// FromUTXOs continuously calls the provided bt.UTXOGetterFunc, adding each returned input // as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. // // After completion, the receiver is ready for `Change(...)` to be called, and then be signed. @@ -113,25 +113,31 @@ func (tx *Tx) From(utxo *UTXO) error { // which need covered. // // Example usage, for when working with a list: -// tx.FromInputs(ctx, bt.NewFeeQuote(), func() bt.InputGetterFunc { -// i := 0 -// return func(ctx context.Context) (*bt.Input, error) { -// if i >= len(utxos) { -// return nil, bt.ErrNoInput +// tx.FromUTXOs(ctx, bt.NewFeeQuote(), func(ctx context.Context, deficit satoshis) ([]*bt.UTXO, error) { +// utxos := make([]*bt.Utxo, 0) +// for _, f := range funds { +// deficit -= satoshis +// utxos := append(utxos, &bt.UTXO{ +// TxID: f.TxID, +// Vout: f.Vout, +// LockingScript: f.Script, +// Satoshis: f.Satoshis, +// }) +// if deficit == 0 { +// return utxos, nil // } -// defer func() { i++ }() -// return bt.NewInputFrom(utxos[i].TxID, utxo[i].Vout, utxos[i].Script, utxos[i].Satoshis), true // } -// }()) -func (tx *Tx) FromInputs(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) error { +// return nil, bt.ErrNoUTXO +// }) +func (tx *Tx) FromUTXOs(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) error { deficit, err := tx.estimateDeficit(fq) if err != nil { return err } for deficit != 0 { - utxos, err := next(ctx, 0) + utxos, err := next(ctx, deficit) if err != nil { - if errors.Is(err, ErrNoInput) { + if errors.Is(err, ErrNoUTXO) { break } diff --git a/txinput_test.go b/txinput_test.go index bc248807..fab5e44e 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -4,6 +4,8 @@ import ( "context" "encoding/hex" "errors" + "fmt" + "math" "testing" "github.com/libsv/go-bt/v2" @@ -86,7 +88,7 @@ func TestTx_From(t *testing.T) { }) } -func TestTx_FromInputs(t *testing.T) { +func TestTx_FromUTXOs(t *testing.T) { tests := map[string]struct { tx *bt.Tx utxos []*bt.UTXO @@ -426,7 +428,7 @@ func TestTx_FromInputs(t *testing.T) { idx := 0 return func(ctx context.Context, deficit uint64) ([]*bt.UTXO, error) { if idx == len(test.utxos) { - return nil, bt.ErrNoInput + return nil, bt.ErrNoUTXO } defer func() { idx += len(test.utxos) }() return test.utxos, nil @@ -436,7 +438,7 @@ func TestTx_FromInputs(t *testing.T) { iptFn = test.utxoGetterFuncOverrider(test.utxos) } - err := test.tx.FromInputs(context.Background(), bt.NewFeeQuote(), iptFn) + err := test.tx.FromUTXOs(context.Background(), bt.NewFeeQuote(), iptFn) if test.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, test.expErr.Error()) @@ -448,3 +450,226 @@ func TestTx_FromInputs(t *testing.T) { }) } } + +func TestTx_FromUTXOs_Deficit(t *testing.T) { + tests := map[string]struct { + utxos []*bt.UTXO + expDeficits []uint64 + iteration int + tx *bt.Tx + }{ + "1 output worth 5000, 3 utxos worth 6000": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) + + return tx + }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }}, + iteration: 1, + expDeficits: []uint64{5022, 3096, 1170}, + }, + "1 output worth 5000, 3 utxos worth 6000, iterations of 2": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) + + return tx + }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }}, + iteration: 2, + expDeficits: []uint64{5022, 1170}, + }, + "5 outputs worth 35000, 12 utxos worth 37000": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 10000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 7000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 3000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 10000)) + + return tx + }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 6000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 8000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 3000, + }}, + iteration: 1, + expDeficits: []uint64{35090, 33164, 31238, 29312, 27386, 23460, 21534, 15608, 11682, 9756, 1830}, + }, + "5 outputs worth 35000, 12 utxos worth 37000, iteration of 3": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 10000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 7000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 3000)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 10000)) + + return tx + }(), + utxos: []*bt.UTXO{{ + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 6000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 8000, + }, { + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 3000, + }}, + iteration: 3, + expDeficits: []uint64{35090, 29312, 21534, 9756}, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + deficits := make([]uint64, 0) + test.tx.FromUTXOs(context.Background(), bt.NewFeeQuote(), func(ctx context.Context, deficit uint64) ([]*bt.UTXO, error) { + if len(test.utxos) == 0 { + return nil, bt.ErrNoUTXO + } + step := int(math.Min(float64(test.iteration), float64(len(test.utxos)))) + defer func() { + test.utxos = test.utxos[step:] + }() + + deficits = append(deficits, deficit) + fmt.Println(test.utxos[:step]) + return test.utxos[:step], nil + }) + + assert.Equal(t, test.expDeficits, deficits) + }) + } +} From e2f01a149763ba5b6f80408c4e563839b7690b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 14:03:57 +0100 Subject: [PATCH 17/24] removed println from tests --- txinput_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/txinput_test.go b/txinput_test.go index fab5e44e..d5c6c9fc 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/hex" "errors" - "fmt" "math" "testing" @@ -89,6 +88,7 @@ func TestTx_From(t *testing.T) { } func TestTx_FromUTXOs(t *testing.T) { + t.Parallel() tests := map[string]struct { tx *bt.Tx utxos []*bt.UTXO @@ -452,6 +452,7 @@ func TestTx_FromUTXOs(t *testing.T) { } func TestTx_FromUTXOs_Deficit(t *testing.T) { + t.Parallel() tests := map[string]struct { utxos []*bt.UTXO expDeficits []uint64 @@ -665,7 +666,6 @@ func TestTx_FromUTXOs_Deficit(t *testing.T) { }() deficits = append(deficits, deficit) - fmt.Println(test.utxos[:step]) return test.utxos[:step], nil }) From cf29059be6155a313e9f4aaf11eb318702e83411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 14:08:52 +0100 Subject: [PATCH 18/24] fixed doc string typos --- txinput.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/txinput.go b/txinput.go index 041115e4..3bebcd37 100644 --- a/txinput.go +++ b/txinput.go @@ -12,10 +12,10 @@ import ( "github.com/libsv/go-bt/v2/bscript" ) -// ErrNoUTXO signals the InputGetterFunc has reached the end of its input. +// ErrNoUTXO signals the UTXOGetterFunc has reached the end of its input. var ErrNoUTXO = errors.New("no remaining inputs") -// UTXOGetterFunc is used for tx.FromInputs. It expects []*bt.UTXO to be returned containing +// UTXOGetterFunc is used for tx.FromUTXOs. It expects []*bt.UTXO to be returned containing // utxos of which an input can be built. // // It is expected that bt.ErrNoUTXO will be returned once the utxo source is depleted. @@ -60,7 +60,7 @@ func (tx *Tx) addInput(input *Input) { // AddP2PKHInputsFromTx will add all Outputs of given previous transaction // that match a specific public key to your transaction. func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { - txID := pvsTx.TxID() + prevTxID := pvsTx.TxID() for i, utxo := range pvsTx.Outputs { utxoPkHASH160, err := utxo.LockingScript.PublicKeyHash() if err != nil { @@ -69,7 +69,7 @@ func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { if bytes.Equal(utxoPkHASH160, crypto.Hash160(matchPK)) { if err := tx.From(&UTXO{ - TxID: txID, + TxID: prevTxID, Vout: uint32(i), Satoshis: utxo.Satoshis, LockingScript: utxo.LockingScriptHexString(), From 2db91909d69e8f889b9b451e636146aed8efed64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 14:12:25 +0100 Subject: [PATCH 19/24] fixed more typos in docs --- txinput.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/txinput.go b/txinput.go index 3bebcd37..a48ad475 100644 --- a/txinput.go +++ b/txinput.go @@ -13,7 +13,7 @@ import ( ) // ErrNoUTXO signals the UTXOGetterFunc has reached the end of its input. -var ErrNoUTXO = errors.New("no remaining inputs") +var ErrNoUTXO = errors.New("no remaining utxos") // UTXOGetterFunc is used for tx.FromUTXOs. It expects []*bt.UTXO to be returned containing // utxos of which an input can be built. @@ -114,7 +114,7 @@ func (tx *Tx) From(utxo *UTXO) error { // // Example usage, for when working with a list: // tx.FromUTXOs(ctx, bt.NewFeeQuote(), func(ctx context.Context, deficit satoshis) ([]*bt.UTXO, error) { -// utxos := make([]*bt.Utxo, 0) +// utxos := make([]*bt.UTXO, 0) // for _, f := range funds { // deficit -= satoshis // utxos := append(utxos, &bt.UTXO{ @@ -156,7 +156,7 @@ func (tx *Tx) FromUTXOs(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) } } if deficit != 0 { - return errors.New("insufficient inputs provided") + return errors.New("insufficient utxos provided") } return nil From 5eb4ae6d465aae4f11b3816c4de563397c43e866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 14:19:59 +0100 Subject: [PATCH 20/24] fixed tests --- txinput_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/txinput_test.go b/txinput_test.go index d5c6c9fc..14bbccce 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -115,7 +115,7 @@ func TestTx_FromUTXOs(t *testing.T) { }}, expTotalInputs: 2, }, - "tx with extra inputs and surplus inputs is covered with all inputs": { + "tx with extra inputs and surplus inputs is covered with all utxos": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) @@ -139,7 +139,7 @@ func TestTx_FromUTXOs(t *testing.T) { }}, expTotalInputs: 3, }, - "tx with extra inputs and surplus inputs that returns correct amount is covered with minimum needed inputs": { + "tx with extra inputs and surplus inputs that returns correct amount is covered with minimum needed utxos": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) @@ -187,7 +187,7 @@ func TestTx_FromUTXOs(t *testing.T) { }}, expTotalInputs: 2, }, - "tx with large amount of satoshis is covered with all inputs": { + "tx with large amount of satoshis is covered with all utxos": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) @@ -236,7 +236,7 @@ func TestTx_FromUTXOs(t *testing.T) { }}, expTotalInputs: 8, }, - "tx with large amount of satoshis is covered with needed inputs": { + "tx with large amount of satoshis is covered with needed utxos": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) @@ -293,16 +293,16 @@ func TestTx_FromUTXOs(t *testing.T) { }}, expTotalInputs: 7, }, - "getter with no inputs error": { + "getter with no utxos error": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), utxos: []*bt.UTXO{}, - expErr: errors.New("insufficient inputs provided"), + expErr: errors.New("insufficient utxos provided"), }, - "getter with insufficient inputs errors": { + "getter with insufficient utxos errors": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 25400)) @@ -349,7 +349,7 @@ func TestTx_FromUTXOs(t *testing.T) { "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 650, }}, - expErr: errors.New("insufficient inputs provided"), + expErr: errors.New("insufficient utxos provided"), }, "error is returned to the user": { tx: func() *bt.Tx { From 85398dde8669afd9c73f277945d2b1a2b5eaff7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 15:35:29 +0100 Subject: [PATCH 21/24] added comment explaining precalculating prevTxID --- txinput.go | 1 + 1 file changed, 1 insertion(+) diff --git a/txinput.go b/txinput.go index a48ad475..40dfcb30 100644 --- a/txinput.go +++ b/txinput.go @@ -60,6 +60,7 @@ func (tx *Tx) addInput(input *Input) { // AddP2PKHInputsFromTx will add all Outputs of given previous transaction // that match a specific public key to your transaction. func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { + // Given that the prevTxID never changes, calculate it once up front. prevTxID := pvsTx.TxID() for i, utxo := range pvsTx.Outputs { utxoPkHASH160, err := utxo.LockingScript.PublicKeyHash() From 1a7d125688ea10358039c5d4cd5e6faa1310db59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 17:06:40 +0100 Subject: [PATCH 22/24] added FromUTXOs func. renamed old one to Fund --- examples/create_tx/create_tx.go | 12 +- .../create_tx_with_opreturn.go | 12 +- tx_test.go | 188 ++--- txchange_test.go | 120 ++-- txinput.go | 60 +- txinput_test.go | 675 ++++++++---------- txoutput_test.go | 32 +- txsign_test.go | 16 +- utxo.go | 6 +- 9 files changed, 536 insertions(+), 585 deletions(-) diff --git a/examples/create_tx/create_tx.go b/examples/create_tx/create_tx.go index b55fa986..99d7bc25 100644 --- a/examples/create_tx/create_tx.go +++ b/examples/create_tx/create_tx.go @@ -11,12 +11,12 @@ import ( func main() { tx := bt.NewTx() - _ = tx.From(&bt.UTXO{ - TxID: "11b476ad8e0a48fcd40807a111a050af51114877e09283bfa7f3505081a1819d", - Vout: 0, - LockingScript: "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac6a0568656c6c6f", - Satoshis: 1500, - }) + _ = tx.From( + "11b476ad8e0a48fcd40807a111a050af51114877e09283bfa7f3505081a1819d", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac6a0568656c6c6f", + 1500, + ) _ = tx.PayToAddress("1NRoySJ9Lvby6DuE2UQYnyT67AASwNZxGb", 1000) diff --git a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go index fe94dac0..897aa682 100644 --- a/examples/create_tx_with_opreturn/create_tx_with_opreturn.go +++ b/examples/create_tx_with_opreturn/create_tx_with_opreturn.go @@ -11,12 +11,12 @@ import ( func main() { tx := bt.NewTx() - _ = tx.From(&bt.UTXO{ - TxID: "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", - Vout: 0, - LockingScript: "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac", - Satoshis: 1000, - }) + _ = tx.From( + "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", + 0, + "76a9149cbe9f5e72fa286ac8a38052d1d5337aa363ea7f88ac", + 1000, + ) _ = tx.PayToAddress("1C8bzHM8XFBHZ2ZZVvFy2NSoAZbwCXAicL", 900) diff --git a/tx_test.go b/tx_test.go index 9e811698..9711ab7e 100644 --- a/tx_test.go +++ b/tx_test.go @@ -197,12 +197,12 @@ func TestTx_CreateTx(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From(&bt.UTXO{ + err := tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - }) + ) assert.NoError(t, err) err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) @@ -224,12 +224,12 @@ func TestTx_HasDataOutputs(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From(&bt.UTXO{ + err := tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - }) + ) assert.NoError(t, err) err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) @@ -257,12 +257,12 @@ func TestTx_HasDataOutputs(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From(&bt.UTXO{ + err := tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - }) + ) assert.NoError(t, err) err = tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1999942) @@ -295,12 +295,12 @@ func TestTx_JSON(t *testing.T) { "standard tx should marshal and unmarshall correctly": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - })) + )) assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var wif *WIF wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") @@ -314,12 +314,12 @@ func TestTx_JSON(t *testing.T) { }, "data tx should marshall correctly": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - })) + )) assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var wif *WIF wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") @@ -406,24 +406,24 @@ func TestTx_MarshallJSON(t *testing.T) { }, "transaction with multiple Inputs": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 10000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 2, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 10000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 114, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 10000, - })) + )) assert.NoError(t, tx.PayToAddress("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) var w *wif.WIF w, err := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") @@ -734,24 +734,24 @@ func TestTx_InputIdx(t *testing.T) { "tx with 3 Inputs and input idx 0 requested should return correct input": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 1000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - })) + )) return tx }(), idx: 0, @@ -772,24 +772,24 @@ func TestTx_InputIdx(t *testing.T) { }, "tx with 3 Outputs and output idx 2 requested should return output": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 1000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdac4", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 999, - })) + )) return tx }(), idx: 2, @@ -810,24 +810,24 @@ func TestTx_InputIdx(t *testing.T) { }, "tx with 3 Outputs and output idx 5 requested should return nil": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 1000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 2000000, - })) - assert.NoError(t, tx.From(&bt.UTXO{ + )) + assert.NoError(t, tx.From( "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", 0, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", 999, - })) + )) return tx }(), idx: 5, @@ -927,10 +927,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { "unsigned transaction (1 input 1 P2PKHOutput + no change) paying less by 1 satoshi": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000, - })) + )) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 905)) return tx @@ -943,10 +943,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 1 P2PKHOutput + change) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709, - })) + )) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -974,10 +974,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834763, - })) + )) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -992,10 +992,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should fail paying less by 1 sat": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834763, - })) + )) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256560)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -1014,10 +1014,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709, - })) + )) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1036,10 +1036,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000, - })) + )) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 904)) tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1057,10 +1057,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000, - })) + )) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 894)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1081,10 +1081,10 @@ func Test_EstimateIsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000, - })) + )) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 895)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1124,8 +1124,8 @@ func Test_IsFeePaidEnough(t *testing.T) { "unsigned transaction (1 input 1 P2PKHOutput + no change) paying less by 1 satoshi": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 959)) return tx @@ -1138,8 +1138,8 @@ func Test_IsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 1 P2PKHOutput + change) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1167,8 +1167,8 @@ func Test_IsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -1183,8 +1183,8 @@ func Test_IsFeePaidEnough(t *testing.T) { }, "unsigned transaction (1 input 2 P2PKHOutputs) should fail paying less by 1 sat": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256560)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) @@ -1203,8 +1203,8 @@ func Test_IsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1223,8 +1223,8 @@ func Test_IsFeePaidEnough(t *testing.T) { if err != nil { log.Fatal(err) } - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 904)) tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1242,8 +1242,8 @@ func Test_IsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000})) + assert.NoError(t, tx.From("160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 894)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1264,8 +1264,8 @@ func Test_IsFeePaidEnough(t *testing.T) { log.Fatal(err) } tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000})) + assert.NoError(t, tx.From("160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 895)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) @@ -1305,8 +1305,8 @@ func Test_EstimateFeesPaid(t *testing.T) { "226B transaction (1 input 1 P2PKHOutput + no change) no data should return 113 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) return tx @@ -1323,8 +1323,8 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "226B transaction (1 input 1 P2PKHOutput + change) no data should return 113 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) @@ -1343,8 +1343,8 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "214B unsigned transaction (1 input, 1 opreturn, no change) 10 byte of data should return 100 sats fee 6 data fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) return tx }(), @@ -1361,14 +1361,14 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "556B unsigned transaction (3 inputs + 2 outputs + no change) no data should return 261 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - err := tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) + err := tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) assert.NoError(t, err) - err = tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) + err = tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) assert.NoError(t, err) - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) return tx @@ -1386,14 +1386,14 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "556B unsigned transaction (3 inputs + 2 outputs + 1 change) no data should return 278 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() - err := tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) + err := tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) assert.NoError(t, err) - err = tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000}) + err = tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000) assert.NoError(t, err) - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote()) @@ -1412,12 +1412,12 @@ func Test_EstimateFeesPaid(t *testing.T) { }, "565B unsigned transaction 100B data should return 63 sats std fee, 50 data fee": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100})) - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100})) - assert.NoError(t, tx.From(&bt.UTXO{"a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000})) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100)) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 100)) + assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 1000)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) assert.NoError(t, tx.AddOpReturnOutput(make([]byte, 0x64))) @@ -1460,11 +1460,11 @@ func TestTx_EstimateFeesPaidTotal(t *testing.T) { "Transaction with one input one output should return 96": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000})) + 1000)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), @@ -1473,12 +1473,12 @@ func TestTx_EstimateFeesPaidTotal(t *testing.T) { }, "Transaction with one input 4 Outputs should return 147": { tx: func() *bt.Tx { tx := bt.NewTx() - assert.NoError(t, tx.From(&bt.UTXO{ + assert.NoError(t, tx.From( "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 2500, - })) + )) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) diff --git a/txchange_test.go b/txchange_test.go index f6446512..06d7a0d4 100644 --- a/txchange_test.go +++ b/txchange_test.go @@ -15,10 +15,10 @@ func TestTx_ChangeToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("", nil) @@ -29,10 +29,10 @@ func TestTx_ChangeToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", nil) @@ -43,10 +43,10 @@ func TestTx_ChangeToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", bt.NewFeeQuote()) @@ -69,10 +69,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err = tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -95,10 +95,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -129,10 +129,10 @@ func TestTx_Change(t *testing.T) { // utxo err := tx.From( - &bt.UTXO{"b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000}) + "b7b0650a7c3a1bd4716369783876348b59f5404784970192cec1996e86950576", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000) assert.NoError(t, err) // pay to @@ -177,10 +177,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -204,10 +204,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000000) @@ -238,10 +238,10 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 999995) @@ -274,17 +274,17 @@ func TestTx_Change(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"9e88ca8eec0845e9e864c024bc5e6711e670932c9c7d929f9fccdb2c440ae28e", - 0, - "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", - 5689}) + "9e88ca8eec0845e9e864c024bc5e6711e670932c9c7d929f9fccdb2c440ae28e", + 0, + "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", + 5689) assert.NoError(t, err) err = tx.From( - &bt.UTXO{"4e25b077d4cbb955b5a215feb53f963cf04688ff1777b9bea097c7ddbdf7ea42", - 0, - "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", - 5689}) + "4e25b077d4cbb955b5a215feb53f963cf04688ff1777b9bea097c7ddbdf7ea42", + 0, + "76a9147824dec00be2c45dad83c9b5e9f5d7ef05ba3cf988ac", + 5689) assert.NoError(t, err) err = tx.ChangeToAddress("1BxGFoRPSFgYxoAStEncL6HuELqPkV3JVj", bt.NewFeeQuote()) @@ -318,10 +318,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000})) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 1000)) return tx }(), @@ -334,10 +334,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000})) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), @@ -350,10 +350,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2500})) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 2500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) @@ -369,10 +369,10 @@ func TestTx_ChangeToOutput(t *testing.T) { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000})) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 1000)) assert.NoError(t, tx.PayToAddress("mxAoAyZFXX6LZBWhoam3vjm6xt9NxPQ15f", 500)) return tx }(), diff --git a/txinput.go b/txinput.go index 40dfcb30..3688067e 100644 --- a/txinput.go +++ b/txinput.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/binary" + "encoding/hex" "errors" "fmt" @@ -15,7 +16,7 @@ import ( // ErrNoUTXO signals the UTXOGetterFunc has reached the end of its input. var ErrNoUTXO = errors.New("no remaining utxos") -// UTXOGetterFunc is used for tx.FromUTXOs. It expects []*bt.UTXO to be returned containing +// UTXOGetterFunc is used for tx.Fund. It expects []*bt.UTXO to be returned containing // utxos of which an input can be built. // // It is expected that bt.ErrNoUTXO will be returned once the utxo source is depleted. @@ -61,7 +62,7 @@ func (tx *Tx) addInput(input *Input) { // that match a specific public key to your transaction. func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { // Given that the prevTxID never changes, calculate it once up front. - prevTxID := pvsTx.TxID() + prevTxIDBytes := pvsTx.TxIDBytes() for i, utxo := range pvsTx.Outputs { utxoPkHASH160, err := utxo.LockingScript.PublicKeyHash() if err != nil { @@ -69,11 +70,11 @@ func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { } if bytes.Equal(utxoPkHASH160, crypto.Hash160(matchPK)) { - if err := tx.From(&UTXO{ - TxID: prevTxID, + if err := tx.FromUTXOs(&UTXO{ + TxID: prevTxIDBytes, Vout: uint32(i), Satoshis: utxo.Satoshis, - LockingScript: utxo.LockingScriptHexString(), + LockingScript: utxo.LockingScript, }); err != nil { return err } @@ -86,27 +87,46 @@ func (tx *Tx) AddP2PKHInputsFromTx(pvsTx *Tx, matchPK []byte) error { // From adds a new input to the transaction from the specified UTXO fields, using the default // finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually // afterwards. -func (tx *Tx) From(utxo *UTXO) error { - pts, err := bscript.NewFromHexString(utxo.LockingScript) +func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, satoshis uint64) error { + pts, err := bscript.NewFromHexString(prevTxLockingScript) if err != nil { return err } - - i := &Input{ - PreviousTxOutIndex: utxo.Vout, - PreviousTxSatoshis: utxo.Satoshis, - PreviousTxScript: pts, - SequenceNumber: DefaultSequenceNumber, // use default finalised sequence number - } - if err := i.PreviousTxIDAddStr(utxo.TxID); err != nil { + pti, err := hex.DecodeString(prevTxID) + if err != nil { return err } - tx.addInput(i) + return tx.FromUTXOs(&UTXO{ + TxID: pti, + Vout: vout, + LockingScript: pts, + Satoshis: satoshis, + }) +} + +// From adds a new input to the transaction from the specified *bt.UTXO fields, using the default +// finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually +// afterwards. +func (tx *Tx) FromUTXOs(utxos ...*UTXO) error { + for _, utxo := range utxos { + i := &Input{ + PreviousTxOutIndex: utxo.Vout, + PreviousTxSatoshis: utxo.Satoshis, + PreviousTxScript: utxo.LockingScript, + SequenceNumber: DefaultSequenceNumber, // use default finalised sequence number + } + if err := i.PreviousTxIDAdd(utxo.TxID); err != nil { + return err + } + + tx.addInput(i) + } + return nil } -// FromUTXOs continuously calls the provided bt.UTXOGetterFunc, adding each returned input +// Fund continuously calls the provided bt.UTXOGetterFunc, adding each returned input // as an input via tx.From(...), until it is estimated that inputs cover the outputs + fees. // // After completion, the receiver is ready for `Change(...)` to be called, and then be signed. @@ -114,7 +134,7 @@ func (tx *Tx) From(utxo *UTXO) error { // which need covered. // // Example usage, for when working with a list: -// tx.FromUTXOs(ctx, bt.NewFeeQuote(), func(ctx context.Context, deficit satoshis) ([]*bt.UTXO, error) { +// tx.Fund(ctx, bt.NewFeeQuote(), func(ctx context.Context, deficit satoshis) ([]*bt.UTXO, error) { // utxos := make([]*bt.UTXO, 0) // for _, f := range funds { // deficit -= satoshis @@ -130,7 +150,7 @@ func (tx *Tx) From(utxo *UTXO) error { // } // return nil, bt.ErrNoUTXO // }) -func (tx *Tx) FromUTXOs(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) error { +func (tx *Tx) Fund(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) error { deficit, err := tx.estimateDeficit(fq) if err != nil { return err @@ -146,7 +166,7 @@ func (tx *Tx) FromUTXOs(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) } for _, utxo := range utxos { - if err = tx.From(utxo); err != nil { + if err = tx.FromUTXOs(utxo); err != nil { return err } } diff --git a/txinput_test.go b/txinput_test.go index 14bbccce..f247c9a3 100644 --- a/txinput_test.go +++ b/txinput_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/libsv/go-bt/v2" + "github.com/libsv/go-bt/v2/bscript" "github.com/stretchr/testify/assert" ) @@ -34,12 +35,12 @@ func TestTx_InputCount(t *testing.T) { t.Run("get input count", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From(&bt.UTXO{ + err := tx.From( "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 4000000, - }) + ) assert.NoError(t, err) assert.Equal(t, 1, tx.InputCount()) }) @@ -49,32 +50,32 @@ func TestTx_From(t *testing.T) { t.Run("invalid locking script (hex decode failed)", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From(&bt.UTXO{ + err := tx.From( "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "0", 4000000, - }) + ) assert.Error(t, err) - err = tx.From(&bt.UTXO{ + err = tx.From( "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae4016", 4000000, - }) + ) assert.Error(t, err) }) t.Run("valid script and tx", func(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) - err := tx.From(&bt.UTXO{ + err := tx.From( "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", 0, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", 4000000, - }) + ) assert.NoError(t, err) inputs := tx.Inputs @@ -89,6 +90,72 @@ func TestTx_From(t *testing.T) { func TestTx_FromUTXOs(t *testing.T) { t.Parallel() + + t.Run("one utxo", func(t *testing.T) { + tx := bt.NewTx() + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + + txID, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + + assert.NoError(t, tx.FromUTXOs(&bt.UTXO{ + TxID: txID, + LockingScript: script, + Vout: 0, + Satoshis: 1000, + })) + + input := tx.Inputs[0] + assert.Equal(t, len(tx.Inputs), 1) + assert.Equal(t, "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", input.PreviousTxIDStr()) + assert.Equal(t, uint32(0), input.PreviousTxOutIndex) + assert.Equal(t, uint64(1000), input.PreviousTxSatoshis) + assert.Equal(t, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", input.PreviousTxScript.String()) + }) + + t.Run("multiple utxos", func(t *testing.T) { + tx := bt.NewTx() + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + txID, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + + script2, err := bscript.NewFromHexString("76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac") + assert.NoError(t, err) + txID2, err := hex.DecodeString("3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5") + assert.NoError(t, err) + + assert.NoError(t, tx.FromUTXOs(&bt.UTXO{ + TxID: txID, + LockingScript: script, + Vout: 0, + Satoshis: 1000, + }, &bt.UTXO{ + TxID: txID2, + LockingScript: script2, + Vout: 1, + Satoshis: 2000, + })) + + assert.Equal(t, len(tx.Inputs), 2) + + input := tx.Inputs[0] + assert.Equal(t, "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", input.PreviousTxIDStr()) + assert.Equal(t, uint32(0), input.PreviousTxOutIndex) + assert.Equal(t, uint64(1000), input.PreviousTxSatoshis) + assert.Equal(t, "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", input.PreviousTxScript.String()) + + input = tx.Inputs[1] + assert.Equal(t, "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", input.PreviousTxIDStr()) + assert.Equal(t, uint32(1), input.PreviousTxOutIndex) + assert.Equal(t, uint64(2000), input.PreviousTxSatoshis) + assert.Equal(t, "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", input.PreviousTxScript.String()) + }) +} + +func TestTx_Fund(t *testing.T) { + t.Parallel() tests := map[string]struct { tx *bt.Tx utxos []*bt.UTXO @@ -102,17 +169,17 @@ func TestTx_FromUTXOs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }} + }(), expTotalInputs: 2, }, "tx with extra inputs and surplus inputs is covered with all utxos": { @@ -121,22 +188,19 @@ func TestTx_FromUTXOs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }} + }(), expTotalInputs: 3, }, "tx with extra inputs and surplus inputs that returns correct amount is covered with minimum needed utxos": { @@ -150,22 +214,19 @@ func TestTx_FromUTXOs(t *testing.T) { return utxos[:2], nil } }, - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }} + }(), expTotalInputs: 2, }, "tx with exact input satshis is covered": { @@ -174,17 +235,17 @@ func TestTx_FromUTXOs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 1500)) return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }} + }(), expTotalInputs: 2, }, "tx with large amount of satoshis is covered with all utxos": { @@ -193,47 +254,29 @@ func TestTx_FromUTXOs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 500, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 670, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 700, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 650, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 500, + }, { + txid, 0, script, 670, + }, { + txid, 0, script, 700, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 650, + }} + }(), expTotalInputs: 8, }, "tx with large amount of satoshis is covered with needed utxos": { @@ -250,47 +293,29 @@ func TestTx_FromUTXOs(t *testing.T) { return utxosCopy[:1], nil } }, - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 500, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 670, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 700, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 650, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 500, + }, { + txid, 0, script, 670, + }, { + txid, 0, script, 700, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 650, + }} + }(), expTotalInputs: 7, }, "getter with no utxos error": { @@ -308,47 +333,29 @@ func TestTx_FromUTXOs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 25400)) return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 500, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 670, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 700, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 650, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 500, + }, { + txid, 0, script, 670, + }, { + txid, 0, script, 700, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 650, + }} + }(), expErr: errors.New("insufficient utxos provided"), }, "error is returned to the user": { @@ -370,47 +377,29 @@ func TestTx_FromUTXOs(t *testing.T) { assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 5000)) return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 500, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 670, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 700, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 1000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 650, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 500, + }, { + txid, 0, script, 670, + }, { + txid, 0, script, 700, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 1000, + }, { + txid, 0, script, 650, + }} + }(), utxoGetterFuncOverrider: func(utxos []*bt.UTXO) bt.UTXOGetterFunc { idx := 0 return func(context.Context, uint64) ([]*bt.UTXO, error) { @@ -438,7 +427,7 @@ func TestTx_FromUTXOs(t *testing.T) { iptFn = test.utxoGetterFuncOverrider(test.utxos) } - err := test.tx.FromUTXOs(context.Background(), bt.NewFeeQuote(), iptFn) + err := test.tx.Fund(context.Background(), bt.NewFeeQuote(), iptFn) if test.expErr != nil { assert.Error(t, err) assert.EqualError(t, err, test.expErr.Error()) @@ -451,7 +440,7 @@ func TestTx_FromUTXOs(t *testing.T) { } } -func TestTx_FromUTXOs_Deficit(t *testing.T) { +func TestTx_Fund_Deficit(t *testing.T) { t.Parallel() tests := map[string]struct { utxos []*bt.UTXO @@ -466,22 +455,19 @@ func TestTx_FromUTXOs_Deficit(t *testing.T) { return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }} + }(), iteration: 1, expDeficits: []uint64{5022, 3096, 1170}, }, @@ -492,22 +478,19 @@ func TestTx_FromUTXOs_Deficit(t *testing.T) { return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }} + }(), iteration: 2, expDeficits: []uint64{5022, 1170}, }, @@ -522,62 +505,35 @@ func TestTx_FromUTXOs_Deficit(t *testing.T) { return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 6000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 8000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 3000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 4000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 6000, + }, { + txid, 0, script, 4000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 8000, + }, { + txid, 0, script, 3000, + }} + }(), iteration: 1, expDeficits: []uint64{35090, 33164, 31238, 29312, 27386, 23460, 21534, 15608, 11682, 9756, 1830}, }, @@ -592,62 +548,35 @@ func TestTx_FromUTXOs_Deficit(t *testing.T) { return tx }(), - utxos: []*bt.UTXO{{ - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 6000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 2000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 8000, - }, { - "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 3000, - }}, + utxos: func() []*bt.UTXO { + txid, err := hex.DecodeString("07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b") + assert.NoError(t, err) + script, err := bscript.NewFromHexString("76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac") + assert.NoError(t, err) + return []*bt.UTXO{{ + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 4000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 6000, + }, { + txid, 0, script, 4000, + }, { + txid, 0, script, 2000, + }, { + txid, 0, script, 8000, + }, { + txid, 0, script, 3000, + }} + }(), iteration: 3, expDeficits: []uint64{35090, 29312, 21534, 9756}, }, @@ -656,7 +585,7 @@ func TestTx_FromUTXOs_Deficit(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { deficits := make([]uint64, 0) - test.tx.FromUTXOs(context.Background(), bt.NewFeeQuote(), func(ctx context.Context, deficit uint64) ([]*bt.UTXO, error) { + test.tx.Fund(context.Background(), bt.NewFeeQuote(), func(ctx context.Context, deficit uint64) ([]*bt.UTXO, error) { if len(test.utxos) == 0 { return nil, bt.ErrNoUTXO } diff --git a/txoutput_test.go b/txoutput_test.go index 8683a2f2..68e57ecb 100644 --- a/txoutput_test.go +++ b/txoutput_test.go @@ -140,10 +140,10 @@ func TestTx_PayToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.PayToAddress("", 100) @@ -154,10 +154,10 @@ func TestTx_PayToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.PayToAddress("1234567", 100) @@ -168,10 +168,10 @@ func TestTx_PayToAddress(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.PayToAddress("1GHMW7ABrFma2NSwiVe9b9bZxkMB7tuPZi", 100) @@ -206,10 +206,10 @@ func TestTx_PayTo(t *testing.T) { tx := bt.NewTx() assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.PayTo(test.script, 100) if test.err == nil { diff --git a/txsign_test.go b/txsign_test.go index 5f5b907f..6a7b1073 100644 --- a/txsign_test.go +++ b/txsign_test.go @@ -21,10 +21,10 @@ func TestTx_SignAuto(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) @@ -60,10 +60,10 @@ func TestTx_SignAuto(t *testing.T) { assert.NotNil(t, tx) err := tx.From( - &bt.UTXO{"07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", - 0, - "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", - 4000000}) + "07912972e42095fe58daaf09161c5a5da57be47c2054dc2aaa52b30fefa1940b", + 0, + "76a914af2590a45ae401651fdbdf59a76ad43d1862534088ac", + 4000000) assert.NoError(t, err) err = tx.ChangeToAddress("mwV3YgnowbJJB3LcyCuqiKpdivvNNFiK7M", bt.NewFeeQuote()) diff --git a/utxo.go b/utxo.go index b9f802d7..a80967b6 100644 --- a/utxo.go +++ b/utxo.go @@ -1,9 +1,11 @@ package bt +import "github.com/libsv/go-bt/v2/bscript" + // UTXO an unspent transaction output, used for creating inputs type UTXO struct { - TxID string + TxID []byte Vout uint32 - LockingScript string + LockingScript *bscript.Script Satoshis uint64 } From 73fd25965fe1341b329271864487365113742da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Tue, 21 Sep 2021 17:08:52 +0100 Subject: [PATCH 23/24] fixed doc string --- txinput.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/txinput.go b/txinput.go index 3688067e..60efcfa4 100644 --- a/txinput.go +++ b/txinput.go @@ -105,7 +105,7 @@ func (tx *Tx) From(prevTxID string, vout uint32, prevTxLockingScript string, sat }) } -// From adds a new input to the transaction from the specified *bt.UTXO fields, using the default +// FromUTXOs adds a new input to the transaction from the specified *bt.UTXO fields, using the default // finalised sequence number (0xFFFFFFFF). If you want a different nSeq, change it manually // afterwards. func (tx *Tx) FromUTXOs(utxos ...*UTXO) error { From a163144904e0e3796db24a0307cb87295a7ef348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=ADghearn=C3=A1n=20Carroll?= Date: Wed, 22 Sep 2021 12:34:36 +0100 Subject: [PATCH 24/24] updated doc string to explain UTXOGetterFunc param --- txinput.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/txinput.go b/txinput.go index 60efcfa4..2f534793 100644 --- a/txinput.go +++ b/txinput.go @@ -16,8 +16,11 @@ import ( // ErrNoUTXO signals the UTXOGetterFunc has reached the end of its input. var ErrNoUTXO = errors.New("no remaining utxos") -// UTXOGetterFunc is used for tx.Fund. It expects []*bt.UTXO to be returned containing -// utxos of which an input can be built. +// UTXOGetterFunc is used for tx.Fund(...). It provides the amount of satoshis required +// for funding as `deficit`, and expects []*bt.UTXO to be returned containing +// utxos of which *bt.Input's can be built. +// If the returned []*bt.UTXO does not cover the deficit after fee recalculation, then +// this UTXOGetterFunc is called again, with the newly calculated deficit passed in. // // It is expected that bt.ErrNoUTXO will be returned once the utxo source is depleted. type UTXOGetterFunc func(ctx context.Context, deficit uint64) ([]*UTXO, error)