Skip to content

Commit

Permalink
client: add external fee estimator as fallback, when feeEstimator fai…
Browse files Browse the repository at this point in the history
…ls on decred rpcwallets.
  • Loading branch information
vctt94 committed Jul 4, 2022
1 parent c68b82f commit c01878a
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 1 deletion.
1 change: 1 addition & 0 deletions client/asset/dcr/config.go
Expand Up @@ -39,6 +39,7 @@ type walletConfig struct {
FeeRateLimit float64 `ini:"feeratelimit"`
RedeemConfTarget uint64 `ini:"redeemconftarget"`
ActivelyUsed bool `ini:"special:activelyUsed"` //injected by core
ApiFeeFallback bool `ini:"apifeefallback"`
}

type rpcConfig struct {
Expand Down
66 changes: 65 additions & 1 deletion client/asset/dcr/dcr.go
Expand Up @@ -9,9 +9,12 @@ import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"math"
"net/http"
"path/filepath"
"sort"
"strconv"
Expand Down Expand Up @@ -74,6 +77,12 @@ const (
// hierarchical deterministic key derivation for the internal branch of an
// account.
acctInternalBranch uint32 = 1

// externalApiUrl is the URL of the external API in case of fallback.
externalApiUrl = "https://explorer.dcrdata.org/insight/api"
// testnetExternalApiUrl is the URL of the testnet external API in case of
// fallback.
testnetExternalApiUrl = "https://testnet.dcrdata.org/insight/api"
)

var (
Expand Down Expand Up @@ -172,6 +181,14 @@ var (
Description: "Path to the dcrwallet TLS certificate file",
DefaultValue: defaultRPCCert,
},
{
Key: "apifeefallback",
DisplayName: "External fee rate estimates",
Description: "Allow fee rate estimation from a block explorer API. " +
"This is useful as a fallback for SPV wallets and RPC wallets " +
"that have recently been started.",
IsBoolean: true,
},
}

spvOpts = []*asset.ConfigOption{{
Expand Down Expand Up @@ -215,6 +232,7 @@ var (
swapFeeBumpKey = "swapfeebump"
splitKey = "swapsplit"
redeemFeeBumpFee = "redeemfeebump"
client http.Client
)

// outPoint is the hash and output index of a transaction output.
Expand Down Expand Up @@ -499,6 +517,8 @@ type ExchangeWallet struct {
feeRateLimit uint64
redeemConfTarget uint64
useSplitTx bool
ApiFeeFallback bool
Network dex.Network

tipMtx sync.RWMutex
currentTip *block
Expand Down Expand Up @@ -656,6 +676,8 @@ func unconnectedWallet(cfg *asset.WalletConfig, dcrCfg *walletConfig, chainParam
feeRateLimit: feesLimitPerByte,
redeemConfTarget: redeemConfTarget,
useSplitTx: dcrCfg.UseSplitTx,
ApiFeeFallback: dcrCfg.ApiFeeFallback,
Network: cfg.Network,
}, nil
}

Expand Down Expand Up @@ -874,7 +896,16 @@ func (dcr *ExchangeWallet) feeRate(confTarget uint64) (uint64, error) {
}
estimatedFeeRate, err := feeEstimator.EstimateSmartFeeRate(dcr.ctx, int64(confTarget), chainjson.EstimateSmartFeeConservative)
if err != nil {
return 0, err
dcr.log.Errorf("Failed to get fee rate with estimate smart fee rate: %v", err)
if !dcr.ApiFeeFallback {
return 0, err
}
dcr.log.Debug("Retrieving fee rate from external API")
estimatedFeeRate, err = externalFeeEstimator(dcr.ctx, dcr.Network, confTarget)
if err != nil {
dcr.log.Errorf("Failed to get fee rate from external API: %v", err)
return 0, err
}
}
atomsPerKB, err := dcrutil.NewAmount(estimatedFeeRate) // atomsPerKB is 0 when err != nil
if err != nil {
Expand All @@ -885,6 +916,39 @@ func (dcr *ExchangeWallet) feeRate(confTarget uint64) (uint64, error) {
return 1 + uint64(atomsPerKB)/1000, nil // dcrPerKB * 1e8 / 1e3
}

// externalFeeEstimator gets the fee rate from the external API
func externalFeeEstimator(ctx context.Context, net dex.Network, nb uint64) (float64, error) {
var url string
if net == dex.Testnet {
url = testnetExternalApiUrl
} else {
url = externalApiUrl
}
url = url + "/utils/estimatefee?nbBlocks=" + strconv.FormatUint(nb, 10)
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
defer cancel()
r, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return 0, err
}
httpResponse, err := client.Do(r)
if err != nil {
return 0, err
}
c := make(map[uint64]float64)
reader := io.LimitReader(httpResponse.Body, 1<<14)
err = json.NewDecoder(reader).Decode(&c)
httpResponse.Body.Close()
if err != nil {
return 0, err
}
estimatedFeeRate, ok := c[nb]
if !ok {
return 0, errors.New("no fee rate for requested number of blocks")
}
return estimatedFeeRate, nil
}

// targetFeeRateWithFallback attempts to get a fresh fee rate for the target
// number of confirmations, but falls back to the suggestion or fallbackFeeRate
// via feeRateWithFallback.
Expand Down
2 changes: 2 additions & 0 deletions client/asset/interface.go
Expand Up @@ -229,6 +229,8 @@ type WalletConfig struct {
// DataDir is a filesystem directory the the wallet may use for persistent
// storage.
DataDir string
// Network flags passed to the wallet representing which network to use.
Network dex.Network
}

// Wallet is a common interface to be implemented by cryptocurrency wallet
Expand Down
1 change: 1 addition & 0 deletions client/core/core.go
Expand Up @@ -1914,6 +1914,7 @@ func (c *Core) loadWallet(dbWallet *db.Wallet) (*xcWallet, error) {
}
},
DataDir: c.assetDataDirectory(assetID),
Network: c.Network(),
}

walletCfg.Settings[asset.SpecialSettingActivelyUsed] =
Expand Down

0 comments on commit c01878a

Please sign in to comment.