decred.org/dcrdex@v1.0.5/server/asset/zec/zec.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package zec
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"math"
    10  
    11  	"decred.org/dcrdex/dex"
    12  	dexbtc "decred.org/dcrdex/dex/networks/btc"
    13  	dexzec "decred.org/dcrdex/dex/networks/zec"
    14  	"decred.org/dcrdex/server/asset"
    15  	"decred.org/dcrdex/server/asset/btc"
    16  	"github.com/btcsuite/btcd/btcutil"
    17  	"github.com/btcsuite/btcd/chaincfg"
    18  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    19  	"github.com/btcsuite/btcd/wire"
    20  )
    21  
    22  // Driver implements asset.Driver.
    23  type Driver struct{}
    24  
    25  // Setup creates the Zcash backend. Start the backend with its Run method.
    26  func (d *Driver) Setup(cfg *asset.BackendConfig) (asset.Backend, error) {
    27  	return NewBackend(cfg)
    28  }
    29  
    30  // DecodeCoinID creates a human-readable representation of a coin ID for
    31  // Zcash.
    32  func (d *Driver) DecodeCoinID(coinID []byte) (string, error) {
    33  	// Zcash and Bitcoin have the same tx hash and output format.
    34  	return (&btc.Driver{}).DecodeCoinID(coinID)
    35  }
    36  
    37  // Version returns the Backend implementation's version number.
    38  func (d *Driver) Version() uint32 {
    39  	return version
    40  }
    41  
    42  // UnitInfo returns the dex.UnitInfo for the asset.
    43  func (d *Driver) UnitInfo() dex.UnitInfo {
    44  	return dexzec.UnitInfo
    45  }
    46  
    47  // Name is the asset's name.
    48  func (d *Driver) Name() string {
    49  	return "Zcash"
    50  }
    51  
    52  // MinBondSize calculates the minimum bond size for a given fee rate that avoids
    53  // dust outputs on the bond and refund txs, assuming the maxFeeRate doesn't
    54  // change.
    55  func (d *Driver) MinBondSize(maxFeeRate uint64) uint64 {
    56  	var inputsSize uint64 = dexbtc.TxInOverhead + dexbtc.RedeemBondSigScriptSize + 1
    57  	var outputsSize uint64 = dexbtc.P2PKHOutputSize + 1
    58  	refundBondTxFees := dexzec.TxFeesZIP317(inputsSize, outputsSize, 0, 0, 0, 0)
    59  	return dexzec.MinHTLCSize(refundBondTxFees)
    60  }
    61  
    62  // MinLotSize calculates the minimum bond size for a given fee rate that avoids
    63  // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't
    64  // change.
    65  func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 {
    66  	return dexzec.MinLotSize(maxFeeRate)
    67  }
    68  
    69  func init() {
    70  	asset.Register(BipID, &Driver{})
    71  }
    72  
    73  const (
    74  	version   = 0
    75  	BipID     = 133
    76  	assetName = "zec"
    77  	feeConfs  = 10 // Block time is 75 seconds
    78  )
    79  
    80  // NewBackend generates the network parameters and creates a zec backend as a
    81  // btc clone using an asset/btc helper function.
    82  func NewBackend(cfg *asset.BackendConfig) (asset.Backend, error) {
    83  	var btcParams *chaincfg.Params
    84  	var addrParams *dexzec.AddressParams
    85  	switch cfg.Net {
    86  	case dex.Mainnet:
    87  		btcParams = dexzec.MainNetParams
    88  		addrParams = dexzec.MainNetAddressParams
    89  	case dex.Testnet:
    90  		btcParams = dexzec.TestNet4Params
    91  		addrParams = dexzec.TestNet4AddressParams
    92  	case dex.Regtest:
    93  		btcParams = dexzec.RegressionNetParams
    94  		addrParams = dexzec.RegressionNetAddressParams
    95  	default:
    96  		return nil, fmt.Errorf("unknown network ID %v", cfg.Net)
    97  	}
    98  
    99  	// Designate the clone ports. These will be overwritten by any explicit
   100  	// settings in the configuration file.
   101  	ports := dexbtc.NetPorts{
   102  		Mainnet: "8232",
   103  		Testnet: "18232",
   104  		Simnet:  "18232",
   105  	}
   106  
   107  	configPath := cfg.ConfigPath
   108  	if configPath == "" {
   109  		configPath = dexbtc.SystemConfigPath("zcash")
   110  	}
   111  
   112  	be, err := btc.NewBTCClone(&btc.BackendCloneConfig{
   113  		Name:        assetName,
   114  		Segwit:      false,
   115  		ConfigPath:  configPath,
   116  		Logger:      cfg.Logger,
   117  		Net:         cfg.Net,
   118  		ChainParams: btcParams,
   119  		Ports:       ports,
   120  		AddressDecoder: func(addr string, net *chaincfg.Params) (btcutil.Address, error) {
   121  			return dexzec.DecodeAddress(addr, addrParams, btcParams)
   122  		},
   123  		TxDeserializer: func(b []byte) (*wire.MsgTx, error) {
   124  			zecTx, err := dexzec.DeserializeTx(b)
   125  			if err != nil {
   126  				return nil, err
   127  			}
   128  			return zecTx.MsgTx, nil
   129  		},
   130  		TxHasher: func(tx *wire.MsgTx) *chainhash.Hash {
   131  			h := zecTx(tx).TxHash()
   132  			return &h
   133  		},
   134  		BlockDeserializer: func(b []byte) (*wire.MsgBlock, error) {
   135  			zecBlock, err := dexzec.DeserializeBlock(b)
   136  			if err != nil {
   137  				return nil, err
   138  			}
   139  			return &zecBlock.MsgBlock, nil
   140  		},
   141  		DumbFeeEstimates:     true,
   142  		FeeConfs:             feeConfs,
   143  		ManualMedianFee:      true,
   144  		BlockFeeTransactions: blockFeeTransactions,
   145  		NumericGetRawRPC:     true,
   146  		ShieldedIO:           shieldedIO,
   147  		RelayAddr:            cfg.RelayAddr,
   148  	})
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  
   153  	return &ZECBackend{
   154  		Backend:    be,
   155  		log:        cfg.Logger,
   156  		addrParams: addrParams,
   157  		btcParams:  btcParams,
   158  	}, nil
   159  }
   160  
   161  // ZECBackend embeds *btc.Backend and re-implements the Contract method to deal
   162  // with Zcash address translation.
   163  type ZECBackend struct {
   164  	*btc.Backend
   165  	log        dex.Logger
   166  	btcParams  *chaincfg.Params
   167  	addrParams *dexzec.AddressParams
   168  }
   169  
   170  // Contract returns the output from embedded Backend's Contract method, but
   171  // with the SwapAddress field converted to Zcash encoding.
   172  // TODO: Drop this in favor of an AddressEncoder field in the
   173  // BackendCloneConfig.
   174  func (be *ZECBackend) Contract(coinID []byte, redeemScript []byte) (*asset.Contract, error) { // Contract.SwapAddress
   175  	contract, err := be.Backend.Contract(coinID, redeemScript)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  	contract.SwapAddress, err = dexzec.RecodeAddress(contract.SwapAddress, be.addrParams, be.btcParams)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return contract, nil
   184  }
   185  
   186  // For Zcash, return a constant fee rate of 10 zats / byte. We just need to
   187  // guarantee the tx get over the legacy 0.00001 standard tx fee.
   188  func (be *ZECBackend) FeeRate(context.Context) (uint64, error) {
   189  	return dexzec.LegacyFeeRate, nil
   190  }
   191  
   192  func (*ZECBackend) ValidateOrderFunding(swapVal, valSum, inputCount, inputsSize, maxSwaps uint64, _ *dex.Asset) bool {
   193  	reqVal := dexzec.RequiredOrderFunds(swapVal, inputCount, inputsSize, maxSwaps)
   194  	return valSum >= reqVal
   195  }
   196  
   197  func (be *ZECBackend) ValidateFeeRate(ci asset.Coin, _ uint64) bool {
   198  	c, is := ci.(interface {
   199  		InputsValue() uint64
   200  		RawTx() []byte
   201  	})
   202  	if !is {
   203  		be.log.Error("ValidateFeeRate contract does not implement TXIO methods")
   204  		return false
   205  	}
   206  	tx, err := dexzec.DeserializeTx(c.RawTx())
   207  	if err != nil {
   208  		be.log.Errorf("error deserializing tx for fee validation: %v", err)
   209  		return false
   210  	}
   211  
   212  	fees, err := newFeeTx(tx).Fees(c.InputsValue())
   213  	if err != nil {
   214  		be.log.Errorf("error calculating tx fees: %v", err)
   215  		return false
   216  	}
   217  
   218  	return fees >= tx.RequiredTxFeesZIP317()
   219  }
   220  
   221  func blockFeeTransactions(rc *btc.RPCClient, blockHash *chainhash.Hash) (feeTxs []btc.FeeTx, prevBlock chainhash.Hash, err error) {
   222  	blockB, err := rc.GetRawBlock(blockHash)
   223  	if err != nil {
   224  		return nil, chainhash.Hash{}, err
   225  	}
   226  
   227  	blk, err := dexzec.DeserializeBlock(blockB)
   228  	if err != nil {
   229  		return nil, chainhash.Hash{}, err
   230  	}
   231  
   232  	if len(blk.Transactions) == 0 {
   233  		return nil, chainhash.Hash{}, fmt.Errorf("block %s has no transactions", blockHash)
   234  	}
   235  
   236  	feeTxs = make([]btc.FeeTx, 0, len(blk.Transactions)-1)
   237  	for _, tx := range blk.Transactions[1:] { // skip coinbase
   238  		feeTx := newFeeTx(tx)
   239  		feeTxs = append(feeTxs, feeTx)
   240  	}
   241  
   242  	return feeTxs, blk.Header.PrevBlock, nil
   243  }
   244  
   245  // feeTx implements FeeTx for manual median-fee calculations.
   246  type feeTx struct {
   247  	size           uint64
   248  	prevOuts       []wire.OutPoint
   249  	shieldedIn     uint64
   250  	transparentOut uint64
   251  	shieldedOut    uint64
   252  }
   253  
   254  var _ btc.FeeTx = (*feeTx)(nil)
   255  
   256  func newFeeTx(zecTx *dexzec.Tx) *feeTx {
   257  	var transparentOut uint64
   258  	for _, out := range zecTx.TxOut {
   259  		transparentOut += uint64(out.Value)
   260  	}
   261  	prevOuts := make([]wire.OutPoint, 0, len(zecTx.TxIn))
   262  	for _, in := range zecTx.TxIn {
   263  		prevOuts = append(prevOuts, in.PreviousOutPoint)
   264  	}
   265  	var shieldedIn, shieldedOut uint64
   266  	for _, js := range zecTx.VJoinSplit {
   267  		shieldedIn += js.New
   268  		shieldedOut += js.Old
   269  	}
   270  	if zecTx.ValueBalanceSapling > 0 {
   271  		shieldedIn += uint64(zecTx.ValueBalanceSapling)
   272  	} else if zecTx.ValueBalanceSapling < 0 {
   273  		shieldedOut += uint64(-1 * zecTx.ValueBalanceSapling)
   274  	}
   275  	if zecTx.ValueBalanceOrchard > 0 {
   276  		shieldedIn += uint64(zecTx.ValueBalanceOrchard)
   277  	} else if zecTx.ValueBalanceOrchard < 0 {
   278  		shieldedOut += uint64(-1 * zecTx.ValueBalanceOrchard)
   279  	}
   280  
   281  	return &feeTx{
   282  		size:           zecTx.SerializeSize(),
   283  		transparentOut: transparentOut,
   284  		shieldedOut:    shieldedOut,
   285  		shieldedIn:     shieldedIn,
   286  		prevOuts:       prevOuts,
   287  	}
   288  }
   289  
   290  func (tx *feeTx) PrevOuts() []wire.OutPoint {
   291  	return tx.prevOuts
   292  }
   293  
   294  func (tx *feeTx) FeeRate(prevOuts map[chainhash.Hash]map[int]int64) (uint64, error) {
   295  	var transparentIn uint64
   296  	for _, op := range tx.prevOuts {
   297  		outs, found := prevOuts[op.Hash]
   298  		if !found {
   299  			return 0, fmt.Errorf("previous outpoint tx not found for %+v", op)
   300  		}
   301  		prevOutValue, found := outs[int(op.Index)]
   302  		if !found {
   303  			return 0, fmt.Errorf("previous outpoint vout not found for %+v", op)
   304  		}
   305  		transparentIn += uint64(prevOutValue)
   306  	}
   307  	fees, err := tx.Fees(transparentIn)
   308  	if err != nil {
   309  		return 0, err
   310  	}
   311  	return uint64(math.Round(float64(fees) / float64(tx.size))), nil
   312  }
   313  
   314  func (tx *feeTx) Fees(transparentIn uint64) (uint64, error) {
   315  	in := tx.shieldedIn + transparentIn
   316  	out := tx.shieldedOut + tx.transparentOut
   317  	if out > in {
   318  		return 0, fmt.Errorf("out > in. %d > %d", out, in)
   319  	}
   320  	return in - out, nil
   321  }
   322  
   323  func shieldedIO(tx *btc.VerboseTxExtended) (in, out uint64, err error) {
   324  	zecTx, err := dexzec.DeserializeTx(tx.Raw)
   325  	if err != nil {
   326  		return 0, 0, fmt.Errorf("DeserializeTx error: %w", err)
   327  	}
   328  	feeTx := newFeeTx(zecTx)
   329  	return feeTx.shieldedIn, feeTx.shieldedOut, nil
   330  }
   331  
   332  func zecTx(tx *wire.MsgTx) *dexzec.Tx {
   333  	return dexzec.NewTxFromMsgTx(tx, dexzec.MaxExpiryHeight)
   334  }