Skip to content
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

Merged
merged 2 commits into from Jul 18, 2022

Conversation

vctt94
Copy link
Member

@vctt94 vctt94 commented Jun 7, 2022

Partially addresses #1618

I am adding a new wallet config field external fee rate estimate, which is defaulted to false right now.

image

image

And estimating the fee with externalFeeEstimator for spv and rpc wallets.

For rpc wallet it is only checked in case of dcrd failure.

@chappjc chappjc added this to the 0.5 milestone Jun 9, 2022
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
@vctt94 vctt94 force-pushed the add-external-api-fallback branch from 85ab4b8 to 4e24290 Compare June 17, 2022 17:08
Comment on lines 48 to 50
// AllowExternalAPIRequest is a flag that allows the client to make requests
// to external apis.
AllowExternalAPIRequest bool `ini:"allowexternalapirequest"`
Copy link
Member

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,
},

Copy link
Member Author

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.

@vctt94 vctt94 force-pushed the add-external-api-fallback branch 2 times, most recently from e979020 to 55649a3 Compare June 20, 2022 20:36
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
@vctt94
Copy link
Member Author

vctt94 commented Jun 21, 2022

Thanks for the review chapp. Adressed your comments.

Copy link
Member

@chappjc chappjc left a 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.

client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
client/asset/dcr/dcr.go Outdated Show resolved Hide resolved
@vctt94 vctt94 force-pushed the add-external-api-fallback branch from 681f83b to 5565cf1 Compare June 21, 2022 14:04
@JoeGruffins
Copy link
Member

JoeGruffins commented Jun 22, 2022

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.

@vctt94 vctt94 changed the title client/wip: Add external api request to get fee rate in case of fallback client: Add external api request to get fee rate in case of fallback Jun 22, 2022
@vctt94 vctt94 force-pushed the add-external-api-fallback branch 2 times, most recently from c6a45b1 to c53296a Compare June 22, 2022 22:10
FallbackFeeRate float64 `ini:"fallbackfee"`
FeeRateLimit float64 `ini:"feeratelimit"`
RedeemConfTarget uint64 `ini:"redeemconftarget"`
AllowExternalAPIRequest bool `ini:"allowexternalapirequest"`
Copy link
Member

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.

Copy link
Member

@JoeGruffins JoeGruffins left a 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.

Comment on lines +79 to +82
// externalApiUrl is the URL of the external API in case of fallback.
externalApiUrl = "https://explorer.dcrdata.org/insight/api"
Copy link
Member

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.

@chappjc
Copy link
Member

chappjc commented Jun 24, 2022

Looks good to me, but does it close #1618 ? I'm assuming this issue is for all assets.

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.

Comment on lines 794 to 928
if dex.Testnet == 1 {
url = testnetExternalApiUrl
} else {
url = externalApiUrl
}
Copy link
Member

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.

Copy link
Member Author

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

@vctt94 vctt94 force-pushed the add-external-api-fallback branch 2 times, most recently from 07b9bad to cb6c981 Compare June 28, 2022 14:44
Comment on lines 571 to 576
// store the network because we might need it for external api calls.
if network == dex.Testnet {
isTestnet = true
} else {
isTestnet = false
}
Copy link
Member

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.

@vctt94 vctt94 force-pushed the add-external-api-fallback branch 2 times, most recently from 42fd304 to c01878a Compare July 4, 2022 19:46
@vctt94 vctt94 force-pushed the add-external-api-fallback branch from c01878a to b396250 Compare July 6, 2022 21:44
@@ -1914,6 +1914,7 @@ func (c *Core) loadWallet(dbWallet *db.Wallet) (*xcWallet, error) {
}
},
DataDir: c.assetDataDirectory(assetID),
Network: c.Network(),
Copy link
Member

@chappjc chappjc Jul 12, 2022

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.

Copy link
Member Author

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)

Copy link
Member

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.

Comment on lines +947 to +927
} else {
url = externalApiUrl
Copy link
Member

@chappjc chappjc Jul 12, 2022

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).

@vctt94 vctt94 force-pushed the add-external-api-fallback branch 2 times, most recently from 65f1033 to 0429c27 Compare July 15, 2022 15:27
Comment on lines 875 to 891
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
Copy link
Member

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)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactored

@vctt94 vctt94 force-pushed the add-external-api-fallback branch from 0429c27 to b9ba779 Compare July 15, 2022 18:35
Copy link
Member

@chappjc chappjc left a 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)
Copy link
Member

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

Copy link
Member Author

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?

Copy link
Member

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.

Copy link
Member Author

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
}

Copy link
Member

@chappjc chappjc Jul 15, 2022

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.

Copy link
Member Author

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.

@vctt94 vctt94 force-pushed the add-external-api-fallback branch from 3e15346 to 49a3a78 Compare July 18, 2022 16:31
@chappjc chappjc merged commit 78dc765 into decred:master Jul 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants