Skip to content

multi: match fee rates and smart fees#505

Merged
chappjc merged 1 commit into
decred:masterfrom
chappjc:match-fee-rates
Jul 2, 2020
Merged

multi: match fee rates and smart fees#505
chappjc merged 1 commit into
decred:masterfrom
chappjc:match-fee-rates

Conversation

@chappjc
Copy link
Copy Markdown
Member

@chappjc chappjc commented Jun 24, 2020

Resolving #502

Purpose

Smart fee rates from server provided to clients for their swap contract txns.

Details

Server asset fee estimation

  • The server's asset backends provide an optimal fee rate (e.g. estimatesmartfee with dcr/btc/etc.). asset.Backend interface method FeeRate() (uint64, error) is added.
  • Fee rate is in atoms/byte, but we could consider atoms/KB for increased precision.

Fee rate communication

  • Instead of the "feeRate" asset config parameter, it is now "maxFeeRate", which indicates how much clients are required to reserve in their order-funding coins to cover the worst case fees at swap time.
  • The server-originating 'match' request payload (msgjson.Match) now includes "feeratebase" and "feeratequote". The client only cares about one presently, the one that applies to their swap contract txns, but the other could be used for both auditing the counterparty contract txn and possibly their own redeem txn. The msgjson.Match serialization=>signature is updated to include these fields. The dex/order.Match type now also includes FeeRateBase and FeeRateQuote fields, but these are not included in the serialization=>matchID.

Server-side (Swapper) contract fee rate handling

  • The Swapper computes required fee rates on start of negotiation (after market matching and before sending 'match' requests). If fee estimation fails or returns a value higher than the configured maxFeeRate, it uses maxFeeRate.
  • The Swapper checks each contract txn's fee rate, ensuring it is at least the required fee rate for the relevant asset as set in the matchTracker.Match.FeeRate{Base,Quote}.
  • The redemption txns' fee rates are not checked as this is the user's problem.

Client-side contract fee rate handling

  • Use the provided fee rate from the 'match' message.
  • Since swap transactions batch several contracts, the highest fee rate across all of the batched swaps is used.
  • Client assets estimate their own fee in Redeem, Refund, PayFee, and Withdraw internally (with a FallbackFeeRate). Swap uses the fee rate provided by the server.
  • TODO: Setting for fallback fee rate: a78171b

Transaction sizing and order funding

  • Several transaction sizing fixes, and a new InitTxSizeBase const that does not include inputs.
  • Order funding had incorrectly computed the required funds for a chain of N swaps while double counting the already-included input of the first swap in the chain.
    • Fix RequiredFunds (now RequiredOrderFunds) to account for this when adding inputs for the first txn in the chain, and perform all computations in integer instead of float.
    • Client exchange wallets' FundOrder method uses RequiredOrderFunds with the InitTxSizeBase.
  • Server assets provide an InitTxSizeBase method.
  • Market is updated to check required funds on new orders using RequiredOrderFunds and InitTxSizeBase.

Server DB updates for contract fee rates

  • Store the base and quote rates in the matches table.
  • Finally use the placeholder takerSell column, using the value to load the correct rate for UserMatch.FeeRateSwap.

Update Swapper startup for the recently added match time field

@chappjc chappjc force-pushed the match-fee-rates branch 5 times, most recently from 2421a14 to cdcea26 Compare June 25, 2020 15:27
@chappjc chappjc marked this pull request as ready for review June 25, 2020 15:49
@chappjc

This comment has been minimized.

Copy link
Copy Markdown
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.

I could only find a few places to comment on the code. Everything makes sense and looks good to me. However, while testing this I continue to run into a panic in dcrd, and so will continue testing once the reasons for that are better understood.

Comment thread client/asset/btc/livetest/livetest.go Outdated
Comment thread dex/networks/btc/script.go Outdated
Comment thread dex/networks/dcr/script.go
Comment thread server/asset/common.go Outdated
Comment thread server/asset/btc/btc.go Outdated
@chappjc
Copy link
Copy Markdown
Member Author

