Skip to content

Commit

Permalink
added func for estimating if fees are paid. also added tests for this…
Browse files Browse the repository at this point in the history
…, and a case in isfeespaid with 0 inputs
  • Loading branch information
Tíghearnán Carroll committed Sep 15, 2021
1 parent 7a05422 commit e60a76f
Show file tree
Hide file tree
Showing 2 changed files with 217 additions and 0 deletions.
24 changes: 24 additions & 0 deletions tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,30 @@ func (tx *Tx) IsFeePaidEnough(fees *FeeQuote) (bool, error) {
return actualFeePaid >= expFeesPaid.TotalFeePaid, nil
}

// EstimateIsFeePaidEnough will calculate the fees that this transaction is paying
// including the individual fee types (std/data/etc.), and will add 107 bytes to the unlocking
// script of any unsigned inputs (only P2PKH for now) found to give a final size
// estimate of the tx size for fee calculation.
func (tx *Tx) EstimateIsFeePaidEnough(fees *FeeQuote) (bool, error) {
tempTx, err := tx.estimatedFinalTx()
if err != nil {
return false, err
}
expFeesPaid, err := tempTx.feesPaid(tempTx.SizeWithTypes(), fees)
if err != nil {
return false, err
}
totalInputSatoshis := tempTx.TotalInputSatoshis()
totalOutputSatoshis := tempTx.TotalOutputSatoshis()

if totalInputSatoshis < totalOutputSatoshis {
return false, nil
}

actualFeePaid := totalInputSatoshis - totalOutputSatoshis
return actualFeePaid >= expFeesPaid.TotalFeePaid, nil
}

// EstimateFeesPaid will estimate how big the tx will be when finalised
// by estimating input unlocking scripts that have not yet been filled
// including the individual fee types (std/data/etc.).
Expand Down
193 changes: 193 additions & 0 deletions tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,186 @@ func TestTx_Clone(t *testing.T) {
}
})
}
func Test_EstimateIsFeePaidEnough(t *testing.T) {
tests := map[string]struct {
tx *bt.Tx
dataLength uint64
expSize *bt.TxSize
isEnough bool
}{
"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", 905))
return tx
}(),
expSize: &bt.TxSize{
TotalBytes: 85,
TotalStdBytes: 85,
},
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", 834709))

assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559))
assert.NoError(t, tx.ChangeToAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", bt.NewFeeQuote()))
return tx
}(),
expSize: &bt.TxSize{
TotalBytes: 119,
TotalStdBytes: 119,
TotalDataBytes: 0,
},
isEnough: true,
}, "unsigned transaction (0 input 1 P2PKHOutput) should not pay": {
tx: func() *bt.Tx {
tx := bt.NewTx()

assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559))
return tx
}(),
expSize: &bt.TxSize{
TotalBytes: 44,
TotalStdBytes: 44,
TotalDataBytes: 0,
},
isEnough: false,
}, "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.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", 834763))

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()
w, err := wif.DecodeWIF("cRhdUmZx4MbsjxVxGH4bM4geNLzQEPxspnhGtDCvMmfCLcED8Q6G")
if err != nil {
log.Fatal(err)
}
assert.NoError(t, tx.From("a4c76f8a7c05a91dcf5699b95b54e856298e50c1ceca9a8a5569c8532c500c11",
0, "76a914ff8c9344d4e76c0580420142f697e5fc2ce5c98e88ac", 834709))

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
}(),
expSize: &bt.TxSize{
TotalBytes: 226,
TotalStdBytes: 226,
},
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")
if err != nil {
log.Fatal(err)
}
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})
return tx
}(),
expSize: &bt.TxSize{
TotalBytes: 192,
TotalStdBytes: 192,
},
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 {
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", 894))
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: 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()
isEnough, err := test.tx.EstimateIsFeePaidEnough(fee)
assert.NoError(t, err)
assert.Equal(t, test.isEnough, isEnough)

swt := test.tx.SizeWithTypes()
assert.Equal(t, test.expSize, swt)
})
}
}

func Test_IsFeePaidEnough(t *testing.T) {
tests := map[string]struct {
Expand Down Expand Up @@ -939,6 +1119,19 @@ func Test_IsFeePaidEnough(t *testing.T) {
TotalDataBytes: 0,
},
isEnough: true,
}, "unsigned transaction (0 input 1 P2PKHOutput) should not pay": {
tx: func() *bt.Tx {
tx := bt.NewTx()

assert.NoError(t, tx.AddP2PKHOutputFromAddress("mtestD3vRB7AoYWK2n6kLdZmAMLbLhDsLr", 256559))
return tx
}(),
expSize: &bt.TxSize{
TotalBytes: 44,
TotalStdBytes: 44,
TotalDataBytes: 0,
},
isEnough: false,
}, "unsigned transaction (1 input 2 P2PKHOutputs) should pay exact amount": {
tx: func() *bt.Tx {
tx := bt.NewTx()
Expand Down

0 comments on commit e60a76f

Please sign in to comment.