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

server/eth: Match fee rate = MaxFeeRate #1447

Merged
merged 6 commits into from
Feb 8, 2022

Conversation

martonp
Copy link
Contributor

@martonp martonp commented Jan 25, 2022

  • server/asset: Add Info and ValidateFeeRate functions
  • server/dex: Add SwapFeeRate function to FeeFetcher. This will return the max fee rate for assets that support dynamic transaction fees, but the regular tx fee otherwise
  • server/market: Use SwapFeeRate instead of the regular FeeRate
  • server/swap: Use ValidateFeeRate function instead of just comparing the fee rates. This is to allow ETH to check the prority fee in addition to the MaxFeeCap

Closes #1372.

@martonp martonp changed the title server/eth: Swap gas fees server/eth: Match fee rate = MaxFeeRate Jan 25, 2022
server/dex/feemgr.go Outdated Show resolved Hide resolved
server/asset/eth/eth.go Outdated Show resolved Hide resolved
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.

Working well.

server/dex/feemgr.go Outdated Show resolved Hide resolved
- server/asset: Add `SupportsDynamicTxFee` and `ValidateFeeRate`
  functions
- server/dex: Add `SwapFeeRate` function to `FeeFetcher`. This will
  return the max fee rate for assets that support dynamic transaction
  fees, but the regular tx fee otherwise
- server/market: Use `SwapFeeRate` instead of the regular `FeeRate`
- server/swap: Use `ValidateFeeRate` function instead of just comparing
  the fee rates. This is to allow eth to check the prority fee in
  addition to the `MaxFeeCap`.
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.

LGTM. Keeping open to see if @buck54321 is good with SupportsDynamicTxFee + ValidateFeeRate

Copy link
Member

@buck54321 buck54321 left a comment

Choose a reason for hiding this comment

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

Concept ACK. Untested and one suggestion, but feel free to merge when you're comfy, @chappjc.

return false
}