chappjc commented Jun 29, 2020

decred/dcrd#2232 testnet issues.

But I'm afraid to say I've not taken this for a spin yet, so there's likely many things I've neglected.

Comment thread client/asset/btc/btc.go Outdated
Comment thread client/asset/dcr/dcr.go Outdated
Comment thread dex/msgjson/types.go
Comment thread client/core/core.go
Copy link
Copy Markdown
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.

First pass. No major hangups. I still need to deep dive on the script size fixes and TestFundEdges stuff.

Comment thread client/asset/btc/btc.go Outdated
Comment thread client/asset/btc/btc.go Outdated
Comment thread client/core/trade.go Outdated
Comment thread dex/networks/dcr/script.go Outdated
Comment thread dex/order/match.go
Comment thread client/asset/btc/btc.go Outdated
Comment thread client/asset/dcr/dcr.go Outdated
Comment thread client/core/trade.go Outdated
Comment on lines +307 to +308
optimalRate := float64(optimalFeeRate) * 1e-5
// fmt.Println((float64(optimalFeeRate)*1e-5)-0.00024)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

BTW, the kind of thing that makes be super uncomfortable with floating point math: https://play.golang.org/p/Dhmxmp5OosM

The error is less than 1e-8, so it's going to work out to the same atoms/byte, but it still sucks.

@chappjc
Copy link
Copy Markdown
Member Author

chappjc commented Jun 30, 2020

@buck54321 Addressed your comments in 0f80a22, 73c00f7, and 00d632a.

@chappjc chappjc force-pushed the match-fee-rates branch from 3d60981 to 3ccfa24 Compare July 1, 2020 15:20
@chappjc
Copy link
Copy Markdown
Member Author

chappjc commented Jul 1, 2020

Tested on simnet now that it's rebased on master, and it worked as expected. However, fee estimation seems to not work on BTC simnet/regnet, so the server's configured fallback fee is sent to the client for BTC.

dcrdex

The server got the optimal fee rate of 10 atoms/byte and the fallback of 100 sat/byte:

[INF] MKT: Matching complete for market dcr_btc epoch 265603419: 1 matches (0 partial fills), 1 completed OK (not booked), 0 booked, 1 unbooked, 0 failed
[DBG] MKT: Negotiating 1 matches for epoch 265603419:6000
[DBG] SWAP: Optimal fee rate for "dcr": 10
[WRN] SWAP: Unable to determining optimal fee rate for "btc", using fallback of 100. Err: Insufficient data or no feerate found

All negotiation steps including fee rate checks succeeded:

[DBG] AUTH: Recorded order 4056844955cce70784bf6c7591b8b2f47abaf47771500e13947ce5a5886614f9 that has finished processing: user=05b1c1137a602501bed7c9791f4b20d5240e47584dd24ce724cbc7659b18f697, time=1593620524791, target=<nil>
...
[DBG] AUTH: Recorded order 28c9bd49fa4d6769a455b10b9c6fd75566d59cd2c647c34b932e9cfb57a0a3fc that has finished processing: user=05b1c1137a602501bed7c9791f4b20d5240e47584dd24ce724cbc7659b18f697, time=1593620524808, target=<nil>

dexc

The client used the fee rates provided in the 'match' message (note the Fee rate = part):

[INF] CORE: Broadcasted transaction with 1 swap contracts for order 28c9bd49fa4d6769a455b10b9c6fd75566d59cd2c647c34b932e9cfb57a0a3fc. Fee rate = 100. Receipts: [0baac477af6eca6e105c6e158a3f4aa253821172cff3fa0225ae0e78c9b68347:0]
...
[INF] CORE: Broadcasted transaction with 1 swap contracts for order 4056844955cce70784bf6c7591b8b2f47abaf47771500e13947ce5a5886614f9. Fee rate = 10. Receipts: [0b52c01391b5b35d7b91202b8de713a516f9808083a8214f9be1edee101094ca:0]

