@@ -18,6 +18,7 @@ package btc
18
18
import (
19
19
"encoding/base64"
20
20
"fmt"
21
+ "net/http"
21
22
"os"
22
23
"path"
23
24
"sort"
@@ -36,6 +37,7 @@ import (
36
37
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/coin"
37
38
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/ltc"
38
39
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/signing"
40
+ "github.com/BitBoxSwiss/bitbox-wallet-app/backend/util"
39
41
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
40
42
"github.com/BitBoxSwiss/bitbox-wallet-app/util/locker"
41
43
"github.com/BitBoxSwiss/bitbox-wallet-app/util/observable"
@@ -56,6 +58,10 @@ const (
56
58
// maxGapLimit limits the maximum gap limit that can be used. It is an arbitrary number with the
57
59
// goal that the scanning will stop in a reasonable amount of time.
58
60
maxGapLimit = 2000
61
+
62
+ // mempoolSpaceMirror is Shift server that mirrors "https://mempool.space/api/v1/fees/recommended"
63
+ // rest call.
64
+ mempoolSpaceMirror = "https://fees1.shiftcrypto.io"
59
65
)
60
66
61
67
type subaccount struct {
@@ -101,8 +107,6 @@ type Account struct {
101
107
activeTxProposal * maketx.TxProposal
102
108
activeTxProposalLock locker.Locker
103
109
104
- feeTargets []* FeeTarget
105
- feeTargetsLock locker.Locker
106
110
// Access this only via getMinRelayFeeRate(). sat/kB.
107
111
minRelayFeeRate * btcutil.Amount
108
112
minRelayFeeRateLock locker.Locker
@@ -116,6 +120,8 @@ type Account struct {
116
120
closed bool
117
121
118
122
log * logrus.Entry
123
+
124
+ httpClient * http.Client
119
125
}
120
126
121
127
// NewAccount creates a new account.
@@ -126,6 +132,7 @@ func NewAccount(
126
132
coin * Coin ,
127
133
forceGapLimits * types.GapLimits ,
128
134
log * logrus.Entry ,
135
+ httpClient * http.Client ,
129
136
) * Account {
130
137
log = log .WithField ("group" , "btc" ).
131
138
WithFields (logrus.Fields {"coin" : coin .String (), "code" : config .Config .Code , "name" : config .Config .Name })
@@ -137,14 +144,8 @@ func NewAccount(
137
144
dbSubfolder : "" , // set in Initialize()
138
145
forceGapLimits : forceGapLimits ,
139
146
140
- // feeTargets must be sorted by ascending priority.
141
- feeTargets : []* FeeTarget {
142
- {blocks : 24 , code : accounts .FeeTargetCodeEconomy },
143
- {blocks : 12 , code : accounts .FeeTargetCodeLow },
144
- {blocks : 6 , code : accounts .FeeTargetCodeNormal },
145
- {blocks : 2 , code : accounts .FeeTargetCodeHigh },
146
- },
147
- log : log ,
147
+ log : log ,
148
+ httpClient : httpClient ,
148
149
}
149
150
return account
150
151
}
@@ -430,8 +431,6 @@ func (account *Account) onNewHeader(header *electrumTypes.Header) {
430
431
return
431
432
}
432
433
account .log .WithField ("block-height" , header .Height ).Debug ("Received new header" )
433
- // Fee estimates change with each block.
434
- account .updateFeeTargets ()
435
434
}
436
435
437
436
// FatalError returns true if the account had a fatal error.
@@ -476,26 +475,68 @@ func (account *Account) Notifier() accounts.Notifier {
476
475
return account .notifier
477
476
}
478
477
479
- func (account * Account ) updateFeeTargets () {
480
- defer account .feeTargetsLock .Lock ()()
478
+ // feeTargets fetches the available fees. For mainnet BTC it uses mempool.space estimation.
479
+ //
480
+ // For the other coins or in case mempool.space is not available it fallbacks on Bitcoin Core.
481
+ // The minimum relay fee is used as a last resource fallback in case also Bitcoin Core is
482
+ // unavailable.
483
+ func (account * Account ) feeTargets () []* FeeTarget {
484
+ // for mainnet BTC we fetch mempool.space fees, as they should be more reliable.
485
+ var mempoolFees * accounts.MempoolSpaceFees
486
+ if account .coin .Code () == coin .CodeBTC {
487
+ mempoolFees = & accounts.MempoolSpaceFees {}
488
+ _ , err := util .APIGet (account .httpClient , mempoolSpaceMirror , "" , 1000 , mempoolFees )
489
+ if err != nil {
490
+ mempoolFees = nil
491
+ account .log .WithError (err ).Errorf ("Fetching fees from %s failed" , mempoolSpaceMirror )
492
+ }
493
+ }
494
+
495
+ // feeTargets must be sorted by ascending priority.
496
+ var feeTargets []* FeeTarget
497
+ if mempoolFees != nil {
498
+ feeTargets = []* FeeTarget {
499
+ {blocks : 12 , code : accounts .FeeTargetCodeMempoolEconomy },
500
+ {blocks : 3 , code : accounts .FeeTargetCodeMempoolHour },
501
+ {blocks : 2 , code : accounts .FeeTargetCodeMempoolHalfHour },
502
+ {blocks : 1 , code : accounts .FeeTargetCodeMempoolFastest },
503
+ }
504
+ } else {
505
+ feeTargets = []* FeeTarget {
506
+ {blocks : 24 , code : accounts .FeeTargetCodeEconomy },
507
+ {blocks : 12 , code : accounts .FeeTargetCodeLow },
508
+ {blocks : 6 , code : accounts .FeeTargetCodeNormal },
509
+ {blocks : 2 , code : accounts .FeeTargetCodeHigh },
510
+ }
511
+ }
512
+
481
513
var minRelayFeeRate * btcutil.Amount
482
514
minRelayFeeRateVal , err := account .getMinRelayFeeRate ()
483
515
if err == nil {
484
516
minRelayFeeRate = & minRelayFeeRateVal
485
517
}
486
- for _ , feeTarget := range account .feeTargets {
487
- feeRatePerKb , err := account .coin .Blockchain ().EstimateFee (feeTarget .blocks )
488
- if err != nil {
489
- if account .coin .Code () != coin .CodeTLTC {
490
- account .log .WithField ("fee-target" , feeTarget .blocks ).
491
- Warning ("Fee could not be estimated. Taking the minimum relay fee instead" )
492
- }
493
- if minRelayFeeRate == nil {
494
- account .log .WithField ("fee-target" , feeTarget .blocks ).
495
- Warning ("Minimum relay fee could not be determined" )
496
- continue
518
+
519
+ for _ , feeTarget := range feeTargets {
520
+ var feeRatePerKb btcutil.Amount
521
+
522
+ if mempoolFees != nil {
523
+ feeRatePerKb = mempoolFees .GetFeeRate (feeTarget .code )
524
+ } else {
525
+ // If mempool.space fees are not available, we fallback on Bitcoin Core estimation.
526
+ // If even that one is not available, we just offer the min relay fee.
527
+ feeRatePerKb , err = account .coin .Blockchain ().EstimateFee (feeTarget .blocks )
528
+ if err != nil {
529
+ if account .coin .Code () != coin .CodeTLTC {
530
+ account .log .WithField ("fee-target" , feeTarget .blocks ).
531
+ Warning ("Fee could not be estimated. Taking the minimum relay fee instead" )
532
+ }
533
+ if minRelayFeeRate == nil {
534
+ account .log .WithField ("fee-target" , feeTarget .blocks ).
535
+ Warning ("Minimum relay fee could not be determined" )
536
+ continue
537
+ }
538
+ feeRatePerKb = * minRelayFeeRate
497
539
}
498
- feeRatePerKb = * minRelayFeeRate
499
540
}
500
541
// If the minrelayfee is available the estimated fee rate is smaller than the minrelayfee,
501
542
// we use the minrelayfee instead. If the minrelayfee is unknown, we leave the fee
@@ -506,42 +547,36 @@ func (account *Account) updateFeeTargets() {
506
547
feeTarget .feeRatePerKb = & feeRatePerKb
507
548
account .log .WithFields (logrus.Fields {"blocks" : feeTarget .blocks ,
508
549
"fee-rate-per-kb" : feeRatePerKb }).Debug ("Fee estimate per kb" )
509
- account .Config ().OnEvent (accountsTypes .EventFeeTargetsChanged )
510
550
}
551
+
552
+ return feeTargets
511
553
}
512
554
513
555
// FeeTargets returns the fee targets and the default fee target.
514
556
func (account * Account ) FeeTargets () ([]accounts.FeeTarget , accounts.FeeTargetCode ) {
515
- defer account .feeTargetsLock .RLock ()()
516
- // Return only fee targets with a valid fee rate (drop if fee could not be estimated). Also
517
- // remove all duplicate fee rates.
557
+ // Return only fee targets with a valid fee rate (drop if fee could not be estimated).
558
+ fetchedFeeTargets := account .feeTargets ()
518
559
feeTargets := []accounts.FeeTarget {}
519
- defaultAvailable := false
520
- outer:
521
- for i := len (account .feeTargets ) - 1 ; i >= 0 ; i -- {
522
- feeTarget := account .feeTargets [i ]
560
+ defaultFee := accounts .FeeTargetCodeCustom
561
+
562
+ for _ , feeTarget := range fetchedFeeTargets {
523
563
if feeTarget .feeRatePerKb == nil {
524
564
continue
525
565
}
526
- for j := i - 1 ; j >= 0 ; j -- {
527
- checkFeeTarget := account .feeTargets [j ]
528
- if checkFeeTarget .feeRatePerKb != nil && * checkFeeTarget .feeRatePerKb == * feeTarget .feeRatePerKb {
529
- continue outer
530
- }
531
- }
532
- if feeTarget .code == accounts .DefaultFeeTarget {
533
- defaultAvailable = true
566
+
567
+ switch feeTarget .code {
568
+ case accounts .DefaultFeeTarget :
569
+ fallthrough
570
+ case accounts .DefaultMempoolFeeTarget :
571
+ defaultFee = feeTarget .code
534
572
}
535
573
feeTargets = append (feeTargets , feeTarget )
536
574
}
537
575
// If the default fee level was dropped, use the cheapest.
538
576
// If no fee targets are available, use custom (the user can manually enter a fee rate).
539
- defaultFee := accounts .DefaultFeeTarget
540
- if ! defaultAvailable {
577
+ if defaultFee == accounts .FeeTargetCodeCustom {
541
578
if len (feeTargets ) != 0 {
542
579
defaultFee = feeTargets [0 ].Code ()
543
- } else {
544
- defaultFee = accounts .FeeTargetCodeCustom
545
580
}
546
581
}
547
582
return feeTargets , defaultFee
0 commit comments