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