Since client/asset.Wallet.Redeem(contract) uses fee estimation internally now, BTC used the fallback fee and DCR used a fee estimate:

[WRN] CORE[btc]: Unable to get optimal fee rate, using fallback of 80: Insufficient data or no feerate found

Note that the client's fallback fee is presently hardcoded. It will become a new github issue to have this configured and included in the db wallet scheme.

@chappjc chappjc force-pushed the match-fee-rates branch from 555063f to 442f2a5 Compare July 1, 2020 16:53
Comment thread client/asset/btc/btc.go Outdated
Comment thread client/asset/interface.go
Comment on lines +46 to +48
// FallbackFeeRate is fee rate used for withdraws, refunds, and DEX fee
// payment transactions when optimal fee estimation is unavailable.
FallbackFeeRate uint64
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this should be a WalletInfo.ConfigOpts setting, since it's particular to BTC and DCR.

I see your related comment at https://github.com/decred/dcrdex/pull/505/files#r447077464. Are we punting for now?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thought about ConfigOpts, but I got the impression that was for wallet software (dcrwallet, bitcoind, etc.) settings rather than settings for the Go wallet implementation. Will revisit this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Anyway, punt as per matrix chat. @itswisdomagain Maybe this could be a follow up for you. There's a sort of obsolete WalletInfo.DefaultFeeRate now and WalletConfig.FallbackFeeRate. The best way to clean this up depends on when, how, and where we want the fallback fee rate to be set by the user.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Right. Will think up and share some ideas and put up a PR once an issue for this is put up.

Comment thread client/asset/interface.go Outdated
Comment thread dex/calc/fees.go Outdated
Comment thread dex/msgjson/types.go Outdated
Comment thread dex/networks/btc/script.go
Comment thread dex/networks/btc/script.go Outdated
Comment thread dex/networks/btc/script.go Outdated
Comment thread dex/networks/dcr/script.go Outdated
@chappjc chappjc force-pushed the match-fee-rates branch from af7f5fa to a6b8def Compare July 2, 2020 17:09
Purpose

Smart fee rates from server provided to clients for their swap contract txns.

Details

Server asset fee estimation
- The server's asset backends provide an optimal fee rate (e.g. estimatesmartfee with dcr/btc/etc.).
- Fee rate is in atoms/byte, but we could consider atoms/KB for increased precision.

Fee rate communication
- Instead of the `"feeRate"` asset config parameter, it is now `"maxFeeRate"`, which indicates how much clients are required to reserve in their order-funding coins to cover the worst case fees at swap time.
- The server-originating `'match'` request payload (`msgjson.Match`) now includes `"feeratebase"` and `"feeratequote"`.  The client only cares about one presently, the one that applies to their swap contract txns, but the other could be used for both auditing the counterparty contract txn and possibly their own redeem txn.  The `msgjson.Match` serialization=>signature is updated to include these fields.  The `dex/order.Match` type now also includes `FeeRateBase` and `FeeRateQuote` fields, but these are _not_ included in the serialization=>matchID.
- The `'config'` response from the server now includes a `swapSizeBase` field that corresponds to the size of the asset's swap transaction not including inputs, which is now required to compute the required order funds for arbitary numbers and types of inputs to the first swap in a chain.

Server-side (Swapper) contract fee rate handling
- The Swapper computes required fee rates on start of negotiation (after market matching and before sending `'match'` requests). If fee estimation fails or returns a value higher than the configured maxFeeRate, it uses maxFeeRate.
- The Swapper checks each contract txn's fee rate, ensuring it is at least the required fee rate for the relevant asset as set in the `matchTracker.Match.FeeRate{Base,Quote}`.
- The redemption txns' fee rates are not checked as this is the user's problem.

