-
Notifications
You must be signed in to change notification settings - Fork 97
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/core,eth: Lock funds for refund #1479
Conversation
8544515
to
546fed0
Compare
Minor conflict by proximity to FeeRater changes in core_test.go. Not holding up my review though, that's coming shortly. |
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.
Working well.
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.
Some quick comments. I think we need to have a discussion in matrix about making the eth client a FeeRate with the formula we've settled on, and then perhaps capping the computed rate by the user's fee rate limit. related: #1485
- client/asset: Rename AssetRedeemer interface to AssetLocker, and add identical functions used for refunds in addition to redemptions. - client/eth: Refactor fund locking to keep track of funds locked for initiation, refunds, and redemptions separately. - client/core: Keep track of refund reserves the same way as they are tracked for redemptions. In addition to unlocking when the deal is revoked or cancelled, funds reserved for refunds are also unlocked when either the counterparty or the current user does a redemption.
5d55f85
to
7281814
Compare
@@ -4203,17 +4206,31 @@ func (c *Core) prepareTrackedTrade(dc *dexConnection, form *TradeForm, crypter e | |||
if len(pubKeys) == 0 || len(sigs) == 0 { | |||
return nil, 0, newError(signatureErr, "wrong number of pubkeys or signatures, %d & %d", len(pubKeys), len(sigs)) | |||
} | |||
redemptionReserves, err = accountRedeemer.ReserveN(redemptionLots, wallets.toAsset.MaxFeeRate, wallets.toAsset.Version) | |||
redemptionReserves, err = accountRedeemer.ReserveNRedemptions(redemptionRefundLots, wallets.toAsset.MaxFeeRate, wallets.toAsset.Version) |
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.
Works for me. Only additional thought about this matter is that the rate given to ReserveNRedemptions
and ReserveNRefunds
could be the larger of MaxFeeRate
and the rate from FeeRate
(if also a FeeRater
). That would be pretty screwy if the server had a rate set that low, so whatever, let's not change anything now. This PR looks good to me.
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 the swap cannot be initiated, then it doesn't matter if too little is reserved for refunds and redemptions.
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.
The swap tx could still be mined with a gas fee cap less than what FeeRate would recommend. It would just be dicey for the refund (that happens in several hours). For the redeem, you're redeeming the other person's swap, so who knows what they used.
@@ -95,7 +96,8 @@ var ( | |||
fromVersionKey = []byte("fromVersion") | |||
toVersionKey = []byte("toVersion") | |||
optionsKey = []byte("options") | |||
reservesKey = []byte("reservesKey") |
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.
For the record, I'm on board with breaking compatibility with the last dev version of the DB encoding. Not worth an upgrade esp. considering the state of ETH (not used in the wild).
7281814
to
6c8fcc1
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.
LGTM. Kinda too bad that the unlock
method has an error return though.
client/asset/eth/eth.go
Outdated
default: | ||
return nil, fmt.Errorf("invalid fund reserve type: %v", t) |
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 tempted to say panic, remove the error and logging in unlock
's caller.
With an enum of fundReserveType
s, it takes a special type of programmer error to pass an invalid value.
Ready to merge unless @buck54321 is reviewing. |
client/asset/eth/eth.go
Outdated
initiationFunds := ord.Value + maxSwapFees | ||
coins := asset.Coins{eth.createFundingCoin(initiationFunds)} | ||
|
||
eth.lockedFundsMtx.Lock() | ||
defer eth.lockedFundsMtx.Unlock() | ||
err := eth.lockedFunds.lock(initiationFunds, initiationReserve, eth.balance) |
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.
Can we lock up the refund reserves here too, so the caller doesn't have to do it separately?
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 was thinking about that too, but I did it this way because if the refund reserves were locked here, then FundOrder
would need to return the amount locked for refunds separately. I didn't want this to have to be done in non-account based coins.
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 see what you mean. I might propose a change later, but this is good for now.
client/asset/eth/eth.go
Outdated
} | ||
|
||
// lock locks funds for a use case. | ||
func (r *fundReserves) lock(amt uint64, t fundReserveType, getBalanceFn func() (*asset.Balance, error)) error { |
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 things here that suggest to me that fundReserves
is better as an ExchangeWallet
struct
field. 1) You're passing in ExchangeWallet
methods, 2) you're returning errors just for logging, and 3) the mutex is still being handled by ExchangeWallet
. How about converting these to ExchangeWallet
methods and doing
type ExchangeWallet struct {
...
lockedFundsMtx sync.RWMutex
lockedFunds struct {
initiateReserves uint64
redemptionReserves uint64
refundReserves uint64
}
...
}
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.
That's a good point. Could even embed the mutex in that struct
client/core/trade.go
Outdated
newReserved := applyFraction(num, denom, t.redemptionReserves) | ||
if t.redemptionLocked+newReserved > t.redemptionReserves { | ||
|
||
newReserved := t.reservesToLock(num, denom, t.redemptionReserves, t.redemptionLocked) | ||
|
||
if err := redeemer.ReReserveRedemption(newReserved); err != nil { | ||
t.dc.log.Errorf("error re-reserving redemption %d %s for order %s: %v", | ||
newReserved, t.wallets.toAsset.UnitInfo.AtomicUnit, t.ID(), err) | ||
return | ||
} | ||
t.redemptionLocked += newReserved | ||
} |
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.
This is a change in behavior, even if the outcome may be identical. Before, if the requested reservers were > redemptionReserves
, an error was logged and that's it. Now, an error is logged, but we still call ReReserveRedemption
with an argument of 0
, and add 0
to the redemptionLocked
. I don't expect that this would result in any issues, but calling ReReserveRedemption
with 0
is questionable behavior.
// UnlockReserves is used to return funds when an order is canceled or | ||
// otherwise completed unfilled. | ||
UnlockReserves(uint64) | ||
ReserveNRefunds(n, feeRate uint64, assetVer uint32) (uint64, error) |
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.
This method would be unneeded if we lock up the refund reserves in FundOrder
.
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 last things.
client/asset/eth/eth.go
Outdated
|
||
// fundReserveOfType returns a pointer to the funds reserved for a particular | ||
// use case. | ||
func (eth *ExchangeWallet) fundReserveOfType(t fundReserveType) *uint64 { |
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.
Let's move this down by the other related methods below the ExchangeWallet
definition.
client/asset/eth/eth.go
Outdated
func (eth *ExchangeWallet) balance() (*asset.Balance, error) { | ||
bal, err := eth.node.balance(eth.ctx) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
locked := eth.locked + eth.redemptionReserve | ||
locked := eth.amountLocked() | ||
fmt.Printf("in balance -- %+v - %v\n", bal, locked) |
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 like this can go
c.db, c.latencyQ, wallets, coins, c.notify, c.formatDetails, form.Options, redemptionReserves) | ||
c.db, c.latencyQ, wallets, coins, c.notify, c.formatDetails, form.Options, redemptionReserves, refundReserves) | ||
|
||
tracker.redemptionLocked = tracker.redemptionReserves |
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.
You've got another one of these three lines down.
client/core/trade.go
Outdated
@@ -640,6 +702,7 @@ func (t *trackedTrade) negotiate(msgMatches []*msgjson.Match) error { | |||
// reserves. | |||
if remain := trade.Quantity - preCancelFilled; remain > 0 && (completedMarketSell || cancelMatch != 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.
Not from this PR, but I think we need to return reserves when lo, ok := t.Order.(*order.LimitOrder); ok && lo.Force == order.StandingTiF
here too.
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.
You mean ImmediateTiF
right?
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.
Yes. Sorry.
Previously, the funds needed for refunds were locked in
FundOrder
together with the funds needed for initiations, but they were unlocked whenSwap
was called withLockChange = false
. This diff applies the same logic we use to reserve funds for redemptions to refunds.Prior to this PR, redemptions were being reserved using the server's max fee rate, but the redemption transaction was being submitted with the current fee rate estimate. This PR updates this to use the server's max fee rate when submitting the transaction as well. There is no reason to use a lower rate when submitting the transaction, as ETH uses dynamic transaction fees, and the entire fee will not be used anyways. The server's max fee rate is also used for refunds.
client/asset
: RenameAssetRedeemer
interface toAssetLocker
, and add functions for refunds with the same functionality as already exist for redemptions.client/eth
: Refactor fund locking to keep track of funds locked for initiation, refunds, and redemptions separately. Also, make ETH aFeeRater
.client/core
: Keep track of refund reserves the same way as they are tracked for redemptions. In addition to unlocking when the deal is revoked or cancelled, funds reserved for refunds are also unlocked when either the counterparty or the current user does a redemption.Closes #1466