Skip to content

Commit

Permalink
docs and touchups
Browse files Browse the repository at this point in the history
  • Loading branch information
buck54321 committed May 9, 2023
1 parent c8041c7 commit 3a944c5
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 55 deletions.
58 changes: 39 additions & 19 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,22 +291,35 @@ func RPCConfigOpts(name, rpcPort string) []*asset.ConfigOption {
}
}

// OrderEstimator estimates the funding required for an order. A clone may
// specify a custom OrderEstimator via their BTCCloneCFG. Otherwise
// defaultOrderEstimator (calc.RequiredOrderFundsAlt) is used.
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)
}

// DustRater indicates whether an output is considered dust. A clone may specify
// a custom DustRater via their BTCCloneCFG. Otherwise, dexbtc.IsDustVal will
// be used.
type DustRater func(txSize, value, minRelayTxFee uint64, segwit bool) bool

var defaultDustRater = dexbtc.IsDustVal

// TxFeesCalculator calculates the fees for a transaction. The baseTxSize
// argument should be the tx size including outputs but without inputs. A clone
// may specify a custom TxFeesCalculator via their BTCCloneCFG. Otherwise,
// defaultTxFeesCalculator is used.
type TxFeesCalculator func(baseTxSize, inputCount, inputsSize, feeRate uint64) uint64

func defaultTxFeesCalculator(baseTxSize, _, inputsSize, feeRate uint64) uint64 {
return (baseTxSize + inputsSize) * feeRate
}

// SplitFeeCalculator calculates the fees associated with a split tx. A clone
// may specify a custom SplitFeeCalculator via their BTCCloneCFG. Otherwise,
// defaultSplitFeeCalculator is used.
type SplitFeeCalculator func(inputCount, inputsSize, maxFeeRate uint64, extraOutput, segwit bool) (swapInputSize, baggage uint64)

func defaultSplitFeeCalculator(_, _, maxFeeRate uint64, extraOutput bool, segwit bool) (swapInputSize, baggage uint64) {
Expand Down Expand Up @@ -418,10 +431,20 @@ 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
DustRater DustRater
OrderEstimator OrderEstimator
TxFeesCalculator TxFeesCalculator
TxSizeCalculator func(*wire.MsgTx) uint64
// DustRater is an optional function to calculate whether a tx output is
// dust. If not specified, dexbtc.IsDustVal will be used.
DustRater DustRater
// OrderEstimator is an optional function that will calculate the funding
// required for an order. In not spcified, calc.RequiredOrderFunds will be
// used.
OrderEstimator OrderEstimator
// TxFeesCalculator is an optional function that calculates fees for a tx.
// If not specified, the typical tx_size * fee_rate formula will be used.
TxFeesCalculator TxFeesCalculator
// SplitFeeCalculator is an optional function that specified the fees
// associated with a split tx. If not specified, a typical
// tx_size * fee_rate formula is used.
SplitFeeCalculator SplitFeeCalculator
// TxVersion is an optional function that returns a version to use for
// new transactions.
Expand Down Expand Up @@ -4848,22 +4871,19 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre
}
vSize := btc.calcTxSize(msgTx)

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())
}
inCount := uint64(len(msgTx.TxIn))
var 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