if sc.dynamicTx && sc.gasTipCap < dexeth.MinGasTipCap {
Copy link
Member

Choose a reason for hiding this comment

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

We should probably just enforce dynamicTx = true for all of our transactions.

Comment on lines 496 to 500
// SupportsDynamicTxFee returns true if the tx fee for this asset adjusts based
// on market conditions
func (*Backend) SupportsDynamicTxFee() bool {
return false
}
Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't need to implement this here and in dcr if you made SupportsDynamicTxFee part of an optional interface like we're doing with the client.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, I'm now thinking this might not work like I expected, since it's just a boolean return value. So we'd verify that the interface is a DynamicFeeSupporter or whatever, and then still check the boolean? What we really need is something akin to WalletInfo, but on the server side. I'm okay with this as is too

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, it works as is. @martonp can try some stuff out I guess, but I can see the optional method being SwapFeeRate, which would make feeFetcher unaware of the whole dynamic<->max_fee relationship. Maybe we like that dynamic/max concept front and center?

Specifically, changing

// SwapFeeRate returns the tx fee that needs to be used to initiate a swap.
// This fee will be the max fee rate if the asset supports dynamic tx fees,
// and otherwise it will be the current market fee rate.
func (f *feeFetcher) SwapFeeRate(ctx context.Context) uint64 {
	if f.Backend.SupportsDynamicTxFee() {
		return f.MaxFeeRate()
	}
	return f.FeeRate(ctx)
}

into

// SwapFeeRate returns the tx fee that needs to be used to initiate a swap.
// This will use the Backend's SwapFeeRate method if it exists, otherwise
// it will request the fee rate based on current network conditions.
func (f *feeFetcher) SwapFeeRate(ctx context.Context) uint64 {
	if fr, ok := f.Backend.(asset.SwapFeeRater); ok {
		return fr.SwapFeeRate()
	}
	return f.FeeRate(ctx)
}

The question is do we hide the whole dynamic-must-use-max concept in the Backend's methods or do we apply that notion at the higher levels like done currently in this PR?

Copy link
Contributor Author

@martonp martonp Feb 3, 2022

Choose a reason for hiding this comment

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

This wouldn't work because the backend doesn't have access to the MaxFeeRate. It's stored in the dex.Asset.

Copy link
Member

@chappjc chappjc Feb 3, 2022

Choose a reason for hiding this comment

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

Ahh, that's right. Max fee rate is a market/swapper concept, so must be done higher up. ✔️

@chappjc
Copy link
Member

chappjc commented Feb 2, 2022

Concept ACK. Untested and one suggestion, but feel free to merge when you're comfy, @chappjc.

I like both of your suggestions, enforce dynamic tx and consider an interface assertion for server's asset Backends

@martonp
Copy link
Contributor Author

martonp commented Feb 3, 2022

We don't really need to check for dynamic vs legacy, in the geth Transaction object, for a legacy transaction GasFeeCap and GasTipCap will both just return the gas price. GasPrice returns the fee cap in a dynamic transaction. Anyways, the code is more simple like this. I also added a test to make sure the coiner was populating the tip cap correctly.

@chappjc
Copy link
Member

chappjc commented Feb 3, 2022

@martonp I noticed that too:

https://github.com/ethereum/go-ethereum/blob/aaca58a7a1d9acbd24bbc74c49933efa2f1af183/core/types/legacy_tx.go#L99-L101

func (tx *LegacyTx) gasPrice() *big.Int     { return tx.GasPrice }
func (tx *LegacyTx) gasTipCap() *big.Int    { return tx.GasPrice }
func (tx *LegacyTx) gasFeeCap() *big.Int    { return tx.GasPrice }

vs.

func (tx *DynamicFeeTx) gasFeeCap() *big.Int    { return tx.GasFeeCap }
func (tx *DynamicFeeTx) gasTipCap() *big.Int    { return tx.GasTipCap }
func (tx *DynamicFeeTx) gasPrice() *big.Int     { return tx.GasFeeCap }

accessed via

// GasPrice returns the gas price of the transaction.
func (tx *Transaction) GasPrice() *big.Int { return new(big.Int).Set(tx.inner.gasPrice()) }

// GasTipCap returns the gasTipCap per gas of the transaction.
func (tx *Transaction) GasTipCap() *big.Int { return new(big.Int).Set(tx.inner.gasTipCap()) }

// GasFeeCap returns the fee cap per gas of the transaction.
func (tx *Transaction) GasFeeCap() *big.Int { return new(big.Int).Set(tx.inner.gasFeeCap()) }

But your point is that if it's a legacy txn with a gasPrice set to our (very high) gasFeeCap, so be it, even though that's incredibly wasteful of the user?

So you are proposing setting baseCoin.gasFeeCap to either gasPrice if it is a legacy txn or actually gasFeeCap for dynamic. Seems reasonable, and if we do decide to block legacy txns, we can always go back to (*Backend).baseCoin to block it.

Finally, given the above code snippets, was our way of detecting legacy/dynamic tx type incorrect before? Should we have been using (tx *Transaction) Type() uint8?

@martonp
Copy link
Contributor Author

martonp commented Feb 3, 2022

But your point is that if it's a legacy txn with a gasPrice set to our (very high) gasFeeCap, so be it, even though that's incredibly wasteful of the user?

Yes, I think the client should definitely use a dynamic transaction, but I don't see a reason to block legacy ones.

Finally, given the above code snippets, was our way of detecting legacy/dynamic tx type incorrect before? Should we have been using (tx *Transaction) Type() uint8?

Yeah, what we had was pointless lol.

Comment on lines +159 to +160
gasTipCap := tx.GasTipCap()
if gasTipCap == nil || gasTipCap.Cmp(zero) <= 0 {
Copy link
Member

Choose a reason for hiding this comment

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

With a legacy transaction, which returns the same gas price for both GasFeeCap and GasTipCap, I think that the swapCoin.gasTipCap check in (*Backend).ValidateFeeRate is wrong or at least misleading.
Say we are debugging and we inspect a swapCoin, we'd see a gasTipCap == gasFeeCap (a huge gasTipCap).
I guess we can solve this with documentation spelling out the logic of why a legacy txn can be treated as a dynamic tx with a massive tip, but I do think we should spell that out in both methods (baseCoin construction and ValidateFeeRate)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added some docs.

@chappjc chappjc merged commit 6e18f18 into decred:master Feb 8, 2022
@chappjc chappjc added this to the 0.5 milestone Apr 21, 2022
@chappjc chappjc added the ETH label Aug 26, 2022
@martonp martonp deleted the ethServerFeeRate branch December 20, 2022 22:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

server/asset/eth: max fee rate needs to be a max gas cap
4 participants