Client-side contract fee rate handling
- Use the provided fee rate from the `'match'` message.
- Since swap transactions batch several contracts, the highest fee rate across all of the batched swaps is used.
- Client assets estimate their own fee in `Redeem`, `Refund`, `PayFee`, and `Withdraw` internally (with a `FallbackFeeRate`).  `Swap` uses the fee rate provided by the server.
- TODO: User setting for fallback fee rate.

Transaction sizing and order funding
- Several transaction sizing fixes, and a new `InitTxSizeBase` const that does not include inputs.
- Order funding had incorrectly computed the required funds for a chain of N swaps while double counting the already-included input of the first swap in the chain.
    - Fix `RequiredFunds` (now `RequiredOrderFunds`) to account for this when adding inputs for the first txn in the chain, and perform all computations in integer instead of float.
    - Client exchange wallets' `FundOrder` method uses `RequiredOrderFunds` with the `InitTxSizeBase`.
- Server assets provide an `InitTxSizeBase` method
- `Market` is updated to check required funds on new orders using `RequiredOrderFunds` and `InitTxSizeBase`

Server DB updates for contract fee rates
- Store the base and quote rates in the `matches` table.
- Finally use the placeholder `takerSell` column, using the value to load the correct rate for `UserMatch.FeeRateSwap`.

Update Swapper startup for the recently added match time field
- The recent addition of `matchTracker.MatchTime` in ab3dff2 requires reloading this time from `matchTracker.Match.Epoch.End()`

Squashed commit messages:

server/asset: Backend.FeeRate

Update btcd to master for EstimateSmartFee.

TODO: consider changing fee rates from atoms/byte to atoms/KB.

msgjson: Add feeratebase and feeratequote to Match

TODO: consider feerateswap and feerateredeem instead since these are
sent to the actor.

dex/order: add FeeRateBase and FeeRateQuote to Match.

dex/{,msgjson}: Rename FeeRate to MaxFeeRate

msgjson: Match need not be Stampable as ServerTime is set directly

Remove the unused (*Match).Stamp method.

msgjson: include FeeRate{Base,Quote} in serialization

swap: check fee rates in processInit and processRedeem

Currently get optimal fee rates on Negotiate and set them in each
dex/order.Match for the matchTrackers, and msgjson.Match for the
match' route request.

TODO: consider sending fee rates with audit ack and redeem ack requests.

Use dex.Asset.MaxFeeRate.

swap: Negotiate ensures assets of provided matches are supported

dex/calc: refactor RequiredFunds => RequiredOrderFunds

RequiredFunds added the input size to the reference SwapSize, but this
counted an input already. Use a "asset.SwapSizeBase" that is a swap txn size
without an input when computing the first swap in a chain of swaps.
The following N-1 swaps in a chain of N just use SwapSize as before.
The result is the same as before *if* the provided input size is a p2pkh
input as was assumed in the SwapSize (InitTxSize) computation.
The result is only different if the input is sized differently than a
p2pkh input, and we do support that.

Also refactor to use integer math only.

SwapSizeBase in messages and calculations.

client/asset: SwapSizeBase

dex/{,calc,msgjson}: SwapSizeBase fields

client/{core,webserver}: use SwapSizeBase

server: use SwapSizeBase in ConfigResponse and RequiredOrderFunds

client/asset: use fee estimation

Add FeeRate method to asset.Wallet inferface to estimate fee.
Add FeeRate field to the asset.Swaps struct.

Refactor asset.Wallet methods that send txns to either take a fee or use
an internal fee estimate.
- Swap uses the asset.Swaps.FeeRate field.
- Redeem takes a feeRate uint64 input, replacing the dex.Asset arg.
- Refund, PayFee, and Withdraw should estimate their own fee (e.g. with
  FeeRate internally).

client/asset/{btc,dcr}: impl. fee est. and new asset.Wallet interface

Add EstimateSmartFee to rpcClient interface.

