New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
client: Add external api request to get fee rate in case of fallback #1654
Conversation
85ab4b8
to
4e24290
Compare
client/asset/dcr/config.go
Outdated
// AllowExternalAPIRequest is a flag that allows the client to make requests | ||
// to external apis. | ||
AllowExternalAPIRequest bool `ini:"allowexternalapirequest"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Config
items are set by the user via a corresponding ConfigOpt
in the WalletDefinition.ConfigOpts
. I agree that this is a wallet-level configuration option, and this is the right place for this field, but setting the option should not involve core, asset, or db in any way. We already have the system in place to populate Config
.
{
Key: "allowexternalapirequest",
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 wallet th" +
"at have just recently been started.",
IsBoolean: true,
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed changes on core, asset and db and left them only on as wallet option. Much simpler indeed.
e979020
to
55649a3
Compare
Thanks for the review chapp. Adressed your comments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for those last changes. Will test it out shortly. Just two more tweaks please.
681f83b
to
5565cf1
Compare
Still WIP? You can toggle the status to WIP and back to open somewhere now. You weren't able to before so it's new. |
c6a45b1
to
c53296a
Compare
client/asset/dcr/config.go
Outdated
FallbackFeeRate float64 `ini:"fallbackfee"` | ||
FeeRateLimit float64 `ini:"feeratelimit"` | ||
RedeemConfTarget uint64 `ini:"redeemconftarget"` | ||
AllowExternalAPIRequest bool `ini:"allowexternalapirequest"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About half of this diff is just because you picked a long name here and in ExchangeWallet
. Can we not find a shorter way to say this? How about ApiFeeFallback
?
AllowExternalAPIRequest
doesn't mention fees anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me, but does it close #1618 ? I'm assuming this issue is for all assets.
// externalApiUrl is the URL of the external API in case of fallback. | ||
externalApiUrl = "https://explorer.dcrdata.org/insight/api" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testnet also has one. https://testnet.dcrdata.org/insight/api
would help with testing.
It does not. It's of particular importance for BTC SPV and even DOGE and ZEC full node that can't figure out how to get fee rates. |
client/asset/dcr/dcr.go
Outdated
if dex.Testnet == 1 { | ||
url = testnetExternalApiUrl | ||
} else { | ||
url = externalApiUrl | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if dex.Testnet == 1
is always true
. You'll need to work with the dex.Network
passed to NewWallet
somehow. You could inspect the *chaincfg.Params
to figure out what network you're on, but using the dex.Network
would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my bad. Now I am adding a new global var isTestnet
, which is stored on NewWallet
and check when calling the externalFeeEstimator
07b9bad
to
cb6c981
Compare
client/asset/dcr/dcr.go
Outdated
// store the network because we might need it for external api calls. | ||
if network == dex.Testnet { | ||
isTestnet = true | ||
} else { | ||
isTestnet = false | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No globals here, please. Let's pass it in to unconnectedWallet
and save it as a field on the ExchangeWallet
.
42fd304
to
c01878a
Compare
c01878a
to
b396250
Compare
client/core/core.go
Outdated
@@ -1914,6 +1914,7 @@ func (c *Core) loadWallet(dbWallet *db.Wallet) (*xcWallet, error) { | |||
} | |||
}, | |||
DataDir: c.assetDataDirectory(assetID), | |||
Network: c.Network(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the call to asset.OpenWallet
about 6 lines down, providing c.net
as an input arg. Then see:
func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (*ExchangeWallet, error)
There is no need to add Network
to asset.WalletConfig
because it is already provided directly to NewWallet
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should I just pass network
directly to unconnectedWallet()
instead?
like:
dcr, err := unconnectedWallet(cfg, walletCfg, chainParams, logger, network)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, or set dcr.network = network
in NewWallet
. I honestly don't care which, but unconnectedWallet
is probably right.
Also, I don't think the ExchangeWallet
fields should be exported.
} else { | ||
url = externalApiUrl |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that for simnet it's gonna use mainnet's fee estimates. This isn't bad TBH as it allows some more fee rate variability when testing (maybe not with DCR, but in general it would).
65f1033
to
0429c27
Compare
client/asset/dcr/dcr.go
Outdated
func (dcr *ExchangeWallet) FeeRate() uint64 { | ||
confTarget := uint64(2) | ||
if dcr.wallet.SpvMode() { | ||
return 0 // EstimateSmartFeeRate needs dcrd passthrough | ||
if !dcr.apiFeeFallback { | ||
return 0 // EstimateSmartFeeRate needs dcrd passthrough | ||
} | ||
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 | ||
} | ||
uintFee, err := convertFeeToUint(estimatedFeeRate) | ||
if err != nil { | ||
dcr.log.Errorf("Failed to convert fee rate: %v", err) | ||
return 0 | ||
} | ||
return uintFee |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of this refactor to reduce duplication?
// FeeRate satisfies asset.FeeRater.
func (dcr *ExchangeWallet) FeeRate() uint64 {
const confTarget = 2
rate, err := dcr.feeRate(confTarget)
if err != nil {
dcr.log.Errorf("feeRate error: %v", err)
}
return rate
}
// FeeRate returns the current optimal fee rate in atoms / byte.
func (dcr *ExchangeWallet) feeRate(confTarget uint64) (uint64, error) {
// estimatesmartfee 1 returns extremely high rates on DCR.
if confTarget < 2 {
confTarget = 2
}
if feeEstimator, is := dcr.wallet.(FeeRateEstimator); is {
ratePerKB, err := feeEstimator.EstimateSmartFeeRate(dcr.ctx, int64(confTarget), chainjson.EstimateSmartFeeConservative)
if err == nil {
return convertFeeToUint(ratePerKB)
} else {
dcr.log.Errorf("Failed to get fee rate with estimate smart fee rate: %v", err)
}
}
// Either SPV wallet or EstimateSmartFeeRate failed.
if !dcr.apiFeeFallback {
return 0, fmt.Errorf("fee rate estimation unavailable")
}
dcr.log.Debug("Retrieving fee rate from external API")
ratePerKB, 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
}
return convertFeeToUint(ratePerKB)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored
0429c27
to
b9ba779
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took a closer look and I think we've neglected two things.
return 0, fmt.Errorf("fee rate estimation unavailable") | ||
} | ||
dcr.log.Debug("Retrieving fee rate from external API") | ||
ratePerKB, err := externalFeeEstimator(dcr.ctx, dcr.network, confTarget) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm just realizing that we're not doing any sanity checking or thresholding on the result from the external source. They could be malicious or maybe they changed the API from DCR/kB -> atoms/kB (or whatever) without warning.
The external source might also say 0.
I think after calling externalFeeEstimator
we need to both catch zero and filter the result of convertFeeToUint
through feeRateWithFallback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you are right, it was returning 1, which would not go into feeRateWithFallback
. Returning 0 now if it is 0.
I am not checking for a upper limit though, should I?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really think we should apply our upper limit to any rates we get from an external source.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds reasonable. Checking if it is greater than dcr.feeRateLimit
now
} | ||
return estimatedFeeRate, nil | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few lines down there's still a if dcr.wallet.SpvMode() {
that skips calling feeRate
where the external API would be called. Thus we're still falling back to the wallet's hard coded fee rate without asking the external source in a lot of cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol I completely missed it. Removed it now, thanks for catching.
3e15346
to
49a3a78
Compare
Partially addresses #1618
I am adding a new wallet config field
external fee rate estimate
, which is defaulted to false right now.And estimating the fee with
externalFeeEstimator
for spv and rpc wallets.For rpc wallet it is only checked in case of dcrd failure.