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 }