baseTxSizeWithoutInputs := vSize - inputsSize
minFee := btc.txFees(baseTxSizeWithoutInputs, inCount, inputsSize, feeRate) // feeRate * vSize
remaining := totalIn - totalOut
if minFee > remaining {
Expand Down Expand Up @@ -4901,7 +4921,6 @@ func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Addre
btc.log.Debugf("Change output size = %d, addr = %s", changeSize, addrStr)

vSize += changeSize
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.
Expand All @@ -4914,7 +4933,6 @@ 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
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
Expand Down Expand Up @@ -5077,6 +5095,8 @@ func (btc *baseWallet) EstimateSendTxFee(address string, sendAmount, feeRate uin
tx := wire.NewMsgTx(btc.txVersion())
tx.AddTxOut(wireOP)

// If the node has a better way, let them do it. Otherwise, we'll use our
// own method, which pulls all unspent outputs.
estimator, is := btc.node.(txFeeEstimator)
if btc.manualSendEst || !is {
estimator = btc
Expand Down
20 changes: 15 additions & 5 deletions client/asset/btc/simnet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,21 @@ func TestMakeBondTx(t *testing.T) {
t.Logf("refundCoin: %v\n", refundCoin)
}

func TestSendEstimation(t *testing.T) {
// TestCompareSendFeeEstimation compares our manual funding routine, which pulls
// all unspent outputs, to the rpcClient method that uses the rundrawtransaction
// rpc.
func TestCompareSendFeeEstimation(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)
const numCycles = 10
var manualTime, rpcTime time.Duration
defer func() {
rig.close(t)
fmt.Printf("%d cycles each\n", numCycles)
fmt.Println("Time to pick utxos ourselves:", manualTime)
fmt.Println("Time to use fundrawtransaction:", rpcTime)
}()

addr, _ := btcutil.DecodeAddress("bcrt1qs6d2lpkcfccus6q7c0dvjnlpf5g45gf7yak6mm", &chaincfg.RegressionNetParams)
pkScript, _ := txscript.PayToAddrScript(addr)
Expand All @@ -254,15 +264,15 @@ func TestSendEstimation(t *testing.T) {

// 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))
manualTime = time.Since(tStart)

node := w.node.(*rpcClient)
tStart = time.Now()
for i := 0; i < numCycles; i++ {
Expand All @@ -271,5 +281,5 @@ func TestSendEstimation(t *testing.T) {
t.Fatalf("Error estimating with utxos: %v", err)
}
}
fmt.Println("Time to use fundrawtransaction:", time.Since(tStart))
rpcTime = time.Since(tStart)
}
20 changes: 12 additions & 8 deletions client/asset/zec/zec.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,10 @@ func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (ass
TxFeesCalculator: func(baseTxSize, inputCount, inputsSize, _ uint64) uint64 {
return txFees(baseTxSize, inputCount, inputsSize)
},
SplitFeeCalculator: splitTxFees,
NonSegwitSigner: signTx,
SplitFeeCalculator: func(inputCount, inputsSize, _ uint64, extraOutput, _ bool) (swapInputSize, baggage uint64) {
return splitTxFees(inputCount, inputsSize, extraOutput)
},
NonSegwitSigner: signTx,
TxDeserializer: func(b []byte) (*wire.MsgTx, error) {
zecTx, err := dexzec.DeserializeTx(b)
if err != nil {
Expand Down Expand Up @@ -294,10 +296,10 @@ func (w *zecWallet) PreSwap(req *asset.PreSwapForm) (*asset.PreSwap, error) {
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
// best case: all lots in a single match.
bestCaseFees := dexzec.TransparentTxFeesZIP317(singleTxInsSize, txOutsSize)
// worst-case: all single-lot matches no batching.
worstCaseFees := bestCaseFees * req.Lots
return &asset.PreRedeem{
Estimate: &asset.RedeemEstimate{
RealisticWorstCase: worstCaseFees,
Expand Down Expand Up @@ -649,15 +651,17 @@ func isDust(val, outputSize uint64) bool {

var emptyTxSize = dexzec.CalcTxSize(new(wire.MsgTx))

// txFees is the tx fees for a basic single-output send with change.
// txFees calculates tx fees. baseTxSize is the size of the transaction with
// outputs and no inputs.
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) {
// splitTxFees calculates the fees for a split tx.
func splitTxFees(inputCount, inputsSize uint64, extraOutput bool) (swapInputSize, baggage uint64) {
txInsSize := inputsSize + uint64(wire.VarIntSerializeSize(inputCount))
var numOutputs uint64 = 2
if extraOutput {
Expand Down
3 changes: 2 additions & 1 deletion dex/networks/zec/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ import (
"github.com/btcsuite/btcd/wire"
)

// https://zips.z.cash/zip-0317

// 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)
}
Expand Down
1 change: 1 addition & 0 deletions dex/networks/zec/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,7 @@ func (tx *Tx) SerializeSize() uint64 {
return sz
}

// TxFeesZIP317 calculates the tx fees according to ZIP-0317.
func (tx *Tx) TxFeesZIP317() uint64 {
txInsSize := uint64(wire.VarIntSerializeSize(uint64(len(tx.TxIn))))
for _, txIn := range tx.TxIn {
Expand Down
Binary file removed docs/images/zip-0317.png
Binary file not shown.
20 changes: 0 additions & 20 deletions docs/images/zip-0317.tex

This file was deleted.

4 changes: 2 additions & 2 deletions server/asset/btc/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ 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 is the fees paid in the tx. fees is used by 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
Expand Down
2 changes: 2 additions & 0 deletions server/asset/zec/zec.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ func (be *ZECBackend) ValidateFeeRate(contract *asset.Contract, reqFeeRate uint6
return fees >= zecTx.TxFeesZIP317()
}

var _ asset.OrderEstimator = (*ZECBackend)(nil)

// 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 {
Expand Down

0 comments on commit 3a944c5

Please sign in to comment.