From bd71290a5c3fcc6bc749a69d0523ae1e9f2e5b69 Mon Sep 17 00:00:00 2001 From: Jad Wahab Date: Mon, 6 Sep 2021 00:16:28 +0100 Subject: [PATCH] change! FeesPaid -> IsFeePaidEnough this is to be used on the receiving end of a tx/payment --- tx.go | 15 ++++--- tx_test.go | 116 +++++++++++++++++++++++++++++++++++------------------ 2 files changed, 86 insertions(+), 45 deletions(-) diff --git a/tx.go b/tx.go index e42699b3..ed276b6d 100644 --- a/tx.go +++ b/tx.go @@ -406,11 +406,15 @@ type TxFees struct { DataFeePaid uint64 } -// FeesPaid will calculate the fees that this transaction is paying +// IsFeePaidEnough will calculate the fees that this transaction is paying // including the individual fee types (std/data/etc.). -func (tx *Tx) FeesPaid(fees *FeeQuote) (*TxFees, error) { - size := tx.SizeWithTypes() - return tx.feesPaid(size, fees) +func (tx *Tx) IsFeePaidEnough(fees *FeeQuote) (bool, error) { + expFeesPaid, err := tx.feesPaid(tx.SizeWithTypes(), fees) + if err != nil { + return false, err + } + actualFeePaid := tx.TotalInputSatoshis() - tx.TotalOutputSatoshis() + return actualFeePaid >= expFeesPaid.TotalFeePaid, nil } // EstimateFeesPaid will estimate how big the tx will be when finalised @@ -436,8 +440,7 @@ func (tx *Tx) feesPaid(size *TxSize, fees *FeeQuote) (*TxFees, error) { } resp := &TxFees{ - StdFeePaid: size.TotalStdBytes * - uint64(stdFee.MiningFee.Satoshis) / uint64(stdFee.MiningFee.Bytes), + StdFeePaid: size.TotalStdBytes * uint64(stdFee.MiningFee.Satoshis) / uint64(stdFee.MiningFee.Bytes), DataFeePaid: size.TotalDataBytes * uint64(dataFee.MiningFee.Satoshis) / uint64(dataFee.MiningFee.Bytes), } resp.TotalFeePaid = resp.StdFeePaid + resp.DataFeePaid diff --git a/tx_test.go b/tx_test.go index aa1a774a..91b7e0b7 100644 --- a/tx_test.go +++ b/tx_test.go @@ -902,51 +902,75 @@ func TestTx_Clone(t *testing.T) { }) } -func Test_FeesPaid(t *testing.T) { +func Test_IsFeePaidEnough(t *testing.T) { tests := map[string]struct { tx *bt.Tx dataLength uint64 - expFees *bt.TxFees expSize *bt.TxSize + isEnough bool }{ - "226B transaction (1 input 1 P2PKHOutput + no change) no data should return 113 sats fee": { + "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.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 959)) return tx }(), - expFees: &bt.TxFees{ - TotalFeePaid: 42, - StdFeePaid: 42, - DataFeePaid: 0, - }, expSize: &bt.TxSize{ TotalBytes: 85, TotalStdBytes: 85, }, - }, "226B transaction (1 input 1 P2PKHOutput + change) no data should return 113 sats fee": { + isEnough: false, + }, "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", 1000)) + 0, "76a91455b61be43392125d127f1780fb038437cd67ef9c88ac", 834709)) - assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) return tx }(), - expFees: &bt.TxFees{ - TotalFeePaid: 59, - StdFeePaid: 59, - DataFeePaid: 0, + expSize: &bt.TxSize{ + TotalBytes: 119, + TotalStdBytes: 119, + TotalDataBytes: 0, }, + isEnough: true, + }, "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.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) + return tx + }(), expSize: &bt.TxSize{ TotalBytes: 119, TotalStdBytes: 119, TotalDataBytes: 0, }, + isEnough: true, + }, "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.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256560)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 578091)) + return tx + }(), + expSize: &bt.TxSize{ + TotalBytes: 119, + TotalStdBytes: 119, + TotalDataBytes: 0, + }, + isEnough: false, }, "226B signed transaction (1 input 1 P2PKHOutput + change) no data should return 113 sats fee": { tx: func() *bt.Tx { tx := bt.NewTx() @@ -955,23 +979,19 @@ func Test_FeesPaid(t *testing.T) { log.Fatal(err) } assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", - 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) + 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709)) - assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559)) assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) return tx }(), - expFees: &bt.TxFees{ - TotalFeePaid: 113, - StdFeePaid: 113, - DataFeePaid: 0, - }, expSize: &bt.TxSize{ TotalBytes: 226, TotalStdBytes: 226, }, - }, "226B signed transaction (1 input 1 P2PKHOutput + no change) no data should return 113 sats fee": { + isEnough: true, + }, "192B signed transaction (1 input 1 P2PKHOutput + no change) should pay exact amount": { tx: func() *bt.Tx { tx := bt.NewTx() w, err := wif.DecodeWIF("cRhdUmZx4MbsjxVxGH4bM4geNLzQEPxspnhGtDCvMmfCLcED8Q6G") @@ -981,20 +1001,16 @@ func Test_FeesPaid(t *testing.T) { assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11", 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) - assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 100)) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 904)) tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) return tx }(), - expFees: &bt.TxFees{ - TotalFeePaid: 96, - StdFeePaid: 96, - DataFeePaid: 0, - }, expSize: &bt.TxSize{ TotalBytes: 192, TotalStdBytes: 192, }, - }, "214B signed transaction (1 input, 1 change output, 1 opreturn) 10 byte of data should return 100 sats fee 6 data fee": { + isEnough: true, + }, "214B signed transaction (1 input, 1 change output, 1 opreturn) should pay exact amount": { tx: func() *bt.Tx { w, err := wif.DecodeWIF("cRhdUmZx4MbsjxVxGH4bM4geNLzQEPxspnhGtDCvMmfCLcED8Q6G") if err != nil { @@ -1004,30 +1020,52 @@ func Test_FeesPaid(t *testing.T) { assert.NoError(t, tx.From("160f06232540dcb0e9b6db9b36a27f01da1e7e473989df67859742cf098d498f", 0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 1000)) assert.NoError(t, tx.AddOpReturnOutput([]byte("hellohello"))) - assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote())) + assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 894)) is, err := tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) assert.Nil(t, err) assert.Equal(t, 1, len(is)) return tx }(), - expFees: &bt.TxFees{ - TotalFeePaid: 106, - StdFeePaid: 100, - DataFeePaid: 6, - }, expSize: &bt.TxSize{ TotalBytes: 214, TotalStdBytes: 201, TotalDataBytes: 13, }, + isEnough: true, + }, "214B signed transaction (1 input, 1 change output, 1 opreturn) should fail paying less by 1 sat": { + tx: func() *bt.Tx { + w, err := wif.DecodeWIF("cRhdUmZx4MbsjxVxGH4bM4geNLzQEPxspnhGtDCvMmfCLcED8Q6G") + if err != nil { + log.Fatal(err) + } + tx := bt.NewTx() + 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}) + assert.Nil(t, err) + assert.Equal(t, 1, len(is)) + return tx + }(), + expSize: &bt.TxSize{ + TotalBytes: 213, + TotalStdBytes: 200, + TotalDataBytes: 13, + }, + isEnough: false, }, + // TODO: add tests for different fee type values } for name, test := range tests { t.Run(name, func(t *testing.T) { fee := bt.NewFeeQuote() - resp, err := test.tx.FeesPaid(fee) + isEnough, err := test.tx.IsFeePaidEnough(fee) assert.NoError(t, err) - assert.Equal(t, test.expFees, resp) + assert.Equal(t, test.isEnough, isEnough) + + swt := test.tx.SizeWithTypes() + assert.Equal(t, test.expSize, swt) }) } }