From eb2af19e22ca47da0d1ee29bf2b7c0ea53b81ddd Mon Sep 17 00:00:00 2001 From: Brian Stafford Date: Tue, 2 May 2023 18:43:34 -0500 Subject: [PATCH] implement zip 317 --- client/asset/btc/btc.go | 201 ++++++++++++++++++++--------- client/asset/btc/btc_test.go | 5 +- client/asset/btc/coin_selection.go | 25 ++-- client/asset/btc/rpcclient.go | 2 +- client/asset/btc/simnet_test.go | 37 ++++++ client/asset/btc/spv_wrapper.go | 27 ++-- client/asset/zec/shielded_rpc.go | 3 +- client/asset/zec/zec.go | 87 ++++++++++++- dex/networks/zec/script.go | 59 +++++++++ dex/networks/zec/tx.go | 14 ++ docs/images/zip-0317.png | Bin 0 -> 122798 bytes docs/images/zip-0317.tex | 20 +++ server/asset/btc/btc.go | 7 +- server/asset/btc/tx.go | 6 +- server/asset/btc/utxo.go | 6 + server/asset/common.go | 7 + server/asset/zec/zec.go | 36 ++++++ server/market/orderrouter.go | 17 ++- 18 files changed, 468 insertions(+), 91 deletions(-) create mode 100644 dex/networks/zec/script.go create mode 100644 docs/images/zip-0317.png create mode 100644 docs/images/zip-0317.tex diff --git a/client/asset/btc/btc.go b/client/asset/btc/btc.go index cc88f63dfb..26dcb00575 100644 --- a/client/asset/btc/btc.go +++ b/client/asset/btc/btc.go @@ -291,6 +291,28 @@ func RPCConfigOpts(name, rpcPort string) []*asset.ConfigOption { } } +type OrderEstimator func(swapVal, inputCount, inputsSize, maxSwaps, swapSizeBase, swapSize, feeRate uint64) uint64 + +func defaultOrderEstimator(swapVal, _, inputsSize, maxSwaps, swapSizeBase, swapSize, feeRate uint64) uint64 { + return calc.RequiredOrderFundsAlt(swapVal, inputsSize, maxSwaps, swapSizeBase, swapSize, feeRate) +} + +type DustRater func(txSize, value, minRelayTxFee uint64, segwit bool) bool + +var defaultDustRater = dexbtc.IsDustVal + +type TxFeesCalculator func(baseTxSize, inputCount, inputsSize, feeRate uint64) uint64 + +func defaultTxFeesCalculator(baseTxSize, _, inputsSize, feeRate uint64) uint64 { + return (baseTxSize + inputsSize) * feeRate +} + +type SplitFeeCalculator func(inputCount, inputsSize, maxFeeRate uint64, extraOutput, segwit bool) (swapInputSize, baggage uint64) + +func defaultSplitFeeCalculator(_, _, maxFeeRate uint64, extraOutput bool, segwit bool) (swapInputSize, baggage uint64) { + return splitTxFees(maxFeeRate, extraOutput, segwit) +} + // TxInSigner is a transaction input signer. In addition to the standard Bitcoin // arguments, TxInSigner receives all values and pubkey scripts for previous // outpoints spent in this transaction. @@ -393,7 +415,11 @@ type BTCCloneCFG struct { TxHasher func(*wire.MsgTx) *chainhash.Hash // TxSizeCalculator is an optional function that will be used to calculate // the size of a transaction. - TxSizeCalculator func(*wire.MsgTx) uint64 + TxSizeCalculator func(*wire.MsgTx) uint64 + DustRater DustRater + OrderEstimator OrderEstimator + TxFeesCalculator TxFeesCalculator + SplitFeeCalculator SplitFeeCalculator // TxVersion is an optional function that returns a version to use for // new transactions. TxVersion func() int32 @@ -809,6 +835,11 @@ type baseWallet struct { deserializeTx func([]byte) (*wire.MsgTx, error) serializeTx func(*wire.MsgTx) ([]byte, error) calcTxSize func(*wire.MsgTx) uint64 + orderReq OrderEstimator + isDust DustRater + txFees TxFeesCalculator + splitTxFees SplitFeeCalculator + manualSendEst bool hashTx func(*wire.MsgTx) *chainhash.Hash stringAddr dexbtc.AddressStringer txVersion func() int32 @@ -868,8 +899,7 @@ func (w *baseWallet) apiFeeFallback() bool { type intermediaryWallet struct { *baseWallet - txFeeEstimator txFeeEstimator - tipRedeemer tipRedemptionWallet + tipRedeemer tipRedemptionWallet } // ExchangeWalletSPV embeds a ExchangeWallet, but also provides the Rescan @@ -1166,9 +1196,8 @@ func newRPCWallet(requester RawRequester, cfg *BTCCloneCFG, parsedCfg *RPCWallet node := newRPCClient(core) btc.node = node return &intermediaryWallet{ - baseWallet: btc, - txFeeEstimator: node, - tipRedeemer: node, + baseWallet: btc, + tipRedeemer: node, }, nil } @@ -1221,6 +1250,28 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWalle txSizeCalculator = dexbtc.MsgTxVBytes } + isDust := cfg.DustRater + if isDust == nil { + isDust = defaultDustRater + } + + orderReq := cfg.OrderEstimator + if orderReq == nil { + orderReq = defaultOrderEstimator + } + + txFees := cfg.TxFeesCalculator + if txFees == nil { + txFees = defaultTxFeesCalculator + } + + splitTxFees := cfg.SplitFeeCalculator + if splitTxFees == nil { + splitTxFees = defaultSplitFeeCalculator + } + + manualSendEstimation := cfg.DustRater != nil && cfg.TxFeesCalculator != nil + txHasher := cfg.TxHasher if txHasher == nil { txHasher = hashTx @@ -1262,6 +1313,11 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWalle serializeTx: txSerializer, hashTx: txHasher, calcTxSize: txSizeCalculator, + isDust: isDust, + orderReq: orderReq, + manualSendEst: manualSendEstimation, + txFees: txFees, + splitTxFees: splitTxFees, txVersion: txVersion, Network: cfg.Network, } @@ -1316,9 +1372,8 @@ func OpenSPVWallet(cfg *BTCCloneCFG, walletConstructor BTCWalletConstructor) (*E return &ExchangeWalletSPV{ intermediaryWallet: &intermediaryWallet{ - baseWallet: btc, - txFeeEstimator: spvw, - tipRedeemer: spvw, + baseWallet: btc, + tipRedeemer: spvw, }, authAddOn: &authAddOn{spvw}, spvNode: spvw, @@ -1443,7 +1498,7 @@ func (btc *baseWallet) IsDust(txOut *wire.TxOut, minRelayTxFee uint64) bool { if btc.dustLimit > 0 { return txOut.Value < int64(btc.dustLimit) } - return dexbtc.IsDust(txOut, minRelayTxFee) + return btc.isDust(uint64(txOut.SerializeSize()), uint64(txOut.Value), minRelayTxFee, btc.segwit) } // getBlockchainInfoResult models the data returned from the getblockchaininfo @@ -1937,7 +1992,7 @@ func (btc *baseWallet) SingleLotSwapFees(form *asset.PreSwapForm) (fees uint64, } const maxSwaps = 1 // Assumed single lot order - swapFunds := calc.RequiredOrderFundsAlt(form.LotSize, inputSize, maxSwaps, + swapFunds := btc.orderReq(form.LotSize, 1, inputSize, maxSwaps, btc.initTxSizeBase, btc.initTxSize, bumpedNetRate) fees += swapFunds - form.LotSize @@ -2022,22 +2077,23 @@ func (btc *baseWallet) estimateSwap(lots, lotSize, feeSuggestion, maxFeeRate uin // UTXOs. Actual order funding accounts for this. For this estimate, we will // just not use a split tx if the split-adjusted required funds exceeds the // total value of the UTXO selected with this enough closure. - sum, _, inputsSize, _, _, _, _, err := tryFund(utxos, - orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, trySplit)) + sum, _, inputsSize, fundingCoins, _, _, _, err := tryFund(utxos, + btc.orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, trySplit)) if err != nil { return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", amount(val), err) } + inputCount := uint64(len(fundingCoins)) digestInputs := func(inputsSize uint64) (reqFunds, maxFees, estHighFees, estLowFees uint64) { - reqFunds = calc.RequiredOrderFundsAlt(val, inputsSize, lots, + reqFunds = btc.orderReq(val, inputCount, inputsSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) // same as in enough func maxFees = reqFunds - val - estHighFunds := calc.RequiredOrderFundsAlt(val, inputsSize, lots, + estHighFunds := btc.orderReq(val, inputCount, inputsSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedNetRate) estHighFees = estHighFunds - val - estLowFunds := calc.RequiredOrderFundsAlt(val, inputsSize, 1, + estLowFunds := btc.orderReq(val, inputCount, inputsSize, 1, btc.initTxSizeBase, btc.initTxSize, bumpedNetRate) // best means single multi-lot match, even better than batch estLowFees = estLowFunds - val return @@ -2047,8 +2103,8 @@ func (btc *baseWallet) estimateSwap(lots, lotSize, feeSuggestion, maxFeeRate uin // Math for split transactions is a little different. if trySplit { - _, splitMaxFees := btc.splitBaggageFees(bumpedMaxRate, false) - _, splitFees := btc.splitBaggageFees(bumpedNetRate, false) + _, splitMaxFees := btc.splitTxFees(inputCount, inputsSize, bumpedMaxRate, false, btc.segwit) + _, splitFees := btc.splitTxFees(inputCount, inputsSize, bumpedNetRate, false, btc.segwit) reqTotal := reqFunds + splitMaxFees // ~ rather than actually fund()ing again if reqTotal <= sum && sum-reqTotal >= reserves { return &asset.SwapEstimate{ @@ -2068,7 +2124,7 @@ func (btc *baseWallet) estimateSwap(lots, lotSize, feeSuggestion, maxFeeRate uin kept := leastOverFund(reserves, utxos) utxos := utxoSetDiff(utxos, kept) - sum, _, inputsSize, _, _, _, _, err = tryFund(utxos, orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, false)) + sum, _, inputsSize, _, _, _, _, err = tryFund(utxos, btc.orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, false)) if err != nil { return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", amount(val), err) } @@ -2227,14 +2283,14 @@ func (btc *baseWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, er reserves := btc.reserves() minConfs := uint32(0) coins, fundingCoins, spents, redeemScripts, inputsSize, sum, err := btc.fund(reserves, minConfs, true, - orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit)) + btc.orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit)) if err != nil { if !useSplit && reserves > 0 { // Force a split if funding failure may be due to reserves. btc.log.Infof("Retrying order funding with a forced split transaction to help respect reserves.") useSplit = true coins, fundingCoins, spents, redeemScripts, inputsSize, sum, err = btc.fund(reserves, minConfs, true, - orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit)) + btc.orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit)) extraSplitOutput = reserves + bondsFeeBuffer(btc.segwit, btc.feeRateLimit()) } if err != nil { @@ -2284,7 +2340,7 @@ func (btc *baseWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, er } func (btc *baseWallet) fundInternal(keep uint64, minConfs uint32, lockUnspents bool, - enough func(size, sum uint64) (bool, uint64)) ( + enough func(count, size, sum uint64) (bool, uint64)) ( coins asset.Coins, fundingCoins map[outPoint]*utxo, spents []*output, redeemScripts []dex.Bytes, size, sum uint64, err error) { utxos, _, avail, err := btc.spendableUTXOs(minConfs) if err != nil { @@ -2333,7 +2389,7 @@ func (btc *baseWallet) fundInternal(keep uint64, minConfs uint32, lockUnspents b } func (btc *baseWallet) fund(keep uint64, minConfs uint32, lockUnspents bool, - enough func(size, sum uint64) (bool, uint64)) ( + enough func(count, size, sum uint64) (bool, uint64)) ( coins asset.Coins, fundingCoins map[outPoint]*utxo, spents []*output, redeemScripts []dex.Bytes, size, sum uint64, err error) { btc.fundingMtx.Lock() @@ -2343,13 +2399,13 @@ func (btc *baseWallet) fund(keep uint64, minConfs uint32, lockUnspents bool, } func tryFund(utxos []*compositeUTXO, - enough func(uint64, uint64) (bool, uint64)) ( + enough func(uint64, uint64, uint64) (bool, uint64)) ( sum, extra, size uint64, coins asset.Coins, fundingCoins map[outPoint]*utxo, redeemScripts []dex.Bytes, spents []*output, err error) { fundingCoins = make(map[outPoint]*utxo) isEnoughWith := func(unspent *compositeUTXO) bool { - ok, _ := enough(size+uint64(unspent.input.VBytes()), sum+unspent.amount) + ok, _ := enough(uint64(len(fundingCoins)+1), size+uint64(unspent.input.VBytes()), sum+unspent.amount) return ok } @@ -2397,7 +2453,7 @@ func tryFund(utxos []*compositeUTXO, // No need to check idx == len(okUTXOs). We already verified that the last // utxo passes above. addUTXO(okUTXOs[idx]) - _, extra = enough(size, sum) + _, extra = enough(uint64(len(fundingCoins)), size, sum) return true } } @@ -2451,7 +2507,7 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*output, input // Calculate the extra fees associated with the additional inputs, outputs, // and transaction overhead, and compare to the excess that would be locked. - swapInputSize, baggage := btc.splitBaggageFees(bumpedMaxRate, extraOutput > 0) + swapInputSize, baggage := btc.splitTxFees(uint64(len(fundingCoins)), inputsSize, bumpedMaxRate, extraOutput > 0, btc.segwit) var coinSum uint64 coins := make(asset.Coins, 0, len(outputs)) @@ -2461,8 +2517,9 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*output, input } valueStr := amount(value).String() + inputCount := uint64(len(fundingCoins)) - excess := coinSum - calc.RequiredOrderFundsAlt(value, inputsSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) + excess := coinSum - btc.orderReq(value, inputCount, inputsSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) if baggage > excess { btc.log.Debugf("Skipping split transaction because cost is greater than potential over-lock. "+ "%s > %s", amount(baggage), amount(excess)) @@ -2481,7 +2538,7 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*output, input return nil, false, fmt.Errorf("failed to stringify the change address: %w", err) } - reqFunds := calc.RequiredOrderFundsAlt(value, swapInputSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) + reqFunds := btc.orderReq(value, 1, swapInputSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) baseTx, _, _, err := btc.fundedTx(coins) splitScript, err := txscript.PayToAddrScript(addr) @@ -2541,9 +2598,9 @@ func (btc *baseWallet) split(value uint64, lots uint64, outputs []*output, input return asset.Coins{op}, true, nil } -// splitBaggageFees is the fees associated with adding a split transaction. -func (btc *baseWallet) splitBaggageFees(maxFeeRate uint64, extraOutput bool) (swapInputSize, baggage uint64) { - if btc.segwit { +// splitTxFees is the fees associated with adding a split transaction. +func splitTxFees(maxFeeRate uint64, extraOutput bool, segwit bool) (swapInputSize, baggage uint64) { + if segwit { baggage = maxFeeRate * splitTxBaggageSegwit if extraOutput { baggage += maxFeeRate * dexbtc.P2WPKHOutputSize @@ -2979,7 +3036,7 @@ func (btc *baseWallet) signedAccelerationTx(previousTxs []*GetTransactionResult, var additionalInputs asset.Coins if fundsRequired > orderChange.value { // If change not enough, need to use other UTXOs. - enough := func(inputSize, inputsVal uint64) (bool, uint64) { + enough := func(inputCount, inputSize, inputsVal uint64) (bool, uint64) { txSize := dexbtc.MinimumTxOverhead + inputSize // add the order change as an input @@ -3560,7 +3617,8 @@ func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, ui func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) { // Create a transaction that spends the referenced contract. msgTx := wire.NewMsgTx(btc.txVersion()) - var totalIn uint64 + baseTxSize := btc.calcTxSize(msgTx) + var totalIn, inputsSize uint64 contracts := make([][]byte, 0, len(form.Redemptions)) prevScripts := make([][]byte, 0, len(form.Redemptions)) addresses := make([]btcutil.Address, 0, len(form.Redemptions)) @@ -3594,19 +3652,19 @@ func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, addresses = append(addresses, receiver) contracts = append(contracts, contract) txIn := wire.NewTxIn(cinfo.output.wireOutPoint(), nil, nil) + inputsSize += uint64(txIn.SerializeSize()) msgTx.AddTxIn(txIn) values = append(values, int64(cinfo.output.value)) totalIn += cinfo.output.value } // Calculate the size and the fees. - size := btc.calcTxSize(msgTx) if btc.segwit { // Add the marker and flag weight here. witnessVBytes := (dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + 2 + 3) / 4 - size += witnessVBytes + dexbtc.P2WPKHOutputSize + baseTxSize += witnessVBytes + dexbtc.P2WPKHOutputSize } else { - size += dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + dexbtc.P2PKHOutputSize + baseTxSize += dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + dexbtc.P2PKHOutputSize } customCfg := new(redeemOptions) @@ -3620,11 +3678,11 @@ func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, if err != nil { btc.log.Errorf("calcBumpRate error: %v", err) } - fee := feeRate * size + fee := btc.txFees(baseTxSize, uint64(len(form.Redemptions)), inputsSize, feeRate) if fee > totalIn { // Double check that the fee bump isn't the issue. feeRate = rawFeeRate - fee = feeRate * size + fee = btc.txFees(baseTxSize, uint64(len(form.Redemptions)), inputsSize, feeRate) if fee > totalIn { return nil, nil, 0, fmt.Errorf("redeem tx not worth the fees") } @@ -4170,6 +4228,7 @@ func (btc *baseWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract de // Create the transaction that spends the contract. msgTx := wire.NewMsgTx(btc.txVersion()) + baseTxSize := btc.calcTxSize(msgTx) msgTx.LockTime = uint32(lockTime) prevOut := wire.NewOutPoint(txHash, vout) txIn := wire.NewTxIn(prevOut, []byte{}, nil) @@ -4177,20 +4236,18 @@ func (btc *baseWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract de // // https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#Spending_wallet_policy txIn.Sequence = wire.MaxTxInSequenceNum - 1 + inputsSize := uint64(txIn.SerializeSize()) msgTx.AddTxIn(txIn) // Calculate fees and add the change output. - - size := btc.calcTxSize(msgTx) - if btc.segwit { // Add the marker and flag weight too. witnessVBytes := uint64((dexbtc.RefundSigScriptSize + 2 + 3) / 4) - size += witnessVBytes + dexbtc.P2WPKHOutputSize + baseTxSize += witnessVBytes + dexbtc.P2WPKHOutputSize } else { - size += dexbtc.RefundSigScriptSize + dexbtc.P2PKHOutputSize + baseTxSize += dexbtc.RefundSigScriptSize + dexbtc.P2PKHOutputSize } - fee := feeRate * size // TODO: use btc.FeeRate in caller and fallback to nfo.MaxFeeRate + fee := btc.txFees(baseTxSize, 1, inputsSize, feeRate) // TODO: use btc.FeeRate in caller and fallback to nfo.MaxFeeRate if fee > val { return nil, fmt.Errorf("refund tx not worth the fees") } @@ -4348,7 +4405,7 @@ func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract btc.fundingMtx.Lock() defer btc.fundingMtx.Unlock() - enough := sendEnough(val, feeRate, subtract, uint64(baseSize), btc.segwit, true) + enough := btc.sendEnough(val, feeRate, subtract, uint64(baseSize), btc.segwit, true) minConfs := uint32(0) coins, _, _, _, inputsSize, _, err := btc.fundInternal(btc.reserves(), minConfs, false, enough) if err != nil { @@ -4768,7 +4825,24 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) } vSize := btc.calcTxSize(msgTx) - minFee := feeRate * vSize + + sumInputSizes := func(msgTx *wire.MsgTx) (inputsSize uint64) { + for _, txIn := range msgTx.TxIn { + if len(txIn.Witness) > 0 { + const witnessWeight = 4 + witnessSize := uint64(txIn.Witness.SerializeSize()) + inputsSize += dexbtc.TxInOverhead + (witnessSize+(witnessWeight-1))/witnessWeight + } else { + inputsSize += uint64(txIn.SerializeSize()) + } + } + return + } + inCount := uint64(len(msgTx.TxIn)) + inputsSize := sumInputSizes(msgTx) + baseTxSizeWithoutInputs := vSize - inputsSize + + minFee := btc.txFees(baseTxSizeWithoutInputs, inCount, inputsSize, feeRate) // feeRate * vSize remaining := totalIn - totalOut if minFee > remaining { return makeErr("not enough funds to cover minimum fee rate. %.8f < %.8f, raw tx: %x", @@ -4780,10 +4854,14 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre if err != nil { return makeErr("error creating change script: %v", err) } - changeFees := dexbtc.P2PKHOutputSize * feeRate + + var changeSize uint64 = dexbtc.P2PKHOutputSize if btc.segwit { - changeFees = dexbtc.P2WPKHOutputSize * feeRate + changeSize = dexbtc.P2WPKHOutputSize } + feesWithChange := btc.txFees(baseTxSizeWithoutInputs+changeSize, inCount, inputsSize, feeRate) + changeFees := feesWithChange - minFee + changeIdx := len(baseTx.TxOut) changeOutput := wire.NewTxOut(int64(remaining-minFee-changeFees), changeScript) if changeFees+minFee > remaining { // Prevent underflow @@ -4801,7 +4879,8 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre btc.log.Debugf("Change output size = %d, addr = %s", changeSize, addrStr) vSize += changeSize - fee := feeRate * vSize + inputsSize := sumInputSizes(msgTx) + fee := btc.txFees(vSize-inputsSize, inCount, inputsSize, feeRate) changeOutput.Value = int64(remaining - fee) // Find the best fee rate by closing in on it in a loop. tried := map[uint64]bool{} @@ -4813,7 +4892,8 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) } vSize = btc.calcTxSize(msgTx) // recompute the size with new tx signature - reqFee := feeRate * vSize + inputsSize := sumInputSizes(msgTx) + reqFee := btc.txFees(vSize-inputsSize, inCount, inputsSize, feeRate) if reqFee > remaining { // I can't imagine a scenario where this condition would be true, but // I'd hate to be wrong. @@ -4951,11 +5031,10 @@ var dummyP2PKHScript = []byte{0x76, 0xa9, 0x14, 0xe4, 0x28, 0x61, 0xa, // EstimateSendTxFee returns a tx fee estimate for sending or withdrawing the // provided amount using the provided feeRate. -func (btc *intermediaryWallet) EstimateSendTxFee(address string, sendAmount, feeRate uint64, subtract bool) (fee uint64, isValidAddress bool, err error) { +func (btc *baseWallet) EstimateSendTxFee(address string, sendAmount, feeRate uint64, subtract bool) (fee uint64, isValidAddress bool, err error) { if sendAmount == 0 { return 0, false, fmt.Errorf("cannot check fee: send amount = 0") } - var pkScript []byte if addr, err := btc.decodeAddr(address, btc.chainParams); err == nil { pkScript, err = txscript.PayToAddrScript(addr) @@ -4969,13 +5048,19 @@ func (btc *intermediaryWallet) EstimateSendTxFee(address string, sendAmount, fee } wireOP := wire.NewTxOut(int64(sendAmount), pkScript) - if dexbtc.IsDust(wireOP, feeRate) { + if btc.IsDust(wireOP, feeRate) { return 0, false, errors.New("output value is dust") } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxOut(wireOP) - fee, err = btc.txFeeEstimator.estimateSendTxFee(tx, btc.feeRateWithFallback(feeRate), subtract) + + estimator, is := btc.node.(txFeeEstimator) + if btc.manualSendEst || !is { + estimator = btc + } + + fee, err = estimator.estimateSendTxFee(tx, btc.feeRateWithFallback(feeRate), subtract) if err != nil { return 0, false, err } @@ -5225,7 +5310,7 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time return nil, nil, fmt.Errorf("error constructing p2sh script: %v", err) } txOut := wire.NewTxOut(int64(amt), pkScript) - if dexbtc.IsDust(txOut, feeRate) { + if btc.IsDust(txOut, feeRate) { return nil, nil, fmt.Errorf("bond output value of %d is dust", amt) } baseTx.AddTxOut(txOut) @@ -5259,7 +5344,7 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time baseSize += dexbtc.P2PKHOutputSize } - coins, _, _, _, _, _, err := btc.fund(0, 0, true, sendEnough(amt, feeRate, true, uint64(baseSize), btc.segwit, true)) + coins, _, _, _, _, _, err := btc.fund(0, 0, true, btc.sendEnough(amt, feeRate, true, uint64(baseSize), btc.segwit, true)) if err != nil { return nil, nil, fmt.Errorf("failed to fund bond tx: %w", err) } @@ -5383,7 +5468,7 @@ func (btc *baseWallet) makeBondRefundTxV0(txid *chainhash.Hash, vout uint32, amt return nil, fmt.Errorf("error creating pubkey script: %w", err) } redeemTxOut := wire.NewTxOut(int64(amt-fee), redeemPkScript) - if dexbtc.IsDust(redeemTxOut, feeRate) { // hard to imagine + if btc.IsDust(redeemTxOut, feeRate) { // hard to imagine return nil, fmt.Errorf("redeem output is dust") } msgTx.AddTxOut(redeemTxOut) diff --git a/client/asset/btc/btc_test.go b/client/asset/btc/btc_test.go index fa62494179..552cb626f2 100644 --- a/client/asset/btc/btc_test.go +++ b/client/asset/btc/btc_test.go @@ -669,9 +669,8 @@ func tNewWallet(segwit bool, walletType string) (*intermediaryWallet, *testData, } w.node = spvw wallet = &intermediaryWallet{ - baseWallet: w, - txFeeEstimator: spvw, - tipRedeemer: spvw, + baseWallet: w, + tipRedeemer: spvw, } } } diff --git a/client/asset/btc/coin_selection.go b/client/asset/btc/coin_selection.go index 7b65043362..01cbee2c05 100644 --- a/client/asset/btc/coin_selection.go +++ b/client/asset/btc/coin_selection.go @@ -8,10 +8,13 @@ import ( "sort" "time" - "decred.org/dcrdex/dex/calc" dexbtc "decred.org/dcrdex/dex/networks/btc" ) +func (btc *baseWallet) sendEnough(amt, feeRate uint64, subtract bool, baseTxSize uint64, segwit, reportChange bool) func(inputCount, inputSize, sum uint64) (bool, uint64) { + return sendEnough(amt, feeRate, subtract, baseTxSize, segwit, reportChange, btc.isDust, btc.txFees) +} + // sendEnough generates a function that can be used as the enough argument to // the fund method when creating transactions to send funds. If fees are to be // subtracted from the inputs, set subtract so that the required amount excludes @@ -20,9 +23,9 @@ import ( // enough func will return a non-zero excess value. Otherwise, the enough func // will always return 0, leaving only unselected UTXOs to cover any required // reserves. -func sendEnough(amt, feeRate uint64, subtract bool, baseTxSize uint64, segwit, reportChange bool) func(inputSize, sum uint64) (bool, uint64) { - return func(inputSize, sum uint64) (bool, uint64) { - txFee := (baseTxSize + inputSize) * feeRate +func sendEnough(amt, feeRate uint64, subtract bool, baseTxSize uint64, segwit, reportChange bool, isDust DustRater, sendFees TxFeesCalculator) func(inputCount, inputSize, sum uint64) (bool, uint64) { + return func(inputCount, inputSize, sum uint64) (bool, uint64) { + txFee := sendFees(baseTxSize, inputCount, inputSize, feeRate) req := amt if !subtract { // add the fee to required req += txFee @@ -31,25 +34,29 @@ func sendEnough(amt, feeRate uint64, subtract bool, baseTxSize uint64, segwit, r return false, 0 } excess := sum - req - if !reportChange || dexbtc.IsDustVal(dexbtc.P2PKHOutputSize, excess, feeRate, segwit) { + if !reportChange || isDust(dexbtc.P2PKHOutputSize, excess, feeRate, segwit) { excess = 0 } return true, excess } } +func (btc *baseWallet) orderEnough(val, lots, feeRate, initTxSizeBase, initTxSize uint64, segwit, reportChange bool) func(inputCount, inputSize, sum uint64) (bool, uint64) { + return orderEnough(val, lots, feeRate, initTxSizeBase, initTxSize, segwit, reportChange, btc.orderReq, btc.isDust) +} + // orderEnough generates a function that can be used as the enough argument to // the fund method. If change from a split transaction will be created AND // immediately available, set reportChange to indicate this and the returned // enough func will return a non-zero excess value reflecting this potential // spit tx change. Otherwise, the enough func will always return 0, leaving // only unselected UTXOs to cover any required reserves. -func orderEnough(val, lots, feeRate, initTxSizeBase, initTxSize uint64, segwit, reportChange bool) func(inputsSize, sum uint64) (bool, uint64) { - return func(inputsSize, sum uint64) (bool, uint64) { - reqFunds := calc.RequiredOrderFundsAlt(val, inputsSize, lots, initTxSizeBase, initTxSize, feeRate) +func orderEnough(val, lots, feeRate, initTxSizeBase, initTxSize uint64, segwit, reportChange bool, orderReq OrderEstimator, isDust DustRater) func(inputCount, inputsSize, sum uint64) (bool, uint64) { + return func(inputCount, inputsSize, sum uint64) (bool, uint64) { + reqFunds := orderReq(val, inputCount, inputsSize, lots, initTxSizeBase, initTxSize, feeRate) if sum >= reqFunds { excess := sum - reqFunds - if !reportChange || dexbtc.IsDustVal(dexbtc.P2PKHOutputSize, excess, feeRate, segwit) { + if !reportChange || isDust(dexbtc.P2PKHOutputSize, excess, feeRate, segwit) { excess = 0 } return true, excess diff --git a/client/asset/btc/rpcclient.go b/client/asset/btc/rpcclient.go index bcb00cd62c..b25809d108 100644 --- a/client/asset/btc/rpcclient.go +++ b/client/asset/btc/rpcclient.go @@ -812,7 +812,7 @@ func (wc *rpcClient) locked() bool { return time.Unix(*walletInfo.UnlockedUntil, 0).Before(time.Now()) } -// sendTxFeeEstimator returns the fee required to send tx using the provided +// estimateSendTxFee returns the fee required to send tx using the provided // feeRate. func (wc *rpcClient) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (txfee uint64, err error) { txBytes, err := wc.serializeTx(tx) diff --git a/client/asset/btc/simnet_test.go b/client/asset/btc/simnet_test.go index 61c9be8bce..1b2d7a9c0c 100644 --- a/client/asset/btc/simnet_test.go +++ b/client/asset/btc/simnet_test.go @@ -21,6 +21,9 @@ import ( "decred.org/dcrdex/dex/config" dexbtc "decred.org/dcrdex/dex/networks/btc" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -61,6 +64,7 @@ func tBackend(t *testing.T, name string, blkFunc func(string, error)) (*Exchange } // settings["account"] = "default" walletCfg := &asset.WalletConfig{ + Type: walletTypeRPC, Settings: settings, TipChange: func(err error) { blkFunc(name, err) @@ -236,3 +240,36 @@ func TestMakeBondTx(t *testing.T) { } t.Logf("refundCoin: %v\n", refundCoin) } + +func TestSendEstimation(t *testing.T) { + rig := newTestRig(t, func(name string, err error) { + tLogger.Infof("%s has reported a new block, error = %v", name, err) + }) + defer rig.close(t) + + addr, _ := btcutil.DecodeAddress("bcrt1qs6d2lpkcfccus6q7c0dvjnlpf5g45gf7yak6mm", &chaincfg.RegressionNetParams) + pkScript, _ := txscript.PayToAddrScript(addr) + tx := wire.NewMsgTx(wire.TxVersion) + tx.AddTxOut(wire.NewTxOut(10e8, pkScript)) + + // Use alpha, since there are many utxos. + w := rig.alpha() + const numCycles = 100 + tStart := time.Now() + for i := 0; i < numCycles; i++ { + _, err := w.estimateSendTxFee(tx, 20, false) + if err != nil { + t.Fatalf("Error estimating with utxos: %v", err) + } + } + fmt.Println("Time to pick utxos ourselves:", time.Since(tStart)) + node := w.node.(*rpcClient) + tStart = time.Now() + for i := 0; i < numCycles; i++ { + _, err := node.estimateSendTxFee(tx, 20, false) + if err != nil { + t.Fatalf("Error estimating with utxos: %v", err) + } + } + fmt.Println("Time to use fundrawtransaction:", time.Since(tStart)) +} diff --git a/client/asset/btc/spv_wrapper.go b/client/asset/btc/spv_wrapper.go index 7febc69364..0647eadcb8 100644 --- a/client/asset/btc/spv_wrapper.go +++ b/client/asset/btc/spv_wrapper.go @@ -748,36 +748,43 @@ func (w *spvWallet) Lock() error { return nil } +// DRAFT NOTE: Move estimateSendTxFee out of spv_wrapper before merge. Leaving +// for reviewability. + // estimateSendTxFee callers should provide at least one output value. -func (w *spvWallet) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (fee uint64, err error) { +func (btc *baseWallet) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract bool) (fee uint64, err error) { minTxSize := uint64(tx.SerializeSize()) var sendAmount uint64 for _, txOut := range tx.TxOut { sendAmount += uint64(txOut.Value) } - unspents, err := w.listUnspent() + unspents, err := btc.node.listUnspent() if err != nil { return 0, fmt.Errorf("error listing unspent outputs: %w", err) } - utxos, _, _, err := convertUnspent(0, unspents, w.chainParams) + utxos, _, _, err := convertUnspent(0, unspents, btc.chainParams) if err != nil { return 0, fmt.Errorf("error converting unspent outputs: %w", err) } - enough := sendEnough(sendAmount, feeRate, subtract, minTxSize, true, false) - sum, _, inputsSize, _, _, _, _, err := tryFund(utxos, enough) + enough := btc.sendEnough(sendAmount, feeRate, subtract, minTxSize, true, false) + sum, _, inputsSize, coins, _, _, _, err := tryFund(utxos, enough) if err != nil { return 0, err } - txSize := minTxSize + inputsSize - estFee := feeRate * txSize + estFee := btc.txFees(minTxSize, uint64(len(coins)), inputsSize, feeRate) remaining := sum - sendAmount + var opSize uint64 = dexbtc.P2PKHOutputSize + if btc.segwit { + opSize = dexbtc.P2WPKHOutputSize + } + // Check if there will be a change output if there is enough remaining. - estFeeWithChange := (txSize + dexbtc.P2WPKHOutputSize) * feeRate + estFeeWithChange := btc.txFees(minTxSize+opSize, uint64(len(coins)), inputsSize, feeRate) var changeValue uint64 if remaining > estFeeWithChange { changeValue = remaining - estFeeWithChange @@ -789,7 +796,7 @@ func (w *spvWallet) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract b } var finalFee uint64 - if dexbtc.IsDustVal(dexbtc.P2WPKHOutputSize, changeValue, feeRate, true) { + if btc.isDust(opSize, changeValue, feeRate, true) { // remaining cannot cover a non-dust change and the fee for the change. finalFee = estFee + remaining } else { @@ -800,7 +807,7 @@ func (w *spvWallet) estimateSendTxFee(tx *wire.MsgTx, feeRate uint64, subtract b if subtract { sendAmount -= finalFee } - if dexbtc.IsDustVal(minTxSize, sendAmount, feeRate, true) { + if btc.isDust(minTxSize, sendAmount, feeRate, true) { return 0, errors.New("output value is dust") } diff --git a/client/asset/zec/shielded_rpc.go b/client/asset/zec/shielded_rpc.go index 3d77e61d5e..16505e7a42 100644 --- a/client/asset/zec/shielded_rpc.go +++ b/client/asset/zec/shielded_rpc.go @@ -123,7 +123,8 @@ const ( // z_sendmany "fromaddress" [{"address":... ,"amount":...},...] ( minconf ) ( fee ) ( privacyPolicy ) func zSendMany(c rpcCaller, fromAddress string, recips []*zSendManyRecipient, priv privacyPolicy) (operationID string, err error) { - const minConf, fee = 1, 0.00001 + const minConf = 1 + var fee *uint64 // Only makes sense with >= 5.5.0 return operationID, c.CallRPC(methodZSendMany, []interface{}{fromAddress, recips, minConf, fee, priv}, &operationID) } diff --git a/client/asset/zec/zec.go b/client/asset/zec/zec.go index 9d6fe09ddf..9f4d23e35a 100644 --- a/client/asset/zec/zec.go +++ b/client/asset/zec/zec.go @@ -35,7 +35,7 @@ const ( // structure. defaultFee = 10 defaultFeeRateLimit = 1000 - minNetworkVersion = 5040250 // v5.4.2 + minNetworkVersion = 5050050 // v5.5.0 walletTypeRPC = "zcashdRPC" transparentAcctNumber = 0 @@ -210,7 +210,17 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (ass return dexzec.EncodeAddress(addr, addrParams) }, TxSizeCalculator: dexzec.CalcTxSize, - NonSegwitSigner: signTx, + DustRater: func(outputSize, value, minRelayTxFee uint64, segwit bool) bool { + return isDust(value, outputSize) + }, + OrderEstimator: func(swapVal, inputCount, inputsSize, maxSwaps, swapSizeBase, _, _ uint64) uint64 { + return dexzec.RequiredOrderFunds(swapVal, inputCount, inputsSize, maxSwaps) + }, + TxFeesCalculator: func(baseTxSize, inputCount, inputsSize, _ uint64) uint64 { + return txFees(baseTxSize, inputCount, inputsSize) + }, + SplitFeeCalculator: splitTxFees, + NonSegwitSigner: signTx, TxDeserializer: func(b []byte) (*wire.MsgTx, error) { zecTx, err := dexzec.DeserializeTx(b) if err != nil { @@ -259,6 +269,44 @@ type zecWallet struct { lastAddress atomic.Value // "string" } +func (w *zecWallet) FeeRate(context.Context) (uint64, error) { + return 10, nil +} + +func (w *zecWallet) PreSwap(req *asset.PreSwapForm) (*asset.PreSwap, error) { + est, err := w.ExchangeWalletNoAuth.PreSwap(req) + if err != nil { + return nil, err + } + // We need to strip out the fee bump option. + const swapFeeBumpKey = "swapfeebump" + opts := make([]*asset.OrderOption, 0, len(est.Options)) + for _, opt := range est.Options { + if opt.Key == swapFeeBumpKey { + continue + } + opts = append(opts, opt) + } + est.Options = opts + return est, nil +} + +func (w *zecWallet) PreRedeem(req *asset.PreRedeemForm) (*asset.PreRedeem, error) { + var singleTxInsSize uint64 = dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize + 1 + var txOutsSize uint64 = dexbtc.P2PKHOutputSize + 1 + worstCaseFees := dexzec.TransparentTxFeesZIP317(singleTxInsSize, txOutsSize) * req.Lots + + multiTxInsSize := dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize*req.Lots + uint64(wire.VarIntSerializeSize(req.Lots)) + bestCaseFees := dexzec.TransparentTxFeesZIP317(multiTxInsSize, txOutsSize) * req.Lots + return &asset.PreRedeem{ + Estimate: &asset.RedeemEstimate{ + RealisticWorstCase: worstCaseFees, + RealisticBestCase: bestCaseFees, + }, + Options: []*asset.OrderOption{}, + }, nil +} + var _ asset.ShieldedWallet = (*zecWallet)(nil) func transparentAddress(c rpcCaller, addrParams *dexzec.AddressParams, btcParams *chaincfg.Params) (btcutil.Address, error) { @@ -600,3 +648,38 @@ func signTx(btcTx *wire.MsgTx, idx int, pkScript []byte, hashType txscript.SigHa return append(ecdsa.Sign(key, sigHash[:]).Serialize(), byte(hashType)), nil } + +// isDust returns true if the output will be rejected as dust. +func isDust(val, outputSize uint64) bool { + // See https://github.com/zcash/zcash/blob/5066efbb98bc2af5eed201212d27c77993950cee/src/primitives/transaction.h#L630 + // https://github.com/zcash/zcash/blob/5066efbb98bc2af5eed201212d27c77993950cee/src/primitives/transaction.cpp#L127 + // Also see informative comments hinting towards future changes at + // https://github.com/zcash/zcash/blob/master/src/policy/policy.h + sz := outputSize + 148 // 148 accounts for an input on spending tx + const oneThirdDustThresholdRate = 100 // zats / kB + nFee := oneThirdDustThresholdRate * sz / 1000 // This is different from BTC + if nFee == 0 { + nFee = oneThirdDustThresholdRate + } + return val < 3*nFee +} + +var emptyTxSize = dexzec.CalcTxSize(new(wire.MsgTx)) + +// txFees is the tx fees for a basic single-output send with change. +func txFees(baseTxSize, inputCount, inputsSize uint64) uint64 { + txInSize := inputsSize + uint64(wire.VarIntSerializeSize(inputCount)) + outputsSize := baseTxSize - emptyTxSize + txOutSize := outputsSize + 1 // Assumes < 253 outputs + return dexzec.TransparentTxFeesZIP317(txInSize, txOutSize) +} + +func splitTxFees(inputCount, inputsSize, _ uint64, extraOutput, _ bool) (swapInputSize, baggage uint64) { + txInsSize := inputsSize + uint64(wire.VarIntSerializeSize(inputCount)) + var numOutputs uint64 = 2 + if extraOutput { + numOutputs = 3 + } + txOutsSize := dexbtc.P2PKHOutputSize*numOutputs + uint64(wire.VarIntSerializeSize(numOutputs)) + return dexbtc.RedeemP2PKHInputSize, dexzec.TransparentTxFeesZIP317(txInsSize, txOutsSize) +} diff --git a/dex/networks/zec/script.go b/dex/networks/zec/script.go new file mode 100644 index 0000000000..c3345fb904 --- /dev/null +++ b/dex/networks/zec/script.go @@ -0,0 +1,59 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org + +package zec + +import ( + "math" + + dexbtc "decred.org/dcrdex/dex/networks/btc" + "github.com/btcsuite/btcd/wire" +) + +// TransparentTxFeesZIP317 calculates the ZIP-0317 fees for a fully transparent +// Zcash transaction, which only depends on the size of the tx_in and tx_out +// fields. +// https://github.com/decred/dcrdex/blob/master/docs/images/zip-0317.png +func TransparentTxFeesZIP317(txInSize, txOutSize uint64) uint64 { + return txFeesZIP317(txInSize, txOutSize, nil) +} + +// txFeexZIP317 calculates fees for a transaction. If the tx is +// fully-transparent, the shieldedTx argument can be nil. The caller must sum up +// the txin and txout, which is the entire serialization size associated with +// the respective field, including the size of the count varint. +func txFeesZIP317(transparentTxInsSize, transparentTxOutsSize uint64, shieldedTx *Tx) uint64 { + const ( + marginalFee = 5000 + graceActions = 2 + pkhStandardInputSize = 150 + pkhStandardOutputSize = 34 + ) + + nIn := math.Ceil(float64(transparentTxInsSize) / pkhStandardInputSize) + nOut := math.Ceil(float64(transparentTxOutsSize) / pkhStandardOutputSize) + + logicalActions := uint64(math.Max(nIn, nOut)) + if shieldedTx != nil { + nSapling := uint64(math.Max(float64(shieldedTx.NSpendsSapling), float64(shieldedTx.NOutputsSapling))) + logicalActions += 2*shieldedTx.NJoinSplit + nSapling + shieldedTx.NActionsOrchard + } + + return marginalFee * uint64(math.Max(graceActions, float64(logicalActions))) +} + +// RequiredOrderFunds is the ZIP-0317 compliant version of +// calc.RequiredOrderFunds. +func RequiredOrderFunds(swapVal, inputCount, inputsSize, maxSwaps uint64) uint64 { + // One p2sh output for the contract, 1 change output. + const txOutsSize = dexbtc.P2PKHOutputSize + dexbtc.P2SHOutputSize + 1 /* wire.VarIntSerializeSize(2) */ + txInsSize := inputsSize + uint64(wire.VarIntSerializeSize(inputCount)) + firstTxFees := TransparentTxFeesZIP317(txInsSize, txOutsSize) + if maxSwaps == 1 { + return swapVal + firstTxFees + } + + otherTxsFees := TransparentTxFeesZIP317(dexbtc.RedeemP2PKHInputSize+1, txOutsSize) + fees := firstTxFees + (maxSwaps-1)*otherTxsFees + return swapVal + fees +} diff --git a/dex/networks/zec/tx.go b/dex/networks/zec/tx.go index 8358179a47..81b00e1546 100644 --- a/dex/networks/zec/tx.go +++ b/dex/networks/zec/tx.go @@ -800,6 +800,20 @@ func (tx *Tx) SerializeSize() uint64 { return sz } +func (tx *Tx) TxFeesZIP317() uint64 { + txInsSize := uint64(wire.VarIntSerializeSize(uint64(len(tx.TxIn)))) + for _, txIn := range tx.TxIn { + txInsSize += uint64(txIn.SerializeSize()) + } + + txOutsSize := uint64(wire.VarIntSerializeSize(uint64(len(tx.TxOut)))) + for _, txOut := range tx.TxOut { + txOutsSize += uint64(txOut.SerializeSize()) + } + + return txFeesZIP317(txInsSize, txOutsSize, tx) +} + // writeTxIn encodes ti to the bitcoin protocol encoding for a transaction // input (TxIn) to w. func writeTxIn(w io.Writer, ti *wire.TxIn) error { diff --git a/docs/images/zip-0317.png b/docs/images/zip-0317.png new file mode 100644 index 0000000000000000000000000000000000000000..f4ba1cdc1137f3e83fa41c938e0e837ceefc5d25 GIT binary patch literal 122798 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:JzEWUb|Tfs3HrJ}c2#AF1_gDTZs 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 E2M%;Xvj6}9 literal 0 HcmV?d00001 diff --git a/docs/images/zip-0317.tex b/docs/images/zip-0317.tex new file mode 100644 index 0000000000..84d1e5fa27 --- /dev/null +++ b/docs/images/zip-0317.tex @@ -0,0 +1,20 @@ +\documentclass[]{article} +\begin{document} + +\[ +\begin{array}{rcl} + marginal\_fee &=& 5000 \\ + grace\_actions &=& 2 \\ + p2pkh\_standard\_input\_size &=& 150 \\ + p2pkh\_standard\_output\_size &=& 34 \\ + n\_transparent &=& \mathsf{max}\big(\mathsf{ceiling}\big(\frac{tx\_in\_total\_size}{p2pkh\_standard\_input\_size}\big), + \mathsf{ceiling}\big(\frac{tx\_out\_total\_size}{p2pkh\_standard\_output\_size}\big)\big) \\ + logical\_actions &=& n\_transparent \;+ \\ + & & 2 \cdot nJoinSplit \;+ \\ + & & \mathsf{max}(nSpendsSapling, nOutputsSapling) \;+ \\ + & & nActionsOrchard \\ + conventional\_fee &=& marginal\_fee \cdot \mathsf{max}(grace\_actions, logical\_actions) +\end{array} +\] + +\end{document} diff --git a/server/asset/btc/btc.go b/server/asset/btc/btc.go index fa7394326a..ba69129e24 100644 --- a/server/asset/btc/btc.go +++ b/server/asset/btc/btc.go @@ -1154,17 +1154,18 @@ func (btc *Backend) transaction(txHash *chainhash.Hash, verboseTx *VerboseTxExte } var feeRate uint64 + fees := sumIn - sumOut if btc.segwit { if verboseTx.Vsize > 0 { - feeRate = (sumIn - sumOut) / uint64(verboseTx.Vsize) + feeRate = fees / uint64(verboseTx.Vsize) } } else if verboseTx.Size > 0 { // For non-segwit transactions, Size = Vsize anyway, so use Size to // cover assets that won't set Vsize in their RPC response. - feeRate = (sumIn - sumOut) / uint64(verboseTx.Size) + feeRate = fees / uint64(verboseTx.Size) } - return newTransaction(btc, txHash, blockHash, lastLookup, blockHeight, isCoinbase, inputs, outputs, feeRate, rawTx), nil + return newTransaction(btc, txHash, blockHash, lastLookup, blockHeight, isCoinbase, inputs, outputs, fees, feeRate, rawTx), nil } // Get information for an unspent transaction output and it's transaction. diff --git a/server/asset/btc/tx.go b/server/asset/btc/tx.go index d0b05b6855..b61fda61ce 100644 --- a/server/asset/btc/tx.go +++ b/server/asset/btc/tx.go @@ -26,6 +26,9 @@ type Tx struct { // Used to conditionally skip block lookups on mempool transactions during // calls to Confirmations. lastLookup *chainhash.Hash + // fees is the fees paid in the tx. fees is used Zcash. It is exposed by the + // (*TXIO).Fees which is not part of the asset.Coin interface. + fees uint64 // The calculated transaction fee rate, in satoshis/vbyte feeRate uint64 // raw is the raw tx bytes. @@ -48,7 +51,7 @@ type txOut struct { // A getter for a new Tx. func newTransaction(btc *Backend, txHash, blockHash, lastLookup *chainhash.Hash, - blockHeight int64, isCoinbase bool, ins []txIn, outs []txOut, feeRate uint64, rawTx []byte) *Tx { + blockHeight int64, isCoinbase bool, ins []txIn, outs []txOut, fees, feeRate uint64, rawTx []byte) *Tx { // Set a nil blockHash to the zero hash. hash := blockHash if hash == nil { @@ -63,6 +66,7 @@ func newTransaction(btc *Backend, txHash, blockHash, lastLookup *chainhash.Hash, outs: outs, isCoinbase: isCoinbase, lastLookup: lastLookup, + fees: fees, feeRate: feeRate, raw: rawTx, } diff --git a/server/asset/btc/utxo.go b/server/asset/btc/utxo.go index a1deada9fd..78b9d18e09 100644 --- a/server/asset/btc/utxo.go +++ b/server/asset/btc/utxo.go @@ -99,6 +99,12 @@ func (txio *TXIO) FeeRate() uint64 { return txio.tx.feeRate } +// Fees is the total fees paid for the tx containing the TXIO. Fees is not a +// required method for asset.Coin, but is used by Zcash for fee validation. +func (txio *TXIO) Fees() uint64 { + return txio.tx.fees +} + // Input is a transaction input. type Input struct { TXIO diff --git a/server/asset/common.go b/server/asset/common.go index 2d6ad8451d..8957b88314 100644 --- a/server/asset/common.go +++ b/server/asset/common.go @@ -123,6 +123,13 @@ type TokenBacker interface { TokenBackend(assetID uint32, configPath string) (Backend, error) } +// OrderEstimator estimate the funds required for an order of a particular size. +// If OrderEstimator is not implemented, caller should use +// calc.RequiredOrderFunds. Never used for account-based assets. +type OrderEstimator interface { + CalcOrderFunds(swapVal, inputsCount, inputsSize, maxSwaps uint64) uint64 +} + // Coin represents a transaction input or output. type Coin interface { // Confirmations returns the number of confirmations for a Coin's diff --git a/server/asset/zec/zec.go b/server/asset/zec/zec.go index 863a88f27f..12ca44600f 100644 --- a/server/asset/zec/zec.go +++ b/server/asset/zec/zec.go @@ -4,6 +4,7 @@ package zec import ( + "context" "encoding/hex" "fmt" "math" @@ -121,6 +122,7 @@ func NewBackend(configPath string, logger dex.Logger, network dex.Network) (asse Backend: be, addrParams: addrParams, btcParams: btcParams, + log: logger.SubLogger("ZEC"), }, nil } @@ -130,6 +132,7 @@ type ZECBackend struct { *btc.Backend btcParams *chaincfg.Params addrParams *dexzec.AddressParams + log dex.Logger } // Contract returns the output from embedded Backend's Contract method, but @@ -148,6 +151,39 @@ func (be *ZECBackend) Contract(coinID []byte, redeemScript []byte) (*asset.Contr return contract, nil } +func (be *ZECBackend) FeeRate(context.Context) (uint64, error) { + return 10, nil +} + +func (be *ZECBackend) ValidateFeeRate(contract *asset.Contract, reqFeeRate uint64) bool { + // Fees method is implemented by btc.TXIO especially for use of Zcash, since + // Zcash has a unique fee calculation, and therefore special fee validation + // rules. See ZIP-0317. + fr, is := contract.Coin.(interface { + Fees() uint64 + }) + if !is { + be.log.Errorf("supplied contract coin is not a FeeReporter") + return false + } + + fees := fr.Fees() + + zecTx, err := dexzec.DeserializeTx(contract.TxData) + if err != nil { + be.log.Errorf("error deserializing contract %q tx bytes for fee validation: %w", contract.SecretHash, err) + return false + } + + return fees >= zecTx.TxFeesZIP317() +} + +// CalcOrderFunds is the ZIP-0317 compliant version of calc.RequiredOrderFunds. +// Satisfies the asset.OrderEstimator interface. +func (be *ZECBackend) CalcOrderFunds(swapVal, inputCount, inputsSize, maxSwaps uint64) uint64 { + return dexzec.RequiredOrderFunds(swapVal, inputCount, inputsSize, maxSwaps) +} + func blockFeeTransactions(rc *btc.RPCClient, blockHash *chainhash.Hash) (feeTxs []btc.FeeTx, prevBlock chainhash.Hash, err error) { blockB, err := rc.GetRawBlock(blockHash) if err != nil { diff --git a/server/market/orderrouter.go b/server/market/orderrouter.go index 614a8f0404..be73a67ff4 100644 --- a/server/market/orderrouter.go +++ b/server/market/orderrouter.go @@ -396,6 +396,16 @@ func (r *OrderRouter) handleMarket(user account.AccountID, msg *msgjson.Message) return r.processTrade(oRecord, tunnel, assets, market.Coins, sell, 0, market.RedeemSig, market.Serialize()) } +// orderReq calculates the require balance for a utxo-based funding asset to +// support an order of the specified size. +func (r *OrderRouter) orderReq(be asset.Backend, swapVal, inputsCount, inputsSize, maxSwaps uint64, nfo *dex.Asset) uint64 { + if oe, is := be.(asset.OrderEstimator); is { + return oe.CalcOrderFunds(swapVal, inputsCount, inputsSize, maxSwaps) + } else { + return calc.RequiredOrderFunds(swapVal, inputsSize, maxSwaps, nfo) + } +} + // processTrade checks that the trade is valid and submits it to the market. func (r *OrderRouter) processTrade(oRecord *orderRecord, tunnel MarketTunnel, assets *assetSet, coins []*msgjson.Coin, sell bool, rate uint64, redeemSig *msgjson.RedeemSig, sigMsg []byte) *msgjson.Error { @@ -550,13 +560,14 @@ func (r *OrderRouter) processTrade(oRecord *orderRecord, tunnel MarketTunnel, as } // Calculate the fees and check that the utxo sum is enough. + inputCount := uint64(len(coins)) var reqVal uint64 if sell { - reqVal = calc.RequiredOrderFunds(trade.Quantity, uint64(spendSize), lots, &fundingAsset.Asset) + reqVal = r.orderReq(fundingAsset.Backend, trade.Quantity, inputCount, uint64(spendSize), lots, &fundingAsset.Asset) } else { if rate > 0 { // limit buy quoteQty := calc.BaseToQuote(rate, trade.Quantity) - reqVal = calc.RequiredOrderFunds(quoteQty, uint64(spendSize), lots, &assets.quote.Asset) + reqVal = r.orderReq(fundingAsset.Backend, quoteQty, inputCount, uint64(spendSize), lots, &assets.quote.Asset) } else { // This is a market buy order, so the quantity gets special handling. // 1. The quantity is in units of the quote asset. @@ -572,7 +583,7 @@ func (r *OrderRouter) processTrade(oRecord *orderRecord, tunnel MarketTunnel, as errStr := fmt.Sprintf("order quantity does not satisfy market buy buffer. %d < %d. midGap = %d", trade.Quantity, minReq, midGap) return false, msgjson.NewError(msgjson.FundingError, errStr) } - reqVal = calc.RequiredOrderFunds(minReq, uint64(spendSize), 1, &assets.quote.Asset) + reqVal = r.orderReq(fundingAsset.Backend, minReq, inputCount, uint64(spendSize), 1, &assets.quote.Asset) } }