Add fallbackFeeRate field to ExchangeWallet, used if FeeRate fails to
provied an optimal fee estimate in Redeem, Refund, PayFee, and Withdraw.
Note that FeeRateWithFallback does not need to be part of the interface.

client/core: Add a note a TODO about FallbackFeeRate config and storage

dex/networks: fix some sizes, more docs

btc:
- swap redeem and refund sig script sizes were 1 byte too small;
  they needed 2 bytes for to encode the push data size of 97.
- RedeemP2PKHInputSize was missing a varint data size for the sigscript
  since TxInOverhead does not include that.
- (*SpendInfo).VBytes was also missing a varint data size byte(s).  Use
  wire.VarIntSerializeSize for this now, almost always returning 1.
- New InitTxSizeBase that excludes one input (RedeemP2PKHInputSize).

dcr:
- swap redeem and refund sig script sizes were 1 byte too small;
  they needed 2 bytes for to encode the push data size of 97.
- Add DataPrefixSize and test it with SwapContractSize.
- Remove the sigscript size varint from TxInOverhead to match btc.
- Update (*SpendInfo).Size to compute the varint size based on the
  sigScript as done now in btc's (*SpendInfo).VBytes.
- Use DataPrefixSize in  (*SpendInfo).Size.

There are some TODOs with both networks:
- In btc, the InputInfo function should check and restrict the
  redeem script format since p2sh with 1-of-1 p2ms is presently
  indistinguishable from p2sh with non-multisig.
- ExtractContractHash is functionally ExtractScriptHash.
  In dcr it now wraps ExtractScriptHash.
  In btc, ExtractScriptHash should be simplified to extract it from the
  redeem script []byte instead of extracting addresses and then getting
  the script hash from a AddressScriptHash.

server/asset: add InitTxSizeBase to Backend interface

InitTxSizeBase is InitTxSize not including an input.
Some input size bug fixes.
Move EstimateSmartFee to the node interfaces instead of using rpcclient.

btc:
- Implement InitTxSizeBase.
- Add EstimateSmartFee to btcNode interface.

dcr:
- Implement InitTxSizeBase.
- Add EstimateSmartFee to btcNode interface.
- Use (*SpendInfo).Size in (*Backend).{utxo,output}

server/market: use InitTxSizeBase and RequiredOrderFunds

In order router, use InitTxSizeBase and the revised RequiredOrderFunds
to compute the required funds for incoming orders.

client/core: use FeeRate{Quote,Base} from Match msg

In (*trackedTrade).negotiate, use the fee rate from the match message.
Store the fee rate for the swap txn in
matchTracker.MetaMatch.Match.FeeRateSwap.

In (*trackedTrade).swapMatches where swaps are batches, get the highest
fee rate of all the matchTrackers sent to swap.

Use the new client/asset.Wallet interface (Swap, Refund, FeeRate, etc.).

Use asset.Wallet.FeeRate() to get the fee rate for the redeem txn.
NOTE that the redeem fee rate could be provided by the server, but the
user is already incentivized to get the redeem mined as it pays to them.
Hypothetically, the redeem fee rate could come from the 'audit' and
'redemption' requests from the server.  Alternatively, the slightly
stale fee rate provided in the 'match' request, which is presently just
used for the contract txns, could also apply to the redemption txn.

dex/order: Add FeeRateSwap field to UserMatch

swap: update state for MatchTime in matchTracker

NOTE: MatchTime is not stored in the main DB, but it is used to define
the locktime for the swap contract and redeem txns.

db: matches table: store baserate and quoterate

Add BaseRate, QuoteRate, and TakerSell to db.MatchData.

Use the existing takerSell column, loading into MatchData.
Consider creating a Sell field in dex/order.UserMatch.

client/asset/btc: fix CompatibilityCheck ignoring P2WSHScript/WSHAddr

market: remove stray racy write in TestMarket_Cancelable

dex/networks/btc: correctly work with 1-of-1 p2ms-p2sh in InputInfo
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.

5 participants