decred.org/dcrdex@v1.0.5/client/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 "bytes" 8 "context" 9 "crypto/sha256" 10 "encoding/binary" 11 "encoding/hex" 12 "encoding/json" 13 "errors" 14 "fmt" 15 "math" 16 "os" 17 "path/filepath" 18 "regexp" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "decred.org/dcrdex/client/asset" 27 "decred.org/dcrdex/client/asset/btc" 28 "decred.org/dcrdex/dex" 29 "decred.org/dcrdex/dex/config" 30 dexbtc "decred.org/dcrdex/dex/networks/btc" 31 dexzec "decred.org/dcrdex/dex/networks/zec" 32 "github.com/btcsuite/btcd/btcec/v2" 33 "github.com/btcsuite/btcd/btcec/v2/ecdsa" 34 "github.com/btcsuite/btcd/btcjson" 35 "github.com/btcsuite/btcd/btcutil" 36 "github.com/btcsuite/btcd/chaincfg" 37 "github.com/btcsuite/btcd/chaincfg/chainhash" 38 "github.com/btcsuite/btcd/txscript" 39 "github.com/btcsuite/btcd/wire" 40 "github.com/decred/dcrd/rpcclient/v8" 41 ) 42 43 const ( 44 version = 0 45 BipID = 133 46 // The default fee is passed to the user as part of the asset.WalletInfo 47 // structure. 48 defaultFee = 10 49 defaultFeeRateLimit = 1000 50 minNetworkVersion = 5090150 // v5.9.1 51 walletTypeRPC = "zcashdRPC" 52 53 // defaultConfTarget is the amount of confirmations required to consider 54 // a transaction is confirmed for tx history. 55 defaultConfTarget = 1 56 57 // transparentAcctNumber = 0 58 shieldedAcctNumber = 0 59 60 transparentAddressType = "p2pkh" 61 orchardAddressType = "orchard" 62 saplingAddressType = "sapling" 63 unifiedAddressType = "unified" 64 65 minOrchardConfs = 1 66 // nActionsOrchardEstimate is used for tx fee estimation. Scanning 1000 67 // previous blocks, only found 1 with a tx with > 6 nActionsOrchard. Most 68 // are 2. 69 nActionsOrchardEstimate = 6 70 71 blockTicker = time.Second 72 peerCountTicker = 5 * time.Second 73 74 // requiredRedeemConfirms is the amount of confirms a redeem transaction 75 // needs before the trade is considered confirmed. The redeem is 76 // monitored until this number of confirms is reached. 77 requiredRedeemConfirms = 1 78 79 depositAddrPrefix = "unified:" 80 ) 81 82 var ( 83 configOpts = []*asset.ConfigOption{ 84 { 85 Key: "rpcuser", 86 DisplayName: "JSON-RPC Username", 87 Description: "Zcash's 'rpcuser' setting", 88 }, 89 { 90 Key: "rpcpassword", 91 DisplayName: "JSON-RPC Password", 92 Description: "Zcash's 'rpcpassword' setting", 93 NoEcho: true, 94 }, 95 { 96 Key: "rpcbind", 97 DisplayName: "JSON-RPC Address", 98 Description: "<addr> or <addr>:<port> (default 'localhost')", 99 }, 100 { 101 Key: "rpcport", 102 DisplayName: "JSON-RPC Port", 103 Description: "Port for RPC connections (if not set in Address)", 104 }, 105 { 106 Key: "fallbackfee", 107 DisplayName: "Fallback fee rate", 108 Description: "Zcash's 'fallbackfee' rate. Units: ZEC/kB", 109 DefaultValue: strconv.FormatFloat(defaultFee*1000/1e8, 'f', -1, 64), 110 }, 111 { 112 Key: "feeratelimit", 113 DisplayName: "Highest acceptable fee rate", 114 Description: "This is the highest network fee rate you are willing to " + 115 "pay on swap transactions. If feeratelimit is lower than a market's " + 116 "maxfeerate, you will not be able to trade on that market with this " + 117 "wallet. Units: BTC/kB", 118 DefaultValue: strconv.FormatFloat(defaultFeeRateLimit*1000/1e8, 'f', -1, 64), 119 }, 120 { 121 Key: "txsplit", 122 DisplayName: "Pre-split funding inputs", 123 Description: "When placing an order, create a \"split\" transaction to fund the order without locking more of the wallet balance than " + 124 "necessary. Otherwise, excess funds may be reserved to fund the order until the first swap contract is broadcast " + 125 "during match settlement, or the order is canceled. This an extra transaction for which network mining fees are paid. " + 126 "Used only for standing-type orders, e.g. limit orders without immediate time-in-force.", 127 IsBoolean: true, 128 }, 129 } 130 // WalletInfo defines some general information about a Zcash wallet. 131 WalletInfo = &asset.WalletInfo{ 132 Name: "Zcash", 133 SupportedVersions: []uint32{version}, 134 UnitInfo: dexzec.UnitInfo, 135 AvailableWallets: []*asset.WalletDefinition{{ 136 Type: walletTypeRPC, 137 Tab: "External", 138 Description: "Connect to zcashd", 139 DefaultConfigPath: dexbtc.SystemConfigPath("zcash"), 140 ConfigOpts: configOpts, 141 NoAuth: true, 142 }}, 143 } 144 145 feeReservesPerLot = dexzec.TxFeesZIP317(dexbtc.RedeemP2PKHInputSize+1, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0) 146 ) 147 148 func init() { 149 asset.Register(BipID, &Driver{}) 150 } 151 152 // Driver implements asset.Driver. 153 type Driver struct{} 154 155 // Open creates the ZEC exchange wallet. Start the wallet with its Run method. 156 func (d *Driver) Open(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) { 157 return NewWallet(cfg, logger, network) 158 } 159 160 // DecodeCoinID creates a human-readable representation of a coin ID for 161 // Zcash. 162 func (d *Driver) DecodeCoinID(coinID []byte) (string, error) { 163 // Zcash shielded transactions don't have transparent outputs, so the coinID 164 // will just be the tx hash. 165 if len(coinID) == chainhash.HashSize { 166 var txHash chainhash.Hash 167 copy(txHash[:], coinID) 168 return txHash.String(), nil 169 } 170 // For transparent transactions, Zcash and Bitcoin have the same tx hash 171 // and output format. 172 return (&btc.Driver{}).DecodeCoinID(coinID) 173 } 174 175 // Info returns basic information about the wallet and asset. 176 func (d *Driver) Info() *asset.WalletInfo { 177 return WalletInfo 178 } 179 180 // MinLotSize calculates the minimum bond size for a given fee rate that avoids 181 // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't 182 // change. 183 func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 { 184 return dexzec.MinLotSize(maxFeeRate) 185 } 186 187 // WalletConfig are wallet-level configuration settings. 188 type WalletConfig struct { 189 UseSplitTx bool `ini:"txsplit"` 190 RedeemConfTarget uint64 `ini:"redeemconftarget"` 191 ActivelyUsed bool `ini:"special_activelyUsed"` // injected by core 192 } 193 194 func newRPCConnection(cfg *dexbtc.RPCConfig) (*rpcclient.Client, error) { 195 return rpcclient.New(&rpcclient.ConnConfig{ 196 HTTPPostMode: true, 197 DisableTLS: true, 198 Host: cfg.RPCBind, 199 User: cfg.RPCUser, 200 Pass: cfg.RPCPass, 201 }, nil) 202 } 203 204 // NewWallet is the exported constructor by which the DEX will import the 205 // exchange wallet. The wallet will shut down when the provided context is 206 // canceled. The configPath can be an empty string, in which case the standard 207 // system location of the zcashd config file is assumed. 208 func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (asset.Wallet, error) { 209 var btcParams *chaincfg.Params 210 var addrParams *dexzec.AddressParams 211 switch net { 212 case dex.Mainnet: 213 btcParams = dexzec.MainNetParams 214 addrParams = dexzec.MainNetAddressParams 215 case dex.Testnet: 216 btcParams = dexzec.TestNet4Params 217 addrParams = dexzec.TestNet4AddressParams 218 case dex.Regtest: 219 btcParams = dexzec.RegressionNetParams 220 addrParams = dexzec.RegressionNetAddressParams 221 default: 222 return nil, fmt.Errorf("unknown network ID %v", net) 223 } 224 225 // Designate the clone ports. These will be overwritten by any explicit 226 // settings in the configuration file. 227 ports := dexbtc.NetPorts{ 228 Mainnet: "8232", 229 Testnet: "18232", 230 Simnet: "18232", 231 } 232 233 var walletCfg WalletConfig 234 err := config.Unmapify(cfg.Settings, &walletCfg) 235 if err != nil { 236 return nil, err 237 } 238 239 if err := os.MkdirAll(cfg.DataDir, 0700); err != nil { 240 return nil, fmt.Errorf("error creating data directory at %q: %w", cfg.DataDir, err) 241 } 242 243 ar, err := btc.NewAddressRecycler(filepath.Join(cfg.DataDir, "recycled-addrs.txt"), logger) 244 if err != nil { 245 return nil, fmt.Errorf("error creating address recycler: %w", err) 246 } 247 248 var rpcCfg dexbtc.RPCConfig 249 if err := config.Unmapify(cfg.Settings, &rpcCfg); err != nil { 250 return nil, fmt.Errorf("error reading settings: %w", err) 251 } 252 253 if err := dexbtc.CheckRPCConfig(&rpcCfg, "Zcash", net, ports); err != nil { 254 return nil, fmt.Errorf("rpc config error: %v", err) 255 } 256 257 cl, err := newRPCConnection(&rpcCfg) 258 if err != nil { 259 return nil, fmt.Errorf("error constructing rpc client: %w", err) 260 } 261 262 zw := &zecWallet{ 263 peersChange: cfg.PeersChange, 264 emit: cfg.Emit, 265 log: logger, 266 net: net, 267 btcParams: btcParams, 268 addrParams: addrParams, 269 decodeAddr: func(addr string, net *chaincfg.Params) (btcutil.Address, error) { 270 return dexzec.DecodeAddress(addr, addrParams, btcParams) 271 }, 272 ar: ar, 273 node: cl, 274 walletDir: cfg.DataDir, 275 pendingTxs: make(map[chainhash.Hash]*btc.ExtendedWalletTx), 276 } 277 zw.walletCfg.Store(&walletCfg) 278 zw.prepareCoinManager() 279 zw.prepareRedemptionFinder() 280 return zw, nil 281 } 282 283 type rpcCaller interface { 284 CallRPC(method string, args []any, thing any) error 285 } 286 287 // zecWallet is an asset.Wallet for Zcash. zecWallet is a shielded-first wallet. 288 // This has a number of implications. 289 // 1. zecWallet will, by default, keep funds in the shielded pool. 290 // 2. If you send funds with z_sendmany, the change will go to the shielded pool. 291 // This means that we cannot maintain a transparent pool at all, since this 292 // behavior is unavoidable in the Zcash API, so sending funds to ourselves, 293 // for instance, would probably result in sending more into shielded than 294 // we wanted. This does not apply to fully transparent transactions such as 295 // swaps and redemptions. 296 // 3. When funding is requested for an order, we will generally generate a 297 // "split transaction", but one that moves funds from the shielded pool to 298 // a transparent receiver. This will be default behavior for Zcash now, and 299 // cannot be disabled via configuration. 300 // 4. ... 301 type zecWallet struct { 302 ctx context.Context 303 log dex.Logger 304 net dex.Network 305 lastAddress atomic.Value // "string" 306 btcParams *chaincfg.Params 307 addrParams *dexzec.AddressParams 308 node btc.RawRequester 309 ar *btc.AddressRecycler 310 rf *btc.RedemptionFinder 311 decodeAddr dexbtc.AddressDecoder 312 lastPeerCount uint32 313 peersChange func(uint32, error) 314 emit *asset.WalletEmitter 315 walletCfg atomic.Value // *WalletConfig 316 walletDir string 317 318 // Coins returned by Fund are cached for quick reference. 319 cm *btc.CoinManager 320 321 tipAtConnect atomic.Uint64 322 323 tipMtx sync.RWMutex 324 currentTip *btc.BlockVector 325 326 reserves atomic.Uint64 327 328 pendingTxsMtx sync.RWMutex 329 pendingTxs map[chainhash.Hash]*btc.ExtendedWalletTx 330 331 receiveTxLastQuery atomic.Uint64 332 333 txHistoryDB atomic.Value // *btc.BadgerTxDB 334 syncingTxHistory atomic.Bool 335 } 336 337 var _ asset.FeeRater = (*zecWallet)(nil) 338 var _ asset.Wallet = (*zecWallet)(nil) 339 var _ asset.WalletHistorian = (*zecWallet)(nil) 340 var _ asset.NewAddresser = (*zecWallet)(nil) 341 342 // TODO: Implement LiveReconfigurer 343 // var _ asset.LiveReconfigurer = (*zecWallet)(nil) 344 345 func (w *zecWallet) CallRPC(method string, args []any, thing any) error { 346 return btc.Call(w.ctx, w.node, method, args, thing) 347 } 348 349 // FeeRate returns the asset standard fee rate for Zcash. 350 func (w *zecWallet) FeeRate() uint64 { 351 return 5000 // per logical action 352 } 353 354 func (w *zecWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) { 355 w.ctx = ctx 356 357 if err := w.connectRPC(ctx); err != nil { 358 return nil, err 359 } 360 361 fullTBalance, err := getBalance(w) 362 if err != nil { 363 return nil, fmt.Errorf("error getting account-wide t-balance: %w", err) 364 } 365 366 const minConfs = 0 367 acctBal, err := zGetBalanceForAccount(w, shieldedAcctNumber, minConfs) 368 if err != nil { 369 return nil, fmt.Errorf("error getting account balance: %w", err) 370 } 371 372 locked, err := w.lockedZats() 373 if err != nil { 374 return nil, fmt.Errorf("error getting locked zats: %w", err) 375 } 376 377 if acctBal.Transparent+locked != fullTBalance { 378 return nil, errors.New( 379 "there appears to be some transparent balance that is not in the zeroth account. " + 380 "To operate correctly, all balance must be in the zeroth account. " + 381 "Move all balance to the zeroth account to use the Zcash wallet", 382 ) 383 } 384 385 // Initialize the best block. 386 bestBlockHdr, err := getBestBlockHeader(w) 387 if err != nil { 388 return nil, fmt.Errorf("error initializing best block: %w", err) 389 } 390 bestBlockHash, err := chainhash.NewHashFromStr(bestBlockHdr.Hash) 391 if err != nil { 392 return nil, fmt.Errorf("invalid best block hash from node: %v", err) 393 } 394 395 bestBlock := &btc.BlockVector{Height: bestBlockHdr.Height, Hash: *bestBlockHash} 396 w.log.Infof("Connected wallet with current best block %v (%d)", bestBlock.Hash, bestBlock.Height) 397 w.tipMtx.Lock() 398 w.currentTip = bestBlock 399 w.tipMtx.Unlock() 400 w.tipAtConnect.Store(uint64(w.currentTip.Height)) 401 402 wg, err := w.startTxHistoryDB(ctx) 403 if err != nil { 404 return nil, err 405 } 406 407 wg.Add(1) 408 go func() { 409 defer wg.Done() 410 <-ctx.Done() 411 w.ar.WriteRecycledAddrsToFile() 412 }() 413 414 wg.Add(1) 415 go func() { 416 defer wg.Done() 417 w.watchBlocks(ctx) 418 w.rf.CancelRedemptionSearches() 419 }() 420 wg.Add(1) 421 go func() { 422 defer wg.Done() 423 w.monitorPeers(ctx) 424 }() 425 426 wg.Add(1) 427 go func() { 428 defer wg.Done() 429 w.syncTxHistory(uint64(w.currentTip.Height)) 430 }() 431 432 return wg, nil 433 } 434 435 func (w *zecWallet) monitorPeers(ctx context.Context) { 436 ticker := time.NewTicker(peerCountTicker) 437 defer ticker.Stop() 438 for { 439 w.checkPeers() 440 441 select { 442 case <-ticker.C: 443 case <-ctx.Done(): 444 return 445 } 446 } 447 } 448 449 func (w *zecWallet) checkPeers() { 450 numPeers, err := peerCount(w) 451 if err != nil { 452 prevPeer := atomic.SwapUint32(&w.lastPeerCount, 0) 453 if prevPeer != 0 { 454 w.log.Errorf("Failed to get peer count: %v", err) 455 w.peersChange(0, err) 456 } 457 return 458 } 459 prevPeer := atomic.SwapUint32(&w.lastPeerCount, numPeers) 460 if prevPeer != numPeers { 461 w.peersChange(numPeers, nil) 462 } 463 } 464 465 func (w *zecWallet) prepareCoinManager() { 466 w.cm = btc.NewCoinManager( 467 w.log, 468 w.btcParams, 469 func(val, lots, maxFeeRate uint64, reportChange bool) btc.EnoughFunc { // orderEnough 470 return func(inputCount, inputsSize, sum uint64) (bool, uint64) { 471 req := dexzec.RequiredOrderFunds(val, inputCount, inputsSize, lots) 472 if sum >= req { 473 excess := sum - req 474 if !reportChange || isDust(val, dexbtc.P2SHOutputSize) { 475 excess = 0 476 } 477 return true, excess 478 } 479 return false, 0 480 } 481 }, 482 func() ([]*btc.ListUnspentResult, error) { // list 483 return listUnspent(w) 484 }, 485 func(unlock bool, ops []*btc.Output) error { // lock 486 return lockUnspent(w, unlock, ops) 487 }, 488 func() ([]*btc.RPCOutpoint, error) { // listLocked 489 return listLockUnspent(w, w.log) 490 }, 491 func(txHash *chainhash.Hash, vout uint32) (*wire.TxOut, error) { 492 walletTx, err := getWalletTransaction(w, txHash) 493 if err != nil { 494 return nil, err 495 } 496 tx, err := dexzec.DeserializeTx(walletTx.Bytes) 497 if err != nil { 498 w.log.Warnf("Invalid transaction %v (%x): %v", txHash, walletTx.Bytes, err) 499 return nil, nil 500 } 501 if vout >= uint32(len(tx.TxOut)) { 502 w.log.Warnf("Invalid vout %d for %v", vout, txHash) 503 return nil, nil 504 } 505 return tx.TxOut[vout], nil 506 }, 507 func(addr btcutil.Address) (string, error) { 508 return dexzec.EncodeAddress(addr, w.addrParams) 509 }, 510 ) 511 } 512 513 func (w *zecWallet) prepareRedemptionFinder() { 514 w.rf = btc.NewRedemptionFinder( 515 w.log, 516 func(h *chainhash.Hash) (*btc.GetTransactionResult, error) { 517 tx, err := getWalletTransaction(w, h) 518 if err != nil { 519 return nil, err 520 } 521 if tx.Confirmations < 0 { 522 tx.Confirmations = 0 523 } 524 return &btc.GetTransactionResult{ 525 Confirmations: uint64(tx.Confirmations), 526 BlockHash: tx.BlockHash, 527 BlockTime: tx.BlockTime, 528 TxID: tx.TxID, 529 Time: tx.Time, 530 TimeReceived: tx.TimeReceived, 531 Bytes: tx.Bytes, 532 }, nil 533 }, 534 func(h *chainhash.Hash) (int32, error) { 535 return getBlockHeight(w, h) 536 }, 537 func(h chainhash.Hash) (*wire.MsgBlock, error) { 538 blk, err := getBlock(w, h) 539 if err != nil { 540 return nil, err 541 } 542 return &blk.MsgBlock, nil 543 }, 544 func(h *chainhash.Hash) (hdr *btc.BlockHeader, mainchain bool, err error) { 545 return getVerboseBlockHeader(w, h) 546 }, 547 hashTx, 548 deserializeTx, 549 func() (int32, error) { 550 return getBestBlockHeight(w) 551 }, 552 func(ctx context.Context, reqs map[btc.OutPoint]*btc.FindRedemptionReq, blockHash chainhash.Hash) (discovered map[btc.OutPoint]*btc.FindRedemptionResult) { 553 blk, err := getBlock(w, blockHash) 554 if err != nil { 555 w.log.Errorf("RPC GetBlock error: %v", err) 556 return 557 } 558 return btc.SearchBlockForRedemptions(ctx, reqs, &blk.MsgBlock, false, hashTx, w.btcParams) 559 }, 560 func(h int64) (*chainhash.Hash, error) { 561 return getBlockHash(w, h) 562 }, 563 func(ctx context.Context, reqs map[btc.OutPoint]*btc.FindRedemptionReq) (discovered map[btc.OutPoint]*btc.FindRedemptionResult) { 564 getRawMempool := func() ([]*chainhash.Hash, error) { 565 return getRawMempool(w) 566 } 567 getMsgTx := func(txHash *chainhash.Hash) (*wire.MsgTx, error) { 568 tx, err := getZecTransaction(w, txHash) 569 if err != nil { 570 return nil, err 571 } 572 return tx.MsgTx, nil 573 } 574 return btc.FindRedemptionsInMempool(ctx, w.log, reqs, getRawMempool, getMsgTx, false, hashTx, w.btcParams) 575 }, 576 ) 577 } 578 579 func (w *zecWallet) connectRPC(ctx context.Context) error { 580 netVer, _, err := getVersion(w) 581 if err != nil { 582 return fmt.Errorf("error getting version: %w", err) 583 } 584 if netVer < minNetworkVersion { 585 return fmt.Errorf("reported node version %d is less than minimum %d", netVer, minNetworkVersion) 586 } 587 chainInfo, err := getBlockchainInfo(w) 588 if err != nil { 589 return fmt.Errorf("getblockchaininfo error: %w", err) 590 } 591 if !btc.ChainOK(w.net, chainInfo.Chain) { 592 return errors.New("wrong net") 593 } 594 // Make sure we have zeroth and first account or are able to create them. 595 accts, err := zListAccounts(w) 596 if err != nil { 597 return fmt.Errorf("error listing Zcash accounts: %w", err) 598 } 599 600 createAccount := func(n uint32) error { 601 for _, acct := range accts { 602 if acct.Number == n { 603 return nil 604 } 605 } 606 acctNumber, err := zGetNewAccount(w) 607 if err != nil { 608 if strings.Contains(err.Error(), "zcashd-wallet-tool") { 609 return fmt.Errorf("account %d does not exist and cannot be created because wallet seed backup has not been acknowledged with the zcashd-wallet-tool utility", n) 610 } 611 return fmt.Errorf("error creating account %d: %w", n, err) 612 } 613 if acctNumber != n { 614 return fmt.Errorf("no account %d found and newly created account has unexpected account number %d", n, acctNumber) 615 } 616 return nil 617 } 618 if err := createAccount(shieldedAcctNumber); err != nil { 619 return err 620 } 621 return nil 622 } 623 624 // watchBlocks pings for new blocks and runs the tipChange callback function 625 // when the block changes. 626 func (w *zecWallet) watchBlocks(ctx context.Context) { 627 ticker := time.NewTicker(blockTicker) 628 defer ticker.Stop() 629 630 for { 631 select { 632 633 // Poll for the block. If the wallet offers tip reports, delay reporting 634 // the tip to give the wallet a moment to request and scan block data. 635 case <-ticker.C: 636 newTipHdr, err := getBestBlockHeader(w) 637 if err != nil { 638 w.log.Errorf("failed to get best block header from node: %v", err) 639 continue 640 } 641 newTipHash, err := chainhash.NewHashFromStr(newTipHdr.Hash) 642 if err != nil { 643 w.log.Errorf("invalid best block hash from node: %v", err) 644 continue 645 } 646 647 w.tipMtx.RLock() 648 sameTip := w.currentTip.Hash == *newTipHash 649 w.tipMtx.RUnlock() 650 if sameTip { 651 continue 652 } 653 654 newTip := &btc.BlockVector{Height: newTipHdr.Height, Hash: *newTipHash} 655 w.reportNewTip(ctx, newTip) 656 657 case <-ctx.Done(): 658 return 659 } 660 661 // Ensure context cancellation takes priority before the next iteration. 662 if ctx.Err() != nil { 663 return 664 } 665 } 666 } 667 668 // reportNewTip sets the currentTip. The tipChange callback function is invoked 669 // and a goroutine is started to check if any contracts in the 670 // findRedemptionQueue are redeemed in the new blocks. 671 func (w *zecWallet) reportNewTip(ctx context.Context, newTip *btc.BlockVector) { 672 w.tipMtx.Lock() 673 defer w.tipMtx.Unlock() 674 675 prevTip := w.currentTip 676 w.currentTip = newTip 677 w.log.Tracef("tip change: %d (%s) => %d (%s)", prevTip.Height, prevTip.Hash, newTip.Height, newTip.Hash) 678 w.emit.TipChange(uint64(newTip.Height)) 679 680 w.rf.ReportNewTip(ctx, prevTip, newTip) 681 682 w.syncTxHistory(uint64(newTip.Height)) 683 } 684 685 type swapOptions struct { 686 Split *bool `ini:"swapsplit"` 687 // DRAFT TODO: Strip swapfeebump from PreSwap results. 688 // FeeBump *float64 `ini:"swapfeebump"` 689 } 690 691 func (w *zecWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint64, error) { 692 ordValStr := btcutil.Amount(ord.Value).String() 693 w.log.Debugf("Attempting to fund Zcash order, maxFeeRate = %d, max swaps = %d", 694 ord.MaxFeeRate, ord.MaxSwapCount) 695 696 if ord.Value == 0 { 697 return nil, nil, 0, newError(errNoFundsRequested, "cannot fund value = 0") 698 } 699 if ord.MaxSwapCount == 0 { 700 return nil, nil, 0, fmt.Errorf("cannot fund a zero-lot order") 701 } 702 703 var customCfg swapOptions 704 err := config.Unmapify(ord.Options, &customCfg) 705 if err != nil { 706 return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err) 707 } 708 709 useSplit := w.useSplitTx() 710 if customCfg.Split != nil { 711 useSplit = *customCfg.Split 712 } 713 714 bals, err := w.balances() 715 if err != nil { 716 return nil, nil, 0, fmt.Errorf("balances error: %w", err) 717 } 718 719 utxos, _, _, err := w.cm.SpendableUTXOs(0) 720 if err != nil { 721 return nil, nil, 0, newError(errFunding, "error listing utxos: %w", err) 722 } 723 724 sum, size, shieldedSplitNeeded, shieldedSplitFees, coins, fundingCoins, redeemScripts, spents, err := w.fund( 725 ord.Value, ord.MaxSwapCount, utxos, bals.orchard, 726 ) 727 if err != nil { 728 return nil, nil, 0, err 729 } 730 731 inputsSize := size + uint64(wire.VarIntSerializeSize(uint64(len(coins)))) 732 var transparentSplitFees uint64 733 734 if shieldedSplitNeeded > 0 { 735 acctAddr, err := w.lastShieldedAddress() 736 if err != nil { 737 return nil, nil, 0, fmt.Errorf("lastShieldedAddress error: %w", err) 738 } 739 740 toAddrBTC, err := transparentAddress(w, w.addrParams, w.btcParams) 741 if err != nil { 742 return nil, nil, 0, fmt.Errorf("DepositAddress error: %w", err) 743 } 744 745 toAddrStr, err := dexzec.EncodeAddress(toAddrBTC, w.addrParams) 746 if err != nil { 747 return nil, nil, 0, fmt.Errorf("EncodeAddress error: %w", err) 748 } 749 750 pkScript, err := txscript.PayToAddrScript(toAddrBTC) 751 if err != nil { 752 return nil, nil, 0, fmt.Errorf("PayToAddrScript error: %w", err) 753 } 754 755 txHash, err := w.sendOne(w.ctx, acctAddr, toAddrStr, shieldedSplitNeeded, AllowRevealedRecipients) 756 if err != nil { 757 return nil, nil, 0, fmt.Errorf("error sending shielded split tx: %w", err) 758 } 759 760 tx, err := getTransaction(w, txHash) 761 if err != nil { 762 return nil, nil, 0, fmt.Errorf("error retreiving split transaction %s: %w", txHash, err) 763 } 764 765 var splitOutput *wire.TxOut 766 var splitOutputIndex int 767 for vout, txOut := range tx.TxOut { 768 if txOut.Value >= int64(shieldedSplitNeeded) && bytes.Equal(txOut.PkScript, pkScript) { 769 splitOutput = txOut 770 splitOutputIndex = vout 771 break 772 } 773 } 774 if splitOutput == nil { 775 return nil, nil, 0, fmt.Errorf("split output of size %d not found in transaction %s", shieldedSplitNeeded, txHash) 776 } 777 778 op := btc.NewOutput(txHash, uint32(splitOutputIndex), uint64(splitOutput.Value)) 779 fundingCoins = map[btc.OutPoint]*btc.UTxO{op.Pt: { 780 TxHash: &op.Pt.TxHash, 781 Vout: op.Pt.Vout, 782 Address: toAddrStr, 783 Amount: shieldedSplitNeeded, 784 }} 785 coins = []asset.Coin{op} 786 redeemScripts = []dex.Bytes{nil} 787 spents = []*btc.Output{op} 788 } else if useSplit { 789 // No shielded split needed. Should we do a split to avoid overlock. 790 splitTxFees := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0) 791 requiredForOrderWithoutSplit := dexzec.RequiredOrderFunds(ord.Value, uint64(len(coins)), inputsSize, ord.MaxSwapCount) 792 excessWithoutSplit := sum - requiredForOrderWithoutSplit 793 if splitTxFees >= excessWithoutSplit { 794 w.log.Debugf("Skipping split transaction because cost is greater than potential over-lock. "+ 795 "%s > %s", btcutil.Amount(splitTxFees), btcutil.Amount(excessWithoutSplit)) 796 } else { 797 splitOutputVal := dexzec.RequiredOrderFunds(ord.Value, 1, dexbtc.RedeemP2PKHInputSize, ord.MaxSwapCount) 798 transparentSplitFees = splitTxFees 799 baseTx, _, _, err := w.fundedTx(spents) 800 if err != nil { 801 return nil, nil, 0, fmt.Errorf("fundedTx error: %w", err) 802 } 803 804 splitOutputAddr, err := transparentAddress(w, w.addrParams, w.btcParams) 805 if err != nil { 806 return nil, nil, 0, fmt.Errorf("transparentAddress (0) error: %w", err) 807 } 808 splitOutputScript, err := txscript.PayToAddrScript(splitOutputAddr) 809 if err != nil { 810 return nil, nil, 0, fmt.Errorf("split output addr PayToAddrScript error: %w", err) 811 } 812 baseTx.AddTxOut(wire.NewTxOut(int64(splitOutputVal), splitOutputScript)) 813 814 changeAddr, err := transparentAddress(w, w.addrParams, w.btcParams) 815 if err != nil { 816 return nil, nil, 0, fmt.Errorf("transparentAddress (1) error: %w", err) 817 } 818 819 splitTx, _, err := w.signTxAndAddChange(baseTx, changeAddr, sum, splitOutputVal, transparentSplitFees) 820 if err != nil { 821 return nil, nil, 0, fmt.Errorf("signTxAndAddChange error: %v", err) 822 } 823 824 splitTxHash, err := sendRawTransaction(w, splitTx) 825 if err != nil { 826 return nil, nil, 0, fmt.Errorf("sendRawTransaction error: %w", err) 827 } 828 829 if *splitTxHash != splitTx.TxHash() { 830 return nil, nil, 0, errors.New("split tx had unexpected hash") 831 } 832 833 w.log.Debugf("Sent split tx spending %d outputs with a sum value of %d to get a sized output of value %d", 834 len(coins), sum, splitOutputVal) 835 836 op := btc.NewOutput(splitTxHash, 0, splitOutputVal) 837 838 addrStr, err := dexzec.EncodeAddress(splitOutputAddr, w.addrParams) 839 if err != nil { 840 return nil, nil, 0, fmt.Errorf("error stringing address %q: %w", splitOutputAddr, err) 841 } 842 843 fundingCoins = map[btc.OutPoint]*btc.UTxO{op.Pt: { 844 TxHash: &op.Pt.TxHash, 845 Vout: op.Pt.Vout, 846 Address: addrStr, 847 Amount: splitOutputVal, 848 }} 849 coins = []asset.Coin{op} 850 redeemScripts = []dex.Bytes{nil} 851 spents = []*btc.Output{op} 852 } 853 } 854 855 w.log.Debugf("Funding %s ZEC order with coins %v worth %s", ordValStr, coins, btcutil.Amount(sum)) 856 857 w.cm.LockOutputsMap(fundingCoins) 858 err = lockUnspent(w, false, spents) 859 if err != nil { 860 return nil, nil, 0, newError(errLockUnspent, "LockUnspent error: %w", err) 861 } 862 return coins, redeemScripts, shieldedSplitFees + transparentSplitFees, nil 863 } 864 865 // Redeem sends the redemption transaction, completing the atomic swap. 866 func (w *zecWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) { 867 // Create a transaction that spends the referenced contract. 868 tx := dexzec.NewTxFromMsgTx(wire.NewMsgTx(dexzec.VersionNU5), dexzec.MaxExpiryHeight) 869 var totalIn uint64 870 contracts := make([][]byte, 0, len(form.Redemptions)) 871 prevScripts := make([][]byte, 0, len(form.Redemptions)) 872 addresses := make([]btcutil.Address, 0, len(form.Redemptions)) 873 values := make([]int64, 0, len(form.Redemptions)) 874 var txInsSize uint64 875 for _, r := range form.Redemptions { 876 if r.Spends == nil { 877 return nil, nil, 0, fmt.Errorf("no audit info") 878 } 879 880 cinfo, err := btc.ConvertAuditInfo(r.Spends, w.decodeAddr, w.btcParams) 881 if err != nil { 882 return nil, nil, 0, fmt.Errorf("ConvertAuditInfo error: %w", err) 883 } 884 885 // Extract the swap contract recipient and secret hash and check the secret 886 // hash against the hash of the provided secret. 887 contract := cinfo.Contract() 888 _, receiver, _, secretHash, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams) 889 if err != nil { 890 return nil, nil, 0, fmt.Errorf("error extracting swap addresses: %w", err) 891 } 892 checkSecretHash := sha256.Sum256(r.Secret) 893 if !bytes.Equal(checkSecretHash[:], secretHash) { 894 return nil, nil, 0, fmt.Errorf("secret hash mismatch") 895 } 896 pkScript, err := w.scriptHashScript(contract) 897 if err != nil { 898 return nil, nil, 0, fmt.Errorf("error constructs p2sh script: %v", err) 899 } 900 prevScripts = append(prevScripts, pkScript) 901 addresses = append(addresses, receiver) 902 contracts = append(contracts, contract) 903 txIn := wire.NewTxIn(cinfo.Output.WireOutPoint(), nil, nil) 904 tx.AddTxIn(txIn) 905 values = append(values, int64(cinfo.Output.Val)) 906 totalIn += cinfo.Output.Val 907 txInsSize = tx.SerializeSize() 908 } 909 910 txInsSize += uint64(wire.VarIntSerializeSize(uint64(len(tx.TxIn)))) 911 txOutsSize := uint64(1 + dexbtc.P2PKHOutputSize) 912 fee := dexzec.TxFeesZIP317(txInsSize, txOutsSize, 0, 0, 0, 0) 913 914 // Send the funds back to the exchange wallet. 915 redeemAddr, err := transparentAddress(w, w.addrParams, w.btcParams) 916 if err != nil { 917 return nil, nil, 0, fmt.Errorf("error getting new address from the wallet: %w", err) 918 } 919 pkScript, err := txscript.PayToAddrScript(redeemAddr) 920 if err != nil { 921 return nil, nil, 0, fmt.Errorf("error creating change script: %w", err) 922 } 923 val := totalIn - fee 924 txOut := wire.NewTxOut(int64(val), pkScript) 925 // One last check for dust. 926 if isDust(val, dexbtc.P2PKHOutputSize) { 927 return nil, nil, 0, fmt.Errorf("redeem output is dust") 928 } 929 tx.AddTxOut(txOut) 930 931 for i, r := range form.Redemptions { 932 contract := contracts[i] 933 addr := addresses[i] 934 935 addrStr, err := dexzec.EncodeAddress(addr, w.addrParams) 936 if err != nil { 937 return nil, nil, 0, fmt.Errorf("EncodeAddress error: %w", err) 938 } 939 940 privKey, err := dumpPrivKey(w, addrStr) 941 if err != nil { 942 return nil, nil, 0, fmt.Errorf("dumpPrivKey error: %w", err) 943 } 944 defer privKey.Zero() 945 946 redeemSig, err := signTx(tx.MsgTx, i, contract, txscript.SigHashAll, privKey, values, prevScripts) 947 if err != nil { 948 return nil, nil, 0, fmt.Errorf("tx signing error: %w", err) 949 } 950 redeemPubKey := privKey.PubKey().SerializeCompressed() 951 952 tx.TxIn[i].SignatureScript, err = dexbtc.RedeemP2SHContract(contract, redeemSig, redeemPubKey, r.Secret) 953 if err != nil { 954 return nil, nil, 0, fmt.Errorf("RedeemP2SHContract error: %w", err) 955 } 956 } 957 958 // Send the transaction. 959 txHash, err := sendRawTransaction(w, tx) 960 if err != nil { 961 return nil, nil, 0, fmt.Errorf("error sending tx: %w", err) 962 } 963 964 w.addTxToHistory(&asset.WalletTransaction{ 965 Type: asset.Redeem, 966 ID: txHash.String(), 967 Amount: totalIn, 968 Fees: fee, 969 }, txHash, true) 970 971 // Log the change output. 972 coinIDs := make([]dex.Bytes, 0, len(form.Redemptions)) 973 for i := range form.Redemptions { 974 coinIDs = append(coinIDs, btc.ToCoinID(txHash, uint32(i))) 975 } 976 return coinIDs, btc.NewOutput(txHash, 0, uint64(txOut.Value)), fee, nil 977 } 978 979 // scriptHashAddress returns a new p2sh address. 980 func (w *zecWallet) scriptHashAddress(contract []byte) (btcutil.Address, error) { 981 return btcutil.NewAddressScriptHash(contract, w.btcParams) 982 } 983 984 func (w *zecWallet) scriptHashScript(contract []byte) ([]byte, error) { 985 addr, err := w.scriptHashAddress(contract) 986 if err != nil { 987 return nil, err 988 } 989 return txscript.PayToAddrScript(addr) 990 } 991 992 func (w *zecWallet) ReturnCoins(unspents asset.Coins) error { 993 return w.cm.ReturnCoins(unspents) 994 } 995 996 func (w *zecWallet) MaxOrder(ord *asset.MaxOrderForm) (*asset.SwapEstimate, error) { 997 _, _, maxEst, err := w.maxOrder(ord.LotSize, ord.FeeSuggestion, ord.MaxFeeRate) 998 return maxEst, err 999 } 1000 1001 func (w *zecWallet) maxOrder(lotSize, feeSuggestion, maxFeeRate uint64) (utxos []*btc.CompositeUTXO, bals *balances, est *asset.SwapEstimate, err error) { 1002 if lotSize == 0 { 1003 return nil, nil, nil, errors.New("cannot divide by lotSize zero") 1004 } 1005 1006 utxos, _, avail, err := w.cm.SpendableUTXOs(0) 1007 if err != nil { 1008 return nil, nil, nil, fmt.Errorf("error parsing unspent outputs: %w", err) 1009 } 1010 1011 bals, err = w.balances() 1012 if err != nil { 1013 return nil, nil, nil, fmt.Errorf("error getting current balance: %w", err) 1014 } 1015 1016 avail += bals.orchard.avail 1017 1018 // Find the max lots we can fund. 1019 maxLotsInt := int(avail / lotSize) 1020 oneLotTooMany := sort.Search(maxLotsInt+1, func(lots int) bool { 1021 _, _, _, err = w.estimateSwap(uint64(lots), lotSize, feeSuggestion, maxFeeRate, utxos, bals.orchard, true) 1022 // The only failure mode of estimateSwap -> zec.fund is when there is 1023 // not enough funds. 1024 return err != nil 1025 }) 1026 1027 maxLots := uint64(oneLotTooMany - 1) 1028 if oneLotTooMany == 0 { 1029 maxLots = 0 1030 } 1031 1032 if maxLots > 0 { 1033 est, _, _, err = w.estimateSwap(maxLots, lotSize, feeSuggestion, maxFeeRate, utxos, bals.orchard, true) 1034 return utxos, bals, est, err 1035 } 1036 1037 return utxos, bals, &asset.SwapEstimate{FeeReservesPerLot: feeReservesPerLot}, nil 1038 } 1039 1040 // estimateSwap prepares an *asset.SwapEstimate. 1041 func (w *zecWallet) estimateSwap( 1042 lots, lotSize, feeSuggestion, maxFeeRate uint64, 1043 utxos []*btc.CompositeUTXO, 1044 orchardBal *balanceBreakdown, 1045 trySplit bool, 1046 ) (*asset.SwapEstimate, bool /*split used*/, uint64 /*amt locked*/, error) { 1047 1048 var avail uint64 1049 for _, utxo := range utxos { 1050 avail += utxo.Amount 1051 } 1052 val := lots * lotSize 1053 sum, inputsSize, shieldedSplitNeeded, shieldedSplitFees, coins, _, _, _, err := w.fund(val, lots, utxos, orchardBal) 1054 if err != nil { 1055 return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", btcutil.Amount(val), err) 1056 } 1057 1058 if shieldedSplitNeeded > 0 { 1059 // DRAFT TODO: Do we need to "lock" anything here? 1060 const splitLocked = 0 1061 return &asset.SwapEstimate{ 1062 Lots: lots, 1063 Value: val, 1064 MaxFees: shieldedSplitFees, 1065 RealisticBestCase: shieldedSplitFees, 1066 RealisticWorstCase: shieldedSplitFees, 1067 FeeReservesPerLot: feeReservesPerLot, 1068 }, true, splitLocked, nil 1069 } 1070 1071 digestInputs := func(inputsSize uint64, withSplit bool) (reqFunds, maxFees, estHighFees, estLowFees uint64) { 1072 n := uint64(len(coins)) 1073 1074 splitFees := shieldedSplitFees 1075 if withSplit { 1076 splitInputsSize := inputsSize + uint64(wire.VarIntSerializeSize(n)) 1077 splitOutputsSize := uint64(2*dexbtc.P2PKHOutputSize + 1) 1078 splitFees = dexzec.TxFeesZIP317(splitInputsSize, splitOutputsSize, 0, 0, 0, 0) 1079 inputsSize = dexbtc.RedeemP2PKHInputSize 1080 n = 1 1081 } 1082 1083 firstSwapInputsSize := inputsSize + uint64(wire.VarIntSerializeSize(n)) 1084 singleOutputSize := uint64(dexbtc.P2SHOutputSize+dexbtc.P2PKHOutputSize) + 1 1085 estLowFees = dexzec.TxFeesZIP317(firstSwapInputsSize, singleOutputSize, 0, 0, 0, 0) 1086 1087 req := dexzec.RequiredOrderFunds(val, n, inputsSize, lots) 1088 maxFees = req - val + splitFees 1089 estHighFees = maxFees 1090 return 1091 } 1092 1093 // Math for split transactions is a little different. 1094 if trySplit { 1095 baggage := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0) 1096 excess := sum - dexzec.RequiredOrderFunds(val, uint64(len(coins)), inputsSize, lots) 1097 if baggage >= excess { 1098 reqFunds, maxFees, estHighFees, estLowFees := digestInputs(inputsSize, true) 1099 return &asset.SwapEstimate{ 1100 Lots: lots, 1101 Value: val, 1102 MaxFees: maxFees, 1103 RealisticBestCase: estLowFees, 1104 RealisticWorstCase: estHighFees, 1105 FeeReservesPerLot: feeReservesPerLot, 1106 }, true, reqFunds, nil 1107 } 1108 } 1109 1110 _, maxFees, estHighFees, estLowFees := digestInputs(inputsSize, false) 1111 return &asset.SwapEstimate{ 1112 Lots: lots, 1113 Value: val, 1114 MaxFees: maxFees, 1115 RealisticBestCase: estLowFees, 1116 RealisticWorstCase: estHighFees, 1117 FeeReservesPerLot: feeReservesPerLot, 1118 }, false, sum, nil 1119 } 1120 1121 // PreSwap get order estimates and order options based on the available funds 1122 // and user-selected options. 1123 func (w *zecWallet) PreSwap(req *asset.PreSwapForm) (*asset.PreSwap, error) { 1124 // Start with the maxOrder at the default configuration. This gets us the 1125 // utxo set, the network fee rate, and the wallet's maximum order size. The 1126 // utxo set can then be used repeatedly in estimateSwap at virtually zero 1127 // cost since there are no more RPC calls. 1128 utxos, bals, maxEst, err := w.maxOrder(req.LotSize, req.FeeSuggestion, req.MaxFeeRate) 1129 if err != nil { 1130 return nil, err 1131 } 1132 if maxEst.Lots < req.Lots { 1133 return nil, fmt.Errorf("%d lots available for %d-lot order", maxEst.Lots, req.Lots) 1134 } 1135 1136 // Load the user's selected order-time options. 1137 customCfg := new(swapOptions) 1138 err = config.Unmapify(req.SelectedOptions, customCfg) 1139 if err != nil { 1140 return nil, fmt.Errorf("error parsing selected swap options: %w", err) 1141 } 1142 1143 // Parse the configured split transaction. 1144 useSplit := w.useSplitTx() 1145 if customCfg.Split != nil { 1146 useSplit = *customCfg.Split 1147 } 1148 1149 // Always offer the split option, even for non-standing orders since 1150 // immediately spendable change many be desirable regardless. 1151 opts := []*asset.OrderOption{w.splitOption(req, utxos, bals.orchard)} 1152 1153 est, _, _, err := w.estimateSwap(req.Lots, req.LotSize, req.FeeSuggestion, req.MaxFeeRate, utxos, bals.orchard, useSplit) 1154 if err != nil { 1155 return nil, err 1156 } 1157 return &asset.PreSwap{ 1158 Estimate: est, // may be nil so we can present options, which in turn affect estimate feasibility 1159 Options: opts, 1160 }, nil 1161 } 1162 1163 // splitOption constructs an *asset.OrderOption with customized text based on the 1164 // difference in fees between the configured and test split condition. 1165 func (w *zecWallet) splitOption(req *asset.PreSwapForm, utxos []*btc.CompositeUTXO, orchardBal *balanceBreakdown) *asset.OrderOption { 1166 opt := &asset.OrderOption{ 1167 ConfigOption: asset.ConfigOption{ 1168 Key: "swapsplit", 1169 DisplayName: "Pre-size Funds", 1170 IsBoolean: true, 1171 DefaultValue: strconv.FormatBool(w.useSplitTx()), // not nil interface 1172 ShowByDefault: true, 1173 }, 1174 Boolean: &asset.BooleanConfig{}, 1175 } 1176 1177 noSplitEst, _, noSplitLocked, err := w.estimateSwap(req.Lots, req.LotSize, 1178 req.FeeSuggestion, req.MaxFeeRate, utxos, orchardBal, false) 1179 if err != nil { 1180 w.log.Errorf("estimateSwap (no split) error: %v", err) 1181 opt.Boolean.Reason = fmt.Sprintf("estimate without a split failed with \"%v\"", err) 1182 return opt // utility and overlock report unavailable, but show the option 1183 } 1184 splitEst, splitUsed, splitLocked, err := w.estimateSwap(req.Lots, req.LotSize, 1185 req.FeeSuggestion, req.MaxFeeRate, utxos, orchardBal, true) 1186 if err != nil { 1187 w.log.Errorf("estimateSwap (with split) error: %v", err) 1188 opt.Boolean.Reason = fmt.Sprintf("estimate with a split failed with \"%v\"", err) 1189 return opt // utility and overlock report unavailable, but show the option 1190 } 1191 1192 if !splitUsed || splitLocked >= noSplitLocked { // locked check should be redundant 1193 opt.Boolean.Reason = "avoids no ZEC overlock for this order (ignored)" 1194 opt.Description = "A split transaction for this order avoids no ZEC overlock, " + 1195 "but adds additional fees." 1196 opt.DefaultValue = "false" 1197 return opt // not enabled by default, but explain why 1198 } 1199 1200 overlock := noSplitLocked - splitLocked 1201 pctChange := (float64(splitEst.RealisticWorstCase)/float64(noSplitEst.RealisticWorstCase) - 1) * 100 1202 if pctChange > 1 { 1203 opt.Boolean.Reason = fmt.Sprintf("+%d%% fees, avoids %s ZEC overlock", int(math.Round(pctChange)), btcutil.Amount(overlock).String()) 1204 } else { 1205 opt.Boolean.Reason = fmt.Sprintf("+%.1f%% fees, avoids %s ZEC overlock", pctChange, btcutil.Amount(overlock).String()) 1206 } 1207 1208 xtraFees := splitEst.RealisticWorstCase - noSplitEst.RealisticWorstCase 1209 opt.Description = fmt.Sprintf("Using a split transaction to prevent temporary overlock of %s ZEC, but for additional fees of %s ZEC", 1210 btcutil.Amount(overlock).String(), btcutil.Amount(xtraFees).String()) 1211 1212 return opt 1213 } 1214 1215 // SingleLotSwapRefundFees returns the fees for a swap and refund transaction 1216 // for a single lot. 1217 func (w *zecWallet) SingleLotSwapRefundFees(_ uint32, feeSuggestion uint64, useSafeTxSize bool) (swapFees uint64, refundFees uint64, err error) { 1218 var numInputs uint64 1219 if useSafeTxSize { 1220 numInputs = 12 1221 } else { 1222 numInputs = 2 1223 } 1224 1225 inputsSize := numInputs*dexbtc.RedeemP2PKHInputSize + 1 1226 outputsSize := uint64(dexbtc.P2PKHOutputSize + 1) 1227 swapFees = dexzec.TxFeesZIP317(inputsSize, outputsSize, 0, 0, 0, 0) 1228 refundFees = dexzec.TxFeesZIP317(dexbtc.RefundSigScriptSize+1, dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0) 1229 1230 return swapFees, refundFees, nil 1231 } 1232 1233 func (w *zecWallet) PreRedeem(form *asset.PreRedeemForm) (*asset.PreRedeem, error) { 1234 return w.preRedeem(form.Lots, form.FeeSuggestion, form.SelectedOptions) 1235 } 1236 1237 func (w *zecWallet) preRedeem(numLots, _ uint64, options map[string]string) (*asset.PreRedeem, error) { 1238 singleInputsSize := uint64(dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize + 1) 1239 singleOutputsSize := uint64(dexbtc.P2PKHOutputSize + 1) 1240 singleMatchFees := dexzec.TxFeesZIP317(singleInputsSize, singleOutputsSize, 0, 0, 0, 0) 1241 return &asset.PreRedeem{ 1242 Estimate: &asset.RedeemEstimate{ 1243 RealisticWorstCase: singleMatchFees * numLots, 1244 RealisticBestCase: singleMatchFees, 1245 }, 1246 }, nil 1247 } 1248 1249 func (w *zecWallet) SingleLotRedeemFees(_ uint32, feeSuggestion uint64) (uint64, error) { 1250 singleInputsSize := uint64(dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize + 1) 1251 singleOutputsSize := uint64(dexbtc.P2PKHOutputSize + 1) 1252 return dexzec.TxFeesZIP317(singleInputsSize, singleOutputsSize, 0, 0, 0, 0), nil 1253 } 1254 1255 // FundingCoins gets funding coins for the coin IDs. The coins are locked. This 1256 // method might be called to reinitialize an order from data stored externally. 1257 // This method will only return funding coins, e.g. unspent transaction outputs. 1258 func (w *zecWallet) FundingCoins(ids []dex.Bytes) (asset.Coins, error) { 1259 return w.cm.FundingCoins(ids) 1260 } 1261 1262 func decodeCoinID(coinID dex.Bytes) (*chainhash.Hash, uint32, error) { 1263 if len(coinID) != 36 { 1264 return nil, 0, fmt.Errorf("coin ID wrong length. expected 36, got %d", len(coinID)) 1265 } 1266 var txHash chainhash.Hash 1267 copy(txHash[:], coinID[:32]) 1268 return &txHash, binary.BigEndian.Uint32(coinID[32:]), nil 1269 } 1270 1271 // fundedTx creates and returns a new MsgTx with the provided coins as inputs. 1272 func (w *zecWallet) fundedTx(coins []*btc.Output) (*dexzec.Tx, uint64, []btc.OutPoint, error) { 1273 baseTx := zecTx(wire.NewMsgTx(dexzec.VersionNU5)) 1274 totalIn, pts, err := w.addInputsToTx(baseTx, coins) 1275 if err != nil { 1276 return nil, 0, nil, err 1277 } 1278 return baseTx, totalIn, pts, nil 1279 } 1280 1281 func (w *zecWallet) addInputsToTx(tx *dexzec.Tx, coins []*btc.Output) (uint64, []btc.OutPoint, error) { 1282 var totalIn uint64 1283 // Add the funding utxos. 1284 pts := make([]btc.OutPoint, 0, len(coins)) 1285 for _, op := range coins { 1286 totalIn += op.Val 1287 txIn := wire.NewTxIn(op.WireOutPoint(), []byte{}, nil) 1288 tx.AddTxIn(txIn) 1289 pts = append(pts, op.Pt) 1290 } 1291 return totalIn, pts, nil 1292 } 1293 1294 // signTxAndAddChange signs the passed tx and adds a change output if the change 1295 // wouldn't be dust. Returns but does NOT broadcast the signed tx. 1296 func (w *zecWallet) signTxAndAddChange(baseTx *dexzec.Tx, addr btcutil.Address, totalIn, totalOut, fees uint64) (*dexzec.Tx, *btc.Output, error) { 1297 1298 makeErr := func(s string, a ...any) (*dexzec.Tx, *btc.Output, error) { 1299 return nil, nil, fmt.Errorf(s, a...) 1300 } 1301 1302 // Sign the transaction to get an initial size estimate and calculate whether 1303 // a change output would be dust. 1304 remaining := totalIn - totalOut 1305 if fees > remaining { 1306 b, _ := baseTx.Bytes() 1307 return makeErr("not enough funds to cover minimum fee rate. %s < %s, raw tx: %x", 1308 btcutil.Amount(totalIn), btcutil.Amount(fees+totalOut), b) 1309 } 1310 1311 // Create a change output. 1312 changeScript, err := txscript.PayToAddrScript(addr) 1313 if err != nil { 1314 return makeErr("error creating change script: %v", err) 1315 } 1316 1317 changeIdx := len(baseTx.TxOut) 1318 changeValue := remaining - fees 1319 changeOutput := wire.NewTxOut(int64(changeValue), changeScript) 1320 1321 // If the change is not dust, recompute the signed txn size and iterate on 1322 // the fees vs. change amount. 1323 changeAdded := !isDust(changeValue, dexbtc.P2PKHOutputSize) 1324 if changeAdded { 1325 // Add the change output. 1326 baseTx.AddTxOut(changeOutput) 1327 } else { 1328 w.log.Debugf("Foregoing change worth up to %v because it is dust", changeOutput.Value) 1329 } 1330 1331 msgTx, err := signTxByRPC(w, baseTx) 1332 if err != nil { 1333 b, _ := baseTx.Bytes() 1334 return makeErr("signing error: %v, raw tx: %x", err, b) 1335 } 1336 1337 txHash := msgTx.TxHash() 1338 var change *btc.Output 1339 if changeAdded { 1340 change = btc.NewOutput(&txHash, uint32(changeIdx), uint64(changeOutput.Value)) 1341 } 1342 1343 return msgTx, change, nil 1344 } 1345 1346 type balanceBreakdown struct { 1347 avail uint64 1348 maturing uint64 1349 noteCount uint32 1350 } 1351 1352 type balances struct { 1353 orchard *balanceBreakdown 1354 sapling uint64 1355 transparent *balanceBreakdown 1356 } 1357 1358 func (b *balances) available() uint64 { 1359 return b.orchard.avail + b.transparent.avail 1360 } 1361 1362 func (w *zecWallet) balances() (*balances, error) { 1363 zeroConf, err := zGetBalanceForAccount(w, shieldedAcctNumber, 0) 1364 if err != nil { 1365 return nil, fmt.Errorf("z_getbalanceforaccount (0) error: %w", err) 1366 } 1367 mature, err := zGetBalanceForAccount(w, shieldedAcctNumber, minOrchardConfs) 1368 if err != nil { 1369 return nil, fmt.Errorf("z_getbalanceforaccount (3) error: %w", err) 1370 } 1371 noteCounts, err := zGetNotesCount(w) 1372 if err != nil { 1373 return nil, fmt.Errorf("z_getnotescount error: %w", err) 1374 } 1375 return &balances{ 1376 orchard: &balanceBreakdown{ 1377 avail: mature.Orchard, 1378 maturing: zeroConf.Orchard - mature.Orchard, 1379 noteCount: noteCounts.Orchard, 1380 }, 1381 sapling: zeroConf.Sapling, 1382 transparent: &balanceBreakdown{ 1383 avail: mature.Transparent, 1384 maturing: zeroConf.Transparent - mature.Transparent, 1385 }, 1386 }, nil 1387 } 1388 1389 func (w *zecWallet) fund( 1390 val, maxSwapCount uint64, 1391 utxos []*btc.CompositeUTXO, 1392 orchardBal *balanceBreakdown, 1393 ) ( 1394 sum, size, shieldedSplitNeeded, shieldedSplitFees uint64, 1395 coins asset.Coins, 1396 fundingCoins map[btc.OutPoint]*btc.UTxO, 1397 redeemScripts []dex.Bytes, 1398 spents []*btc.Output, 1399 err error, 1400 ) { 1401 1402 var shieldedAvail uint64 1403 reserves := w.reserves.Load() 1404 nActionsOrchard := uint64(orchardBal.noteCount) 1405 enough := func(inputCount, inputsSize, sum uint64) (bool, uint64) { 1406 req := dexzec.RequiredOrderFunds(val, inputCount, inputsSize, maxSwapCount) 1407 if sum >= req { 1408 return true, sum - req + shieldedAvail 1409 } 1410 if shieldedAvail == 0 { 1411 return false, 0 1412 } 1413 shieldedFees := dexzec.TxFeesZIP317(inputsSize+uint64(wire.VarIntSerializeSize(inputCount)), dexbtc.P2PKHOutputSize+1, 0, 0, 0, nActionsOrchard) 1414 req = dexzec.RequiredOrderFunds(val, 1, dexbtc.RedeemP2PKHInputSize, maxSwapCount) 1415 if sum+shieldedAvail >= req+shieldedFees { 1416 return true, sum + shieldedAvail - (req + shieldedFees) 1417 } 1418 return false, 0 1419 } 1420 1421 coins, fundingCoins, spents, redeemScripts, size, sum, err = w.cm.FundWithUTXOs(utxos, reserves, false, enough) 1422 if err == nil { 1423 return 1424 } 1425 1426 shieldedAvail = orchardBal.avail 1427 1428 if shieldedAvail >= reserves { 1429 shieldedAvail -= reserves 1430 reserves = 0 1431 } else { 1432 reserves -= shieldedAvail 1433 shieldedAvail = 0 1434 } 1435 1436 if shieldedAvail == 0 { 1437 err = codedError(errFunding, err) 1438 return 1439 } 1440 1441 shieldedSplitNeeded = dexzec.RequiredOrderFunds(val, 1, dexbtc.RedeemP2PKHInputSize, maxSwapCount) 1442 1443 // If we don't have any utxos see if a straight shielded split will get us there. 1444 if len(utxos) == 0 { 1445 // Can we do it with just the shielded balance? 1446 shieldedSplitFees = dexzec.TxFeesZIP317(0, dexbtc.P2SHOutputSize+1, 0, 0, 0, nActionsOrchard) 1447 req := val + shieldedSplitFees 1448 if shieldedAvail < req { 1449 // err is still the error from the last call to FundWithUTXOs 1450 err = codedError(errShieldedFunding, err) 1451 } else { 1452 err = nil 1453 } 1454 return 1455 } 1456 1457 // Check with both transparent and orchard funds. (shieldedAvail has been 1458 // set, which changes the behavior of enough. 1459 coins, fundingCoins, spents, redeemScripts, size, sum, err = w.cm.FundWithUTXOs(utxos, reserves, false, enough) 1460 if err != nil { 1461 err = codedError(errFunding, err) 1462 return 1463 } 1464 1465 req := dexzec.RequiredOrderFunds(val, uint64(len(coins)), size, maxSwapCount) 1466 if req > sum { 1467 txOutsSize := uint64(dexbtc.P2PKHOutputSize + 1) // 1 for varint 1468 shieldedSplitFees = dexzec.TxFeesZIP317(size+uint64(wire.VarIntSerializeSize(uint64(len(coins)))), txOutsSize, 0, 0, 0, nActionsOrchard) 1469 } 1470 1471 if shieldedAvail+sum < shieldedSplitNeeded+shieldedSplitFees { 1472 err = newError(errInsufficientBalance, "not enough to cover requested funds. "+ 1473 "%d available in %d UTXOs, %d available from shielded (after bond reserves), total avail = %d, total needed = %d", 1474 sum, len(coins), shieldedAvail, shieldedAvail+sum, shieldedSplitNeeded+shieldedSplitFees) 1475 return 1476 } 1477 1478 return 1479 } 1480 1481 func (w *zecWallet) SetBondReserves(reserves uint64) { 1482 w.reserves.Store(reserves) 1483 } 1484 1485 func (w *zecWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroadcast bool) (*asset.AuditInfo, error) { 1486 txHash, vout, err := decodeCoinID(coinID) 1487 if err != nil { 1488 return nil, err 1489 } 1490 // Get the receiving address. 1491 _, receiver, stamp, secretHash, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams) 1492 if err != nil { 1493 return nil, fmt.Errorf("error extracting swap addresses: %w", err) 1494 } 1495 1496 // If no tx data is provided, attempt to get the required data (the txOut) 1497 // from the wallet. If this is a full node wallet, a simple gettxout RPC is 1498 // sufficient with no pkScript or "since" time. If this is an SPV wallet, 1499 // only a confirmed counterparty contract can be located, and only one 1500 // within ContractSearchLimit. As such, this mode of operation is not 1501 // intended for normal server-coordinated operation. 1502 var tx *dexzec.Tx 1503 var txOut *wire.TxOut 1504 if len(txData) == 0 { 1505 // Fall back to gettxout, but we won't have the tx to rebroadcast. 1506 txOut, _, err = getTxOut(w, txHash, vout) 1507 if err != nil || txOut == nil { 1508 return nil, fmt.Errorf("error finding unspent contract: %s:%d : %w", txHash, vout, err) 1509 } 1510 } else { 1511 tx, err = dexzec.DeserializeTx(txData) 1512 if err != nil { 1513 return nil, fmt.Errorf("coin not found, and error encountered decoding tx data: %v", err) 1514 } 1515 if len(tx.TxOut) <= int(vout) { 1516 return nil, fmt.Errorf("specified output %d not found in decoded tx %s", vout, txHash) 1517 } 1518 txOut = tx.TxOut[vout] 1519 } 1520 1521 // Check for standard P2SH. NOTE: btc.scriptHashScript(contract) should 1522 // equal txOut.PkScript. All we really get from the TxOut is the *value*. 1523 scriptClass, addrs, numReq, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams) 1524 if err != nil { 1525 return nil, fmt.Errorf("error extracting script addresses from '%x': %w", txOut.PkScript, err) 1526 } 1527 if scriptClass != txscript.ScriptHashTy { 1528 return nil, fmt.Errorf("unexpected script class. expected %s, got %s", txscript.ScriptHashTy, scriptClass) 1529 } 1530 // Compare the contract hash to the P2SH address. 1531 contractHash := btcutil.Hash160(contract) 1532 // These last two checks are probably overkill. 1533 if numReq != 1 { 1534 return nil, fmt.Errorf("unexpected number of signatures expected for P2SH script: %d", numReq) 1535 } 1536 if len(addrs) != 1 { 1537 return nil, fmt.Errorf("unexpected number of addresses for P2SH script: %d", len(addrs)) 1538 } 1539 1540 addr := addrs[0] 1541 if !bytes.Equal(contractHash, addr.ScriptAddress()) { 1542 return nil, fmt.Errorf("contract hash doesn't match script address. %x != %x", 1543 contractHash, addr.ScriptAddress()) 1544 } 1545 1546 // Broadcast the transaction, but do not block because this is not required 1547 // and does not affect the audit result. 1548 if rebroadcast && tx != nil { 1549 go func() { 1550 if hashSent, err := sendRawTransaction(w, tx); err != nil { 1551 w.log.Debugf("Rebroadcasting counterparty contract %v (THIS MAY BE NORMAL): %v", txHash, err) 1552 } else if !hashSent.IsEqual(txHash) { 1553 w.log.Errorf("Counterparty contract %v was rebroadcast as %v!", txHash, hashSent) 1554 } 1555 }() 1556 } 1557 1558 addrStr, err := dexzec.EncodeAddress(receiver, w.addrParams) 1559 if err != nil { 1560 w.log.Errorf("Failed to stringify receiver address %v (default): %v", receiver, err) 1561 } 1562 1563 return &asset.AuditInfo{ 1564 Coin: btc.NewOutput(txHash, vout, uint64(txOut.Value)), 1565 Recipient: addrStr, 1566 Contract: contract, 1567 SecretHash: secretHash, 1568 Expiration: time.Unix(int64(stamp), 0).UTC(), 1569 }, nil 1570 } 1571 1572 func (w *zecWallet) ConfirmRedemption(coinID dex.Bytes, redemption *asset.Redemption, feeSuggestion uint64) (*asset.ConfirmRedemptionStatus, error) { 1573 txHash, _, err := decodeCoinID(coinID) 1574 if err != nil { 1575 return nil, err 1576 } 1577 1578 tx, err := getWalletTransaction(w, txHash) 1579 // redemption transaction found, return its confirms. 1580 // 1581 // TODO: Investigate the case where this redeem has been sitting in the 1582 // mempool for a long amount of time, possibly requiring some action by 1583 // us to get it unstuck. 1584 if err == nil { 1585 if tx.Confirmations < 0 { 1586 tx.Confirmations = 0 1587 } 1588 return &asset.ConfirmRedemptionStatus{ 1589 Confs: uint64(tx.Confirmations), 1590 Req: requiredRedeemConfirms, 1591 CoinID: coinID, 1592 }, nil 1593 } 1594 1595 // Redemption transaction is missing from the point of view of our node! 1596 // Unlikely, but possible it was redeemed by another transaction. Check 1597 // if the contract is still an unspent output. 1598 1599 swapHash, vout, err := decodeCoinID(redemption.Spends.Coin.ID()) 1600 if err != nil { 1601 return nil, err 1602 } 1603 1604 utxo, _, err := getTxOut(w, swapHash, vout) 1605 if err != nil { 1606 return nil, newError(errNoTx, "error finding unspent contract %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err) 1607 } 1608 if utxo == nil { 1609 // TODO: Spent, but by who. Find the spending tx. 1610 w.log.Warnf("Contract coin %v with swap hash %v vout %d spent by someone but not sure who.", redemption.Spends.Coin.ID(), swapHash, vout) 1611 // Incorrect, but we will be in a loop of erroring if we don't 1612 // return something. 1613 return &asset.ConfirmRedemptionStatus{ 1614 Confs: requiredRedeemConfirms, 1615 Req: requiredRedeemConfirms, 1616 CoinID: coinID, 1617 }, nil 1618 } 1619 1620 // The contract has not yet been redeemed, but it seems the redeeming 1621 // tx has disappeared. Assume the fee was too low at the time and it 1622 // was eventually purged from the mempool. Attempt to redeem again with 1623 // a currently reasonable fee. 1624 1625 form := &asset.RedeemForm{ 1626 Redemptions: []*asset.Redemption{redemption}, 1627 } 1628 _, coin, _, err := w.Redeem(form) 1629 if err != nil { 1630 return nil, fmt.Errorf("unable to re-redeem %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err) 1631 } 1632 return &asset.ConfirmRedemptionStatus{ 1633 Confs: 0, 1634 Req: requiredRedeemConfirms, 1635 CoinID: coin.ID(), 1636 }, nil 1637 } 1638 1639 func (w *zecWallet) ContractLockTimeExpired(ctx context.Context, contract dex.Bytes) (bool, time.Time, error) { 1640 _, _, locktime, _, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams) 1641 if err != nil { 1642 return false, time.Time{}, fmt.Errorf("error extracting contract locktime: %w", err) 1643 } 1644 contractExpiry := time.Unix(int64(locktime), 0).UTC() 1645 expired, err := w.LockTimeExpired(ctx, contractExpiry) 1646 if err != nil { 1647 return false, time.Time{}, err 1648 } 1649 return expired, contractExpiry, nil 1650 } 1651 1652 type depositAddressJSON struct { 1653 Unified string `json:"unified"` 1654 Transparent string `json:"transparent"` 1655 Orchard string `json:"orchard"` 1656 Sapling string `json:"sapling,omitempty"` 1657 } 1658 1659 func (w *zecWallet) DepositAddress() (string, error) { 1660 addrRes, err := zGetAddressForAccount(w, shieldedAcctNumber, []string{transparentAddressType, orchardAddressType}) 1661 if err != nil { 1662 return "", err 1663 } 1664 receivers, err := zGetUnifiedReceivers(w, addrRes.Address) 1665 if err != nil { 1666 return "", err 1667 } 1668 b, err := json.Marshal(&depositAddressJSON{ 1669 Unified: addrRes.Address, 1670 Transparent: receivers.Transparent, 1671 Orchard: receivers.Orchard, 1672 Sapling: receivers.Sapling, 1673 }) 1674 if err != nil { 1675 return "", fmt.Errorf("error encoding address JSON: %w", err) 1676 } 1677 return depositAddrPrefix + string(b), nil 1678 1679 } 1680 1681 func (w *zecWallet) NewAddress() (string, error) { 1682 return w.DepositAddress() 1683 } 1684 1685 // AddressUsed checks if a wallet address has been used. 1686 func (w *zecWallet) AddressUsed(addrStr string) (bool, error) { 1687 // TODO: Resolve with new unified address encoding in https://github.com/decred/dcrdex/pull/2675 1688 recv, err := getReceivedByAddress(w, addrStr) 1689 return recv != 0, err 1690 } 1691 1692 func (w *zecWallet) FindRedemption(ctx context.Context, coinID, contract dex.Bytes) (redemptionCoin, secret dex.Bytes, err error) { 1693 return w.rf.FindRedemption(ctx, coinID) 1694 } 1695 1696 func (w *zecWallet) FundMultiOrder(mo *asset.MultiOrder, maxLock uint64) (coins []asset.Coins, redeemScripts [][]dex.Bytes, fundingFees uint64, err error) { 1697 w.log.Debugf("Attempting to fund a multi-order for ZEC") 1698 1699 var totalRequiredForOrders uint64 1700 var swapInputSize uint64 = dexbtc.RedeemP2PKHInputSize 1701 for _, v := range mo.Values { 1702 if v.Value == 0 { 1703 return nil, nil, 0, newError(errBadInput, "cannot fund value = 0") 1704 } 1705 if v.MaxSwapCount == 0 { 1706 return nil, nil, 0, fmt.Errorf("cannot fund zero-lot order") 1707 } 1708 req := dexzec.RequiredOrderFunds(v.Value, 1, swapInputSize, v.MaxSwapCount) 1709 totalRequiredForOrders += req 1710 } 1711 1712 if maxLock < totalRequiredForOrders && maxLock != 0 { 1713 return nil, nil, 0, newError(errMaxLock, "maxLock < totalRequiredForOrders (%d < %d)", maxLock, totalRequiredForOrders) 1714 } 1715 1716 bal, err := w.Balance() 1717 if err != nil { 1718 return nil, nil, 0, newError(errFunding, "error getting wallet balance: %w", err) 1719 } 1720 if bal.Available < totalRequiredForOrders { 1721 return nil, nil, 0, newError(errInsufficientBalance, "insufficient funds. %d < %d", bal.Available, totalRequiredForOrders) 1722 } 1723 1724 reserves := w.reserves.Load() 1725 1726 const multiSplitAllowed = true 1727 1728 coins, redeemScripts, fundingCoins, spents, err := w.cm.FundMultiBestEffort(reserves, maxLock, mo.Values, mo.MaxFeeRate, multiSplitAllowed) 1729 if err != nil { 1730 return nil, nil, 0, codedError(errFunding, err) 1731 } 1732 if len(coins) == len(mo.Values) { 1733 w.cm.LockOutputsMap(fundingCoins) 1734 lockUnspent(w, false, spents) 1735 return coins, redeemScripts, 0, nil 1736 } 1737 1738 recips := make([]*zSendManyRecipient, len(mo.Values)) 1739 addrs := make([]string, len(mo.Values)) 1740 orderReqs := make([]uint64, len(mo.Values)) 1741 var txWasBroadcast bool 1742 defer func() { 1743 if txWasBroadcast || len(addrs) == 0 { 1744 return 1745 } 1746 w.ar.ReturnAddresses(addrs) 1747 }() 1748 for i, v := range mo.Values { 1749 addr, err := w.recyclableAddress() 1750 if err != nil { 1751 return nil, nil, 0, fmt.Errorf("error getting address for split tx: %v", err) 1752 } 1753 orderReqs[i] = dexzec.RequiredOrderFunds(v.Value, 1, dexbtc.RedeemP2PKHInputSize, v.MaxSwapCount) 1754 addrs[i] = addr 1755 recips[i] = &zSendManyRecipient{Address: addr, Amount: btcutil.Amount(orderReqs[i]).ToBTC()} 1756 } 1757 1758 txHash, err := w.sendManyShielded(recips) 1759 if err != nil { 1760 return nil, nil, 0, fmt.Errorf("sendManyShielded error: %w", err) 1761 } 1762 1763 tx, err := getTransaction(w, txHash) 1764 if err != nil { 1765 return nil, nil, 0, fmt.Errorf("error retreiving split transaction %s: %w", txHash, err) 1766 } 1767 1768 txWasBroadcast = true 1769 1770 fundingFees = tx.RequiredTxFeesZIP317() 1771 1772 txOuts := make(map[uint32]*wire.TxOut, len(mo.Values)) 1773 for vout, txOut := range tx.TxOut { 1774 txOuts[uint32(vout)] = txOut 1775 } 1776 1777 coins = make([]asset.Coins, len(mo.Values)) 1778 utxos := make([]*btc.UTxO, len(mo.Values)) 1779 ops := make([]*btc.Output, len(mo.Values)) 1780 redeemScripts = make([][]dex.Bytes, len(mo.Values)) 1781 next: 1782 for i, v := range mo.Values { 1783 orderReq := orderReqs[i] 1784 for vout, txOut := range txOuts { 1785 if uint64(txOut.Value) == orderReq { 1786 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams) 1787 if err != nil { 1788 return nil, nil, 0, fmt.Errorf("error extracting addresses error: %w", err) 1789 } 1790 if len(addrs) != 1 { 1791 return nil, nil, 0, fmt.Errorf("unexpected multi-sig (%d)", len(addrs)) 1792 } 1793 addr := addrs[0] 1794 addrStr, err := dexzec.EncodeAddress(addr, w.addrParams) 1795 if err != nil { 1796 return nil, nil, 0, fmt.Errorf("error encoding Zcash transparent address: %w", err) 1797 } 1798 utxos[i] = &btc.UTxO{ 1799 TxHash: txHash, 1800 Vout: vout, 1801 Address: addrStr, 1802 Amount: orderReq, 1803 } 1804 ops[i] = btc.NewOutput(txHash, vout, orderReq) 1805 coins[i] = asset.Coins{ops[i]} 1806 redeemScripts[i] = []dex.Bytes{nil} 1807 delete(txOuts, vout) 1808 continue next 1809 } 1810 } 1811 return nil, nil, 0, fmt.Errorf("failed to find output coin for multisplit value %s at index %d", btcutil.Amount(v.Value), i) 1812 } 1813 w.cm.LockUTXOs(utxos) 1814 if err := lockUnspent(w, false, ops); err != nil { 1815 return nil, nil, 0, fmt.Errorf("error locking unspents: %w", err) 1816 } 1817 1818 return coins, redeemScripts, fundingFees, nil 1819 } 1820 1821 func (w *zecWallet) sendManyShielded(recips []*zSendManyRecipient) (*chainhash.Hash, error) { 1822 lastAddr, err := w.lastShieldedAddress() 1823 if err != nil { 1824 return nil, err 1825 } 1826 1827 operationID, err := zSendMany(w, lastAddr, recips, NoPrivacy) 1828 if err != nil { 1829 return nil, fmt.Errorf("z_sendmany error: %w", err) 1830 } 1831 1832 return w.awaitSendManyOperation(w.ctx, w, operationID) 1833 } 1834 1835 func (w *zecWallet) Info() *asset.WalletInfo { 1836 return WalletInfo 1837 } 1838 1839 func (w *zecWallet) LockTimeExpired(_ context.Context, lockTime time.Time) (bool, error) { 1840 chainStamper := func(blockHash *chainhash.Hash) (stamp time.Time, prevHash *chainhash.Hash, err error) { 1841 hdr, err := getBlockHeader(w, blockHash) 1842 if err != nil { 1843 return 1844 } 1845 return hdr.Timestamp, &hdr.PrevBlock, nil 1846 } 1847 1848 w.tipMtx.RLock() 1849 tip := w.currentTip 1850 w.tipMtx.RUnlock() 1851 1852 medianTime, err := btc.CalcMedianTime(chainStamper, &tip.Hash) // TODO: pass ctx 1853 if err != nil { 1854 return false, fmt.Errorf("error getting median time: %w", err) 1855 } 1856 return medianTime.After(lockTime), nil 1857 } 1858 1859 // fundMultiOptions are the possible order options when calling FundMultiOrder. 1860 type fundMultiOptions struct { 1861 // Split, if true, and multi-order cannot be funded with the existing UTXOs 1862 // in the wallet without going over the maxLock limit, a split transaction 1863 // will be created with one output per order. 1864 // 1865 // Use the multiSplitKey const defined above in the options map to set this option. 1866 Split bool `ini:"multisplit"` 1867 } 1868 1869 func decodeFundMultiSettings(settings map[string]string) (*fundMultiOptions, error) { 1870 opts := new(fundMultiOptions) 1871 return opts, config.Unmapify(settings, opts) 1872 } 1873 1874 func (w *zecWallet) MaxFundingFees(numTrades uint32, feeRate uint64, settings map[string]string) uint64 { 1875 customCfg, err := decodeFundMultiSettings(settings) 1876 if err != nil { 1877 w.log.Errorf("Error decoding multi-fund settings: %v", err) 1878 return 0 1879 } 1880 1881 // Assume a split from shielded 1882 txOutsSize := uint64(numTrades*dexbtc.P2PKHOutputSize + 1) // 1 for varint 1883 shieldedSplitFees := dexzec.TxFeesZIP317(0, txOutsSize, 0, 0, 0, nActionsOrchardEstimate) 1884 1885 if !customCfg.Split { 1886 return shieldedSplitFees 1887 } 1888 1889 return shieldedSplitFees + dexzec.TxFeesZIP317(1, uint64(numTrades+1)*dexbtc.P2PKHOutputSize, 0, 0, 0, 0) 1890 } 1891 1892 func (w *zecWallet) OwnsDepositAddress(addrStr string) (bool, error) { 1893 if strings.HasPrefix(addrStr, depositAddrPrefix) { 1894 var addrs depositAddressJSON 1895 if err := json.Unmarshal([]byte(addrStr[len(depositAddrPrefix):]), &addrs); err != nil { 1896 return false, fmt.Errorf("error decoding unified address info: %w", err) 1897 } 1898 addrStr = addrs.Unified 1899 } 1900 res, err := zValidateAddress(w, addrStr) 1901 if err != nil { 1902 return false, fmt.Errorf("error validating address: %w", err) 1903 } 1904 return res.IsMine, nil 1905 } 1906 1907 func (w *zecWallet) RedemptionAddress() (string, error) { 1908 return w.recyclableAddress() 1909 } 1910 1911 // A recyclable address is a redemption or refund address that may be recycled 1912 // if unused. If already recycled addresses are available, one will be returned. 1913 func (w *zecWallet) recyclableAddress() (string, error) { 1914 var returns []string 1915 defer w.ar.ReturnAddresses(returns) 1916 for { 1917 addr := w.ar.Address() 1918 if addr == "" { 1919 break 1920 } 1921 if owns, err := w.OwnsDepositAddress(addr); owns { 1922 return addr, nil 1923 } else if err != nil { 1924 w.log.Errorf("Error checking ownership of recycled address %q: %v", addr, err) 1925 returns = append(returns, addr) 1926 } 1927 } 1928 return transparentAddressString(w) 1929 } 1930 1931 func (w *zecWallet) Refund(coinID, contract dex.Bytes, feeRate uint64) (dex.Bytes, error) { 1932 txHash, vout, err := decodeCoinID(coinID) 1933 if err != nil { 1934 return nil, err 1935 } 1936 1937 // TODO: I'd recommend not passing a pkScript without a limited startTime 1938 // to prevent potentially long searches. In this case though, the output 1939 // will be found in the wallet and won't need to be searched for, only 1940 // the spender search will be conducted using the pkScript starting from 1941 // the block containing the original tx. The script can be gotten from 1942 // the wallet tx though and used for the spender search, while not passing 1943 // a script here to ensure no attempt is made to find the output without 1944 // a limited startTime. 1945 utxo, _, err := getTxOut(w, txHash, vout) 1946 if err != nil { 1947 return nil, fmt.Errorf("error finding unspent contract: %w", err) 1948 } 1949 if utxo == nil { 1950 return nil, asset.CoinNotFoundError // spent 1951 } 1952 msgTx, err := w.refundTx(txHash, vout, contract, uint64(utxo.Value), nil, feeRate) 1953 if err != nil { 1954 return nil, fmt.Errorf("error creating refund tx: %w", err) 1955 } 1956 1957 refundHash, err := w.broadcastTx(dexzec.NewTxFromMsgTx(msgTx, dexzec.MaxExpiryHeight)) 1958 if err != nil { 1959 return nil, fmt.Errorf("broadcastTx: %w", err) 1960 } 1961 1962 tx := zecTx(msgTx) 1963 w.addTxToHistory(&asset.WalletTransaction{ 1964 Type: asset.Refund, 1965 ID: refundHash.String(), 1966 Amount: uint64(utxo.Value), 1967 Fees: tx.RequiredTxFeesZIP317(), 1968 }, refundHash, true) 1969 1970 return btc.ToCoinID(refundHash, 0), nil 1971 } 1972 1973 func (w *zecWallet) broadcastTx(tx *dexzec.Tx) (*chainhash.Hash, error) { 1974 rawTx := func() string { 1975 b, err := tx.Bytes() 1976 if err != nil { 1977 return "serialization error: " + err.Error() 1978 } 1979 return dex.Bytes(b).String() 1980 } 1981 txHash, err := sendRawTransaction(w, tx) 1982 if err != nil { 1983 return nil, fmt.Errorf("sendrawtx error: %v: %s", err, rawTx()) 1984 } 1985 checkHash := tx.TxHash() 1986 if *txHash != checkHash { 1987 return nil, fmt.Errorf("transaction sent, but received unexpected transaction ID back from RPC server. "+ 1988 "expected %s, got %s. raw tx: %s", checkHash, txHash, rawTx()) 1989 } 1990 return txHash, nil 1991 } 1992 1993 func (w *zecWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract dex.Bytes, val uint64, refundAddr btcutil.Address, fees uint64) (*wire.MsgTx, error) { 1994 sender, _, lockTime, _, err := dexbtc.ExtractSwapDetails(contract, false, w.btcParams) 1995 if err != nil { 1996 return nil, fmt.Errorf("error extracting swap addresses: %w", err) 1997 } 1998 1999 // Create the transaction that spends the contract. 2000 msgTx := wire.NewMsgTx(dexzec.VersionNU5) 2001 msgTx.LockTime = uint32(lockTime) 2002 prevOut := wire.NewOutPoint(txHash, vout) 2003 txIn := wire.NewTxIn(prevOut, []byte{}, nil) 2004 // Enable the OP_CHECKLOCKTIMEVERIFY opcode to be used. 2005 // 2006 // https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#Spending_wallet_policy 2007 txIn.Sequence = wire.MaxTxInSequenceNum - 1 2008 msgTx.AddTxIn(txIn) 2009 // Calculate fees and add the change output. 2010 2011 if refundAddr == nil { 2012 refundAddr, err = transparentAddress(w, w.addrParams, w.btcParams) 2013 if err != nil { 2014 return nil, fmt.Errorf("error getting new address from the wallet: %w", err) 2015 } 2016 } 2017 pkScript, err := txscript.PayToAddrScript(refundAddr) 2018 if err != nil { 2019 return nil, fmt.Errorf("error creating change script: %w", err) 2020 } 2021 txOut := wire.NewTxOut(int64(val-fees), pkScript) 2022 // One last check for dust. 2023 if isDust(uint64(txOut.Value), uint64(txOut.SerializeSize())) { 2024 return nil, fmt.Errorf("refund output is dust. value = %d, size = %d", txOut.Value, txOut.SerializeSize()) 2025 } 2026 msgTx.AddTxOut(txOut) 2027 2028 prevScript, err := w.scriptHashScript(contract) 2029 if err != nil { 2030 return nil, fmt.Errorf("error constructing p2sh script: %w", err) 2031 } 2032 2033 refundSig, refundPubKey, err := w.createSig(msgTx, 0, contract, sender, []int64{int64(val)}, [][]byte{prevScript}) 2034 if err != nil { 2035 return nil, fmt.Errorf("createSig: %w", err) 2036 } 2037 txIn.SignatureScript, err = dexbtc.RefundP2SHContract(contract, refundSig, refundPubKey) 2038 if err != nil { 2039 return nil, fmt.Errorf("RefundP2SHContract: %w", err) 2040 } 2041 return msgTx, nil 2042 } 2043 2044 func (w *zecWallet) createSig(msgTx *wire.MsgTx, idx int, pkScript []byte, addr btcutil.Address, vals []int64, prevScripts [][]byte) (sig, pubkey []byte, err error) { 2045 addrStr, err := dexzec.EncodeAddress(addr, w.addrParams) 2046 if err != nil { 2047 return nil, nil, fmt.Errorf("error encoding address: %w", err) 2048 } 2049 privKey, err := dumpPrivKey(w, addrStr) 2050 if err != nil { 2051 return nil, nil, fmt.Errorf("dumpPrivKey error: %w", err) 2052 } 2053 defer privKey.Zero() 2054 2055 sig, err = signTx(msgTx, idx, pkScript, txscript.SigHashAll, privKey, vals, prevScripts) 2056 if err != nil { 2057 return nil, nil, err 2058 } 2059 2060 return sig, privKey.PubKey().SerializeCompressed(), nil 2061 } 2062 2063 func (w *zecWallet) RegFeeConfirmations(_ context.Context, id dex.Bytes) (confs uint32, err error) { 2064 return 0, errors.New("legacy registration not supported") 2065 } 2066 2067 func sendEnough(val uint64) btc.EnoughFunc { 2068 return func(inputCount, inputsSize, sum uint64) (bool, uint64) { 2069 fees := dexzec.TxFeesZIP317(inputCount*dexbtc.RedeemP2PKHInputSize+1, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0) 2070 total := fees + val 2071 if sum >= total { 2072 return true, sum - total 2073 } 2074 return false, 0 2075 } 2076 } 2077 2078 // fundOrchard checks whether a send of the specified amount can be funded from 2079 // the Orchard pool alone. 2080 func (w *zecWallet) fundOrchard(amt uint64) (sum, fees uint64, n int, funded bool, err error) { 2081 unfiltered, err := zListUnspent(w) 2082 if err != nil { 2083 return 0, 0, 0, false, err 2084 } 2085 unspents := make([]*zListUnspentResult, 0, len(unfiltered)) 2086 for _, u := range unfiltered { 2087 if u.Account == shieldedAcctNumber && u.Confirmations >= minOrchardConfs { 2088 unspents = append(unspents, u) 2089 } 2090 } 2091 sort.Slice(unspents, func(i, j int) bool { 2092 return unspents[i].Amount < unspents[j].Amount 2093 }) 2094 var u *zListUnspentResult 2095 for n, u = range unspents { 2096 sum += toZats(u.Amount) 2097 fees = dexzec.TxFeesZIP317(0, 0, 0, 0, 0, uint64(n)) 2098 if sum > amt+fees { 2099 funded = true 2100 return 2101 } 2102 } 2103 return 2104 } 2105 2106 func (w *zecWallet) EstimateSendTxFee( 2107 addrStr string, amt, _ /* feeRate*/ uint64, _ /* subtract */, maxWithdraw bool, 2108 ) ( 2109 fees uint64, isValidAddress bool, err error, 2110 ) { 2111 2112 res, err := zValidateAddress(w, addrStr) 2113 isValidAddress = err == nil && res.IsValid 2114 2115 if maxWithdraw { 2116 addrType := transparentAddressType 2117 if isValidAddress { 2118 addrType = res.AddressType 2119 } 2120 fees, err = w.maxWithdrawFees(addrType) 2121 return 2122 } 2123 2124 orchardSum, orchardFees, orchardN, orchardFunded, err := w.fundOrchard(amt) 2125 if err != nil { 2126 return 0, false, fmt.Errorf("error checking orchard funding: %w", err) 2127 } 2128 if orchardFunded { 2129 return orchardFees, isValidAddress, nil 2130 } 2131 2132 bal, err := zGetBalanceForAccount(w, shieldedAcctNumber, 0) 2133 if err != nil { 2134 return 0, false, fmt.Errorf("z_getbalanceforaccount (0) error: %w", err) 2135 } 2136 2137 remain := amt - orchardSum - bal.Sapling /* sapling fees not accounted for */ 2138 2139 const minConfs = 0 2140 _, _, spents, _, inputsSize, _, err := w.cm.Fund(w.reserves.Load(), minConfs, false, sendEnough(remain)) 2141 if err != nil { 2142 return 0, false, fmt.Errorf("error funding value %d with %d transparent and %d orchard balances: %w", 2143 amt, bal.Transparent, orchardSum, err) 2144 } 2145 2146 fees = dexzec.TxFeesZIP317(inputsSize+uint64(wire.VarIntSerializeSize(uint64(len(spents)))), 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, uint64(orchardN)) 2147 return 2148 } 2149 2150 func (w *zecWallet) maxWithdrawFees(addrType string) (uint64, error) { 2151 unfiltered, err := listUnspent(w) 2152 if err != nil { 2153 return 0, fmt.Errorf("listunspent error: %w", err) 2154 } 2155 numInputs := uint64(len(unfiltered)) 2156 noteCounts, err := zGetNotesCount(w) 2157 if err != nil { 2158 return 0, fmt.Errorf("balances error: %w", err) 2159 } 2160 2161 txInsSize := numInputs*dexbtc.RedeemP2PKHInputSize + uint64(wire.VarIntSerializeSize(numInputs)) 2162 nActionsOrchard := uint64(noteCounts.Orchard) 2163 var txOutsSize, nOutputsSapling uint64 2164 switch addrType { 2165 case unifiedAddressType, orchardAddressType: 2166 nActionsOrchard++ 2167 case saplingAddressType: 2168 nOutputsSapling = 1 2169 default: // transparent 2170 txOutsSize = dexbtc.P2PKHOutputSize + 1 2171 } 2172 // TODO: Do we get nJoinSplit from noteCounts.Sprout somehow? 2173 return dexzec.TxFeesZIP317(txInsSize, txOutsSize, uint64(noteCounts.Sapling), nOutputsSapling, 0, nActionsOrchard), nil 2174 } 2175 2176 type txCoin struct { 2177 txHash *chainhash.Hash 2178 v uint64 2179 } 2180 2181 var _ asset.Coin = (*txCoin)(nil) 2182 2183 // ID is a unique identifier for this coin. 2184 func (c *txCoin) ID() dex.Bytes { 2185 return c.txHash[:] 2186 } 2187 2188 // String is a string representation of the coin. 2189 func (c *txCoin) String() string { 2190 return c.txHash.String() 2191 } 2192 2193 // Value is the available quantity, in atoms/satoshi. 2194 func (c *txCoin) Value() uint64 { 2195 return c.v 2196 } 2197 2198 // TxID is the ID of the transaction that created the coin. 2199 func (c *txCoin) TxID() string { 2200 return c.txHash.String() 2201 } 2202 2203 func (w *zecWallet) Send(addr string, value, feeRate uint64) (asset.Coin, error) { 2204 txHash, err := w.sendShielded(w.ctx, addr, value) 2205 if err != nil { 2206 return nil, err 2207 } 2208 2209 selfSend, err := w.OwnsDepositAddress(addr) 2210 if err != nil { 2211 w.log.Errorf("error checking if address %q is owned: %v", addr, err) 2212 } 2213 txType := asset.Send 2214 if selfSend { 2215 txType = asset.SelfSend 2216 } 2217 2218 tx, err := getTransaction(w, txHash) 2219 if err != nil { 2220 return nil, fmt.Errorf("unable to find tx after send %s: %v", txHash, err) 2221 } 2222 2223 w.addTxToHistory(&asset.WalletTransaction{ 2224 Type: txType, 2225 ID: txHash.String(), 2226 Amount: value, 2227 Fees: tx.RequiredTxFeesZIP317(), 2228 }, txHash, true) 2229 2230 return &txCoin{ 2231 txHash: txHash, 2232 v: value, 2233 }, nil 2234 } 2235 2236 // StandardSendFees returns the fees for a simple send tx with one input and two 2237 // outputs. 2238 func (w *zecWallet) StandardSendFee(feeRate uint64) uint64 { 2239 return dexzec.TxFeesZIP317(dexbtc.RedeemP2PKHInputSize+1, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0) 2240 } 2241 2242 // TransactionConfirmations gets the number of confirmations for the specified 2243 // transaction. 2244 func (w *zecWallet) TransactionConfirmations(ctx context.Context, txID string) (confs uint32, err error) { 2245 txHash, err := chainhash.NewHashFromStr(txID) 2246 if err != nil { 2247 return 0, fmt.Errorf("error decoding txid %q: %w", txID, err) 2248 } 2249 tx, err := getWalletTransaction(w, txHash) 2250 if err != nil { 2251 return 0, err 2252 } 2253 if tx.Confirmations < 0 { 2254 tx.Confirmations = 0 2255 } 2256 2257 return uint32(tx.Confirmations), nil 2258 } 2259 2260 // send the value to the address, with the given fee rate. If subtract is true, 2261 // the fees will be subtracted from the value. If false, the fees are in 2262 // addition to the value. feeRate is in units of sats/byte. 2263 func (w *zecWallet) send(addrStr string, val uint64, subtract bool) (*chainhash.Hash, uint32, uint64, error) { 2264 addr, err := dexzec.DecodeAddress(addrStr, w.addrParams, w.btcParams) 2265 if err != nil { 2266 return nil, 0, 0, fmt.Errorf("invalid address: %s", addrStr) 2267 } 2268 pay2script, err := txscript.PayToAddrScript(addr) 2269 if err != nil { 2270 return nil, 0, 0, fmt.Errorf("PayToAddrScript error: %w", err) 2271 } 2272 2273 const minConfs = 0 2274 _, _, spents, _, inputsSize, _, err := w.cm.Fund(w.reserves.Load(), minConfs, false, sendEnough(val)) 2275 if err != nil { 2276 return nil, 0, 0, newError(errFunding, "error funding transaction: %w", err) 2277 } 2278 2279 fundedTx, totalIn, _, err := w.fundedTx(spents) 2280 if err != nil { 2281 return nil, 0, 0, fmt.Errorf("error adding inputs to transaction: %w", err) 2282 } 2283 2284 fees := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0) 2285 var toSend uint64 2286 if subtract { 2287 toSend = val - fees 2288 } else { 2289 toSend = val 2290 } 2291 fundedTx.AddTxOut(wire.NewTxOut(int64(toSend), pay2script)) 2292 2293 changeAddr, err := transparentAddress(w, w.addrParams, w.btcParams) 2294 if err != nil { 2295 return nil, 0, 0, fmt.Errorf("error creating change address: %w", err) 2296 } 2297 2298 signedTx, _, err := w.signTxAndAddChange(fundedTx, changeAddr, totalIn, val, fees) 2299 if err != nil { 2300 return nil, 0, 0, fmt.Errorf("signTxAndAddChange error: %v", err) 2301 } 2302 2303 txHash, err := w.broadcastTx(signedTx) 2304 if err != nil { 2305 return nil, 0, 0, err 2306 } 2307 2308 return txHash, 0, toSend, nil 2309 } 2310 2311 func (w *zecWallet) sendWithReturn(baseTx *dexzec.Tx, addr btcutil.Address, totalIn, totalOut uint64) (*dexzec.Tx, error) { 2312 txInsSize := uint64(len(baseTx.TxIn))*dexbtc.RedeemP2PKHInputSize + 1 2313 var txOutsSize uint64 = uint64(wire.VarIntSerializeSize(uint64(len(baseTx.TxOut) + 1))) 2314 for _, txOut := range baseTx.TxOut { 2315 txOutsSize += uint64(txOut.SerializeSize()) 2316 } 2317 2318 fees := dexzec.TxFeesZIP317(txInsSize, txOutsSize, 0, 0, 0, 0) 2319 2320 signedTx, _, err := w.signTxAndAddChange(baseTx, addr, totalIn, totalOut, fees) 2321 if err != nil { 2322 return nil, err 2323 } 2324 2325 _, err = w.broadcastTx(signedTx) 2326 return signedTx, err 2327 } 2328 2329 func (w *zecWallet) SignMessage(coin asset.Coin, msg dex.Bytes) (pubkeys, sigs []dex.Bytes, err error) { 2330 2331 op, err := btc.ConvertCoin(coin) 2332 if err != nil { 2333 return nil, nil, fmt.Errorf("error converting coin: %w", err) 2334 } 2335 utxo := w.cm.LockedOutput(op.Pt) 2336 2337 if utxo == nil { 2338 return nil, nil, fmt.Errorf("no utxo found for %s", op) 2339 } 2340 privKey, err := dumpPrivKey(w, utxo.Address) 2341 if err != nil { 2342 return nil, nil, err 2343 } 2344 defer privKey.Zero() 2345 pk := privKey.PubKey() 2346 hash := chainhash.HashB(msg) // legacy servers will not accept this signature! 2347 sig := ecdsa.Sign(privKey, hash) 2348 pubkeys = append(pubkeys, pk.SerializeCompressed()) 2349 sigs = append(sigs, sig.Serialize()) // DER format serialization 2350 return 2351 } 2352 2353 func (w *zecWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint64, error) { 2354 contracts := make([][]byte, 0, len(swaps.Contracts)) 2355 var totalOut uint64 2356 // Start with an empty MsgTx. 2357 coins := make([]*btc.Output, len(swaps.Inputs)) 2358 for i, coin := range swaps.Inputs { 2359 c, err := btc.ConvertCoin(coin) 2360 if err != nil { 2361 return nil, nil, 0, fmt.Errorf("error converting coin ID: %w", err) 2362 } 2363 coins[i] = c 2364 } 2365 baseTx, totalIn, pts, err := w.fundedTx(coins) 2366 if err != nil { 2367 return nil, nil, 0, err 2368 } 2369 2370 var customCfg swapOptions 2371 err = config.Unmapify(swaps.Options, &customCfg) 2372 if err != nil { 2373 return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err) 2374 } 2375 2376 refundAddrs := make([]btcutil.Address, 0, len(swaps.Contracts)) 2377 2378 // Add the contract outputs. 2379 // TODO: Make P2WSH contract and P2WPKH change outputs instead of 2380 // legacy/non-segwit swap contracts pkScripts. 2381 var swapOutsSize uint64 2382 for _, contract := range swaps.Contracts { 2383 totalOut += contract.Value 2384 // revokeAddr is the address belonging to the key that may be used to 2385 // sign and refund a swap past its encoded refund locktime. 2386 revokeAddrStr, err := w.recyclableAddress() 2387 if err != nil { 2388 return nil, nil, 0, fmt.Errorf("error creating revocation address: %w", err) 2389 } 2390 revokeAddr, err := dexzec.DecodeAddress(revokeAddrStr, w.addrParams, w.btcParams) 2391 if err != nil { 2392 return nil, nil, 0, fmt.Errorf("refund address decode error: %v", err) 2393 } 2394 refundAddrs = append(refundAddrs, revokeAddr) 2395 2396 contractAddr, err := dexzec.DecodeAddress(contract.Address, w.addrParams, w.btcParams) 2397 if err != nil { 2398 return nil, nil, 0, fmt.Errorf("contract address decode error: %v", err) 2399 } 2400 2401 // Create the contract, a P2SH redeem script. 2402 contractScript, err := dexbtc.MakeContract(contractAddr, revokeAddr, 2403 contract.SecretHash, int64(contract.LockTime), false, w.btcParams) 2404 if err != nil { 2405 return nil, nil, 0, fmt.Errorf("unable to create pubkey script for address %s: %w", contract.Address, err) 2406 } 2407 contracts = append(contracts, contractScript) 2408 2409 // Make the P2SH address and pubkey script. 2410 scriptAddr, err := w.scriptHashAddress(contractScript) 2411 if err != nil { 2412 return nil, nil, 0, fmt.Errorf("error encoding script address: %w", err) 2413 } 2414 2415 pkScript, err := txscript.PayToAddrScript(scriptAddr) 2416 if err != nil { 2417 return nil, nil, 0, fmt.Errorf("error creating pubkey script: %w", err) 2418 } 2419 2420 // Add the transaction output. 2421 txOut := wire.NewTxOut(int64(contract.Value), pkScript) 2422 swapOutsSize += uint64(txOut.SerializeSize()) 2423 baseTx.AddTxOut(txOut) 2424 } 2425 if totalIn < totalOut { 2426 return nil, nil, 0, newError(errInsufficientBalance, "unfunded contract. %d < %d", totalIn, totalOut) 2427 } 2428 2429 // Ensure we have enough outputs before broadcasting. 2430 swapCount := len(swaps.Contracts) 2431 if len(baseTx.TxOut) < swapCount { 2432 return nil, nil, 0, fmt.Errorf("fewer outputs than swaps. %d < %d", len(baseTx.TxOut), swapCount) 2433 } 2434 2435 // Grab a change address. 2436 changeAddr, err := transparentAddress(w, w.addrParams, w.btcParams) 2437 if err != nil { 2438 return nil, nil, 0, fmt.Errorf("error creating change address: %w", err) 2439 } 2440 2441 txInsSize := uint64(len(coins))*dexbtc.RedeemP2PKHInputSize + 1 2442 txOutsSize := swapOutsSize + dexbtc.P2PKHOutputSize 2443 fees := dexzec.TxFeesZIP317(txInsSize, txOutsSize, 0, 0, 0, 0) 2444 2445 // Sign, add change, but don't send the transaction yet until 2446 // the individual swap refund txs are prepared and signed. 2447 msgTx, change, err := w.signTxAndAddChange(baseTx, changeAddr, totalIn, totalOut, fees) 2448 if err != nil { 2449 return nil, nil, 0, err 2450 } 2451 txHash := msgTx.TxHash() 2452 2453 // Prepare the receipts. 2454 receipts := make([]asset.Receipt, 0, swapCount) 2455 for i, contract := range swaps.Contracts { 2456 output := btc.NewOutput(&txHash, uint32(i), contract.Value) 2457 refundAddr := refundAddrs[i] 2458 signedRefundTx, err := w.refundTx(&output.Pt.TxHash, output.Pt.Vout, contracts[i], contract.Value, refundAddr, fees) 2459 if err != nil { 2460 return nil, nil, 0, fmt.Errorf("error creating refund tx: %w", err) 2461 } 2462 refundBuff := new(bytes.Buffer) 2463 err = signedRefundTx.Serialize(refundBuff) 2464 if err != nil { 2465 return nil, nil, 0, fmt.Errorf("error serializing refund tx: %w", err) 2466 } 2467 receipts = append(receipts, &btc.SwapReceipt{ 2468 Output: output, 2469 SwapContract: contracts[i], 2470 ExpirationTime: time.Unix(int64(contract.LockTime), 0).UTC(), 2471 SignedRefundBytes: refundBuff.Bytes(), 2472 }) 2473 } 2474 2475 // Refund txs prepared and signed. Can now broadcast the swap(s). 2476 _, err = w.broadcastTx(msgTx) 2477 if err != nil { 2478 return nil, nil, 0, err 2479 } 2480 2481 w.addTxToHistory(&asset.WalletTransaction{ 2482 Type: asset.Swap, 2483 ID: txHash.String(), 2484 Amount: totalOut, 2485 Fees: msgTx.RequiredTxFeesZIP317(), 2486 }, &txHash, true) 2487 2488 // If change is nil, return a nil asset.Coin. 2489 var changeCoin asset.Coin 2490 if change != nil { 2491 changeCoin = change 2492 } 2493 2494 if swaps.LockChange && change != nil { 2495 // Lock the change output 2496 w.log.Debugf("locking change coin %s", change) 2497 err = lockUnspent(w, false, []*btc.Output{change}) 2498 if err != nil { 2499 // The swap transaction is already broadcasted, so don't fail now. 2500 w.log.Errorf("failed to lock change output: %v", err) 2501 } 2502 2503 addrStr, err := dexzec.EncodeAddress(changeAddr, w.addrParams) 2504 if err != nil { 2505 w.log.Errorf("Failed to stringify address %v (default encoding): %v", changeAddr, err) 2506 addrStr = changeAddr.String() // may or may not be able to retrieve the private keys for the next swap! 2507 } 2508 w.cm.LockUTXOs([]*btc.UTxO{{ 2509 TxHash: &change.Pt.TxHash, 2510 Vout: change.Pt.Vout, 2511 Address: addrStr, 2512 Amount: change.Val, 2513 }}) 2514 } 2515 2516 w.cm.UnlockOutPoints(pts) 2517 2518 return receipts, changeCoin, fees, nil 2519 } 2520 2521 func (w *zecWallet) SwapConfirmations(_ context.Context, id dex.Bytes, contract dex.Bytes, startTime time.Time) (uint32, bool, error) { 2522 txHash, vout, err := decodeCoinID(id) 2523 if err != nil { 2524 return 0, false, err 2525 } 2526 // Check for an unspent output. 2527 txOut, err := getTxOutput(w, txHash, vout) 2528 if err == nil && txOut != nil { 2529 return uint32(txOut.Confirmations), false, nil 2530 } 2531 // Check wallet transactions. 2532 tx, err := getWalletTransaction(w, txHash) 2533 if err != nil { 2534 if errors.Is(err, asset.CoinNotFoundError) { 2535 return 0, false, asset.CoinNotFoundError 2536 } 2537 return 0, false, newError(errNoTx, "gettransaction error; %w", err) 2538 } 2539 if tx.Confirmations < 0 { 2540 tx.Confirmations = 0 2541 } 2542 return uint32(tx.Confirmations), true, nil 2543 } 2544 2545 func (w *zecWallet) SyncStatus() (*asset.SyncStatus, error) { 2546 ss, err := syncStatus(w) 2547 if err != nil { 2548 return nil, err 2549 } 2550 ss.StartingBlocks = w.tipAtConnect.Load() 2551 2552 if ss.TargetHeight == 0 { // do not say progress = 1 2553 return &asset.SyncStatus{}, nil 2554 } 2555 if ss.Synced { 2556 numPeers, err := peerCount(w) 2557 if err != nil { 2558 return nil, err 2559 } 2560 ss.Synced = numPeers > 0 2561 } 2562 return ss, nil 2563 } 2564 2565 func (w *zecWallet) ValidateAddress(addr string) bool { 2566 res, err := zValidateAddress(w, addr) 2567 if err != nil { 2568 w.log.Errorf("z_validateaddress error for address %q: %w", addr, err) 2569 return false 2570 } 2571 return res.IsValid 2572 } 2573 2574 func (w *zecWallet) ValidateSecret(secret, secretHash []byte) bool { 2575 h := sha256.Sum256(secret) 2576 return bytes.Equal(h[:], secretHash) 2577 } 2578 2579 func (w *zecWallet) useSplitTx() bool { 2580 return w.walletCfg.Load().(*WalletConfig).UseSplitTx 2581 } 2582 2583 func transparentAddressString(c rpcCaller) (string, error) { 2584 // One of the address types MUST be shielded. 2585 addrRes, err := zGetAddressForAccount(c, shieldedAcctNumber, []string{transparentAddressType, orchardAddressType}) 2586 if err != nil { 2587 return "", err 2588 } 2589 receivers, err := zGetUnifiedReceivers(c, addrRes.Address) 2590 if err != nil { 2591 return "", err 2592 } 2593 return receivers.Transparent, nil 2594 } 2595 2596 func transparentAddress(c rpcCaller, addrParams *dexzec.AddressParams, btcParams *chaincfg.Params) (btcutil.Address, error) { 2597 addrStr, err := transparentAddressString(c) 2598 if err != nil { 2599 return nil, err 2600 } 2601 return dexzec.DecodeAddress(addrStr, addrParams, btcParams) 2602 } 2603 2604 func (w *zecWallet) lastShieldedAddress() (addr string, err error) { 2605 if addrPtr := w.lastAddress.Load(); addrPtr != nil { 2606 return addrPtr.(string), nil 2607 } 2608 accts, err := zListAccounts(w) 2609 if err != nil { 2610 return "", err 2611 } 2612 for _, acct := range accts { 2613 if acct.Number != shieldedAcctNumber { 2614 continue 2615 } 2616 if len(acct.Addresses) == 0 { 2617 break // generate first address 2618 } 2619 lastAddr := acct.Addresses[len(acct.Addresses)-1].UnifiedAddr 2620 w.lastAddress.Store(lastAddr) 2621 return lastAddr, nil // Orchard = Unified for account 1 2622 } 2623 return w.newShieldedAddress() 2624 } 2625 2626 // Balance adds a sum of shielded pool balances to the transparent balance info. 2627 // 2628 // Since v5.4.0, the getnewaddress RPC is deprecated if favor of using unified 2629 // addresses from accounts generated with z_getnewaccount. Addresses are 2630 // generated from account 0. Any addresses previously generated using 2631 // getnewaddress belong to a legacy account that is not listed with 2632 // z_listaccount, nor addressable with e.g. z_getbalanceforaccount. For 2633 // transparent addresses, we still use the getbalance RPC, which combines 2634 // transparent balance from both legacy and generated accounts. This matches the 2635 // behavior of the listunspent RPC, and conveniently makes upgrading simple. So 2636 // even though we ONLY use account 0 to generate t-addresses, any account's 2637 // transparent outputs are eligible for trading. To minimize confusion, we don't 2638 // add transparent receivers to addresses generated from the shielded account. 2639 // This doesn't preclude a user doing something silly with zcash-cli. 2640 func (w *zecWallet) Balance() (*asset.Balance, error) { 2641 2642 bals, err := w.balances() 2643 if err != nil { 2644 return nil, codedError(errBalanceRetrieval, err) 2645 } 2646 locked, err := w.lockedZats() 2647 if err != nil { 2648 return nil, err 2649 } 2650 2651 bal := &asset.Balance{ 2652 Available: bals.orchard.avail + bals.transparent.avail, 2653 Immature: bals.orchard.maturing + bals.transparent.maturing, 2654 Locked: locked, 2655 Other: make(map[asset.BalanceCategory]asset.CustomBalance), 2656 } 2657 2658 bal.Other[asset.BalanceCategoryShielded] = asset.CustomBalance{ 2659 Amount: bals.orchard.avail, // + bals.orchard.maturing, 2660 } 2661 2662 reserves := w.reserves.Load() 2663 if reserves > bal.Available { 2664 w.log.Warnf("Available balance is below configured reserves: %s < %s", 2665 btcutil.Amount(bal.Available), btcutil.Amount(reserves)) 2666 bal.ReservesDeficit = reserves - bal.Available 2667 reserves = bal.Available 2668 } 2669 2670 bal.BondReserves = reserves 2671 bal.Available -= reserves 2672 bal.Locked += reserves 2673 2674 return bal, nil 2675 } 2676 2677 // lockedSats is the total value of locked outputs, as locked with LockUnspent. 2678 func (w *zecWallet) lockedZats() (uint64, error) { 2679 lockedOutpoints, err := listLockUnspent(w, w.log) 2680 if err != nil { 2681 return 0, err 2682 } 2683 var sum uint64 2684 for _, rpcOP := range lockedOutpoints { 2685 txHash, err := chainhash.NewHashFromStr(rpcOP.TxID) 2686 if err != nil { 2687 return 0, err 2688 } 2689 pt := btc.NewOutPoint(txHash, rpcOP.Vout) 2690 utxo := w.cm.LockedOutput(pt) 2691 if utxo != nil { 2692 sum += utxo.Amount 2693 continue 2694 } 2695 tx, err := getWalletTransaction(w, txHash) 2696 if err != nil { 2697 return 0, err 2698 } 2699 txOut, err := btc.TxOutFromTxBytes(tx.Bytes, rpcOP.Vout, deserializeTx, hashTx) 2700 if err != nil { 2701 return 0, err 2702 } 2703 sum += uint64(txOut.Value) 2704 } 2705 return sum, nil 2706 } 2707 2708 // newShieldedAddress creates a new shielded address. 2709 func (w *zecWallet) newShieldedAddress() (string, error) { 2710 // An orchard address is the same as a unified address with only an orchard 2711 // receiver. 2712 addrRes, err := zGetAddressForAccount(w, shieldedAcctNumber, []string{orchardAddressType}) 2713 if err != nil { 2714 return "", err 2715 } 2716 w.lastAddress.Store(addrRes.Address) 2717 return addrRes.Address, nil 2718 } 2719 2720 // sendOne is a helper function for doing a z_sendmany with a single recipient. 2721 func (w *zecWallet) sendOne(ctx context.Context, fromAddr, toAddr string, amt uint64, priv privacyPolicy) (*chainhash.Hash, error) { 2722 recip := singleSendManyRecipient(toAddr, amt) 2723 2724 operationID, err := zSendMany(w, fromAddr, recip, priv) 2725 if err != nil { 2726 return nil, fmt.Errorf("z_sendmany error: %w", err) 2727 } 2728 2729 return w.awaitSendManyOperation(ctx, w, operationID) 2730 } 2731 2732 // awaitSendManyOperation waits for the asynchronous result from a z_sendmany 2733 // operation. 2734 func (w *zecWallet) awaitSendManyOperation(ctx context.Context, c rpcCaller, operationID string) (*chainhash.Hash, error) { 2735 for { 2736 res, err := zGetOperationResult(c, operationID) 2737 if err != nil && !errors.Is(err, ErrEmptyOpResults) { 2738 return nil, fmt.Errorf("error getting operation result: %w", err) 2739 } 2740 if res != nil { 2741 switch res.Status { 2742 case "failed": 2743 return nil, fmt.Errorf("z_sendmany operation failed: %s", res.Error.Message) 2744 2745 case "success": 2746 if res.Result == nil { 2747 return nil, errors.New("async operation result = 'success' but no Result field") 2748 } 2749 txHash, err := chainhash.NewHashFromStr(res.Result.TxID) 2750 if err != nil { 2751 return nil, fmt.Errorf("error decoding txid: %w", err) 2752 } 2753 return txHash, nil 2754 default: 2755 w.log.Warnf("unexpected z_getoperationresult status %q: %+v", res.Status) 2756 } 2757 2758 } 2759 select { 2760 case <-ctx.Done(): 2761 return nil, ctx.Err() 2762 case <-time.After(time.Second): 2763 } 2764 } 2765 } 2766 2767 func (w *zecWallet) sendOneShielded(ctx context.Context, toAddr string, amt uint64, priv privacyPolicy) (*chainhash.Hash, error) { 2768 lastAddr, err := w.lastShieldedAddress() 2769 if err != nil { 2770 return nil, err 2771 } 2772 return w.sendOne(ctx, lastAddr, toAddr, amt, priv) 2773 } 2774 2775 func (w *zecWallet) sendShielded(ctx context.Context, toAddr string, amt uint64) (*chainhash.Hash, error) { 2776 res, err := zValidateAddress(w, toAddr) 2777 if err != nil { 2778 return nil, fmt.Errorf("error validating address: %w", err) 2779 } 2780 2781 if !res.IsValid { 2782 return nil, fmt.Errorf("invalid address %q", toAddr) 2783 } 2784 2785 // TODO: We're using NoPrivacy for everything except orchard-to-orchard 2786 // sends. This can potentially be tightened up, but why? The RPC provides 2787 // no insight into calculating what privacy level is needed, so we would 2788 // have to do a bunch of calculations regarding sources of funds and 2789 // potential for change outputs. In the end, it changes nothing, and zcashd 2790 // will optimize privacy the best it can. 2791 priv := NoPrivacy 2792 if res.AddressType == unifiedAddressType { 2793 r, err := zGetUnifiedReceivers(w, toAddr) 2794 if err != nil { 2795 return nil, fmt.Errorf("error getting unified receivers: %w", err) 2796 } 2797 if r.Orchard != "" { 2798 if _, _, _, isFunded, err := w.fundOrchard(amt); err != nil { 2799 return nil, fmt.Errorf("error checking orchard funding: %w", err) 2800 } else if isFunded { 2801 priv = FullPrivacy 2802 } 2803 } 2804 } 2805 2806 txHash, err := w.sendOneShielded(ctx, toAddr, amt, priv) 2807 if err != nil { 2808 return nil, err 2809 } 2810 2811 return txHash, nil 2812 } 2813 2814 func zecTx(tx *wire.MsgTx) *dexzec.Tx { 2815 return dexzec.NewTxFromMsgTx(tx, dexzec.MaxExpiryHeight) 2816 } 2817 2818 // signTx signs the transaction input with Zcash's BLAKE-2B sighash digest. 2819 // Won't work with shielded or blended transactions. 2820 func signTx(btcTx *wire.MsgTx, idx int, pkScript []byte, hashType txscript.SigHashType, 2821 key *btcec.PrivateKey, amts []int64, prevScripts [][]byte) ([]byte, error) { 2822 2823 tx := zecTx(btcTx) 2824 2825 sigHash, err := tx.SignatureDigest(idx, hashType, pkScript, amts, prevScripts) 2826 if err != nil { 2827 return nil, fmt.Errorf("sighash calculation error: %v", err) 2828 } 2829 2830 return append(ecdsa.Sign(key, sigHash[:]).Serialize(), byte(hashType)), nil 2831 } 2832 2833 // isDust returns true if the output will be rejected as dust. 2834 func isDust(val, outputSize uint64) bool { 2835 // See https://github.com/zcash/zcash/blob/5066efbb98bc2af5eed201212d27c77993950cee/src/primitives/transaction.h#L630 2836 // https://github.com/zcash/zcash/blob/5066efbb98bc2af5eed201212d27c77993950cee/src/primitives/transaction.cpp#L127 2837 // Also see informative comments hinting towards future changes at 2838 // https://github.com/zcash/zcash/blob/master/src/policy/policy.h 2839 sz := outputSize + 148 // 148 accounts for an input on spending tx 2840 const oneThirdDustThresholdRate = 100 // zats / kB 2841 nFee := oneThirdDustThresholdRate * sz / 1000 // This is different from BTC 2842 if nFee == 0 { 2843 nFee = oneThirdDustThresholdRate 2844 } 2845 2846 return val < 3*nFee 2847 } 2848 2849 // Convert the ZEC value to satoshi. 2850 func toZats(v float64) uint64 { 2851 return uint64(math.Round(v * 1e8)) 2852 } 2853 2854 func hashTx(tx *wire.MsgTx) *chainhash.Hash { 2855 h := zecTx(tx).TxHash() 2856 return &h 2857 } 2858 2859 func deserializeTx(b []byte) (*wire.MsgTx, error) { 2860 tx, err := dexzec.DeserializeTx(b) 2861 if err != nil { 2862 return nil, err 2863 } 2864 return tx.MsgTx, nil 2865 } 2866 2867 func (w *zecWallet) txDB() *btc.BadgerTxDB { 2868 db := w.txHistoryDB.Load() 2869 if db == nil { 2870 return nil 2871 } 2872 return db.(*btc.BadgerTxDB) 2873 } 2874 2875 func (w *zecWallet) listSinceBlock(start int64) ([]btcjson.ListTransactionsResult, error) { 2876 hash, err := getBlockHash(w, start) 2877 if err != nil { 2878 return nil, err 2879 } 2880 2881 res, err := listSinceBlock(w, hash) 2882 if err != nil { 2883 return nil, err 2884 } 2885 2886 return res, nil 2887 } 2888 2889 func (w *zecWallet) addTxToHistory(wt *asset.WalletTransaction, txHash *chainhash.Hash, submitted bool, skipNotes ...bool) { 2890 txHistoryDB := w.txDB() 2891 if txHistoryDB == nil { 2892 return 2893 } 2894 2895 ewt := &btc.ExtendedWalletTx{ 2896 WalletTransaction: wt, 2897 Submitted: submitted, 2898 } 2899 2900 if wt.BlockNumber == 0 { 2901 w.pendingTxsMtx.Lock() 2902 w.pendingTxs[*txHash] = ewt 2903 w.pendingTxsMtx.Unlock() 2904 } 2905 2906 err := txHistoryDB.StoreTx(ewt) 2907 if err != nil { 2908 w.log.Errorf("failed to store tx in tx history db: %v", err) 2909 } 2910 2911 skipNote := len(skipNotes) > 0 && skipNotes[0] 2912 if submitted && !skipNote { 2913 w.emit.TransactionNote(wt, true) 2914 } 2915 } 2916 2917 func (w *zecWallet) txHistoryDBPath(walletID string) string { 2918 return filepath.Join(w.walletDir, fmt.Sprintf("txhistorydb-%s", walletID)) 2919 } 2920 2921 // findExistingAddressBasedTxHistoryDB finds the path of a tx history db that 2922 // was created using an address controlled by the wallet. This should only be 2923 // used for RPC wallets, as SPV wallets are able to get the first address 2924 // generated by the wallet. 2925 func (w *zecWallet) findExistingAddressBasedTxHistoryDB(encSeed string) (string, error) { 2926 dir, err := os.Open(w.walletDir) 2927 if err != nil { 2928 return "", fmt.Errorf("error opening wallet directory: %w", err) 2929 } 2930 defer dir.Close() 2931 2932 entries, err := dir.Readdir(0) 2933 if err != nil { 2934 return "", fmt.Errorf("error reading wallet directory: %w", err) 2935 } 2936 2937 pattern := regexp.MustCompile(`^txhistorydb-(.+)$`) 2938 2939 for _, entry := range entries { 2940 if !entry.IsDir() { 2941 continue 2942 } 2943 2944 match := pattern.FindStringSubmatch(entry.Name()) 2945 if match == nil { 2946 continue 2947 } 2948 2949 name := match[1] 2950 if name == encSeed { 2951 return filepath.Join(w.walletDir, entry.Name()), nil 2952 } 2953 } 2954 2955 return "", nil 2956 } 2957 2958 func (w *zecWallet) startTxHistoryDB(ctx context.Context) (*sync.WaitGroup, error) { 2959 wInfo, err := walletInfo(w) 2960 if err != nil { 2961 return nil, err 2962 } 2963 encSeed := wInfo.MnemonicSeedfp 2964 name, err := w.findExistingAddressBasedTxHistoryDB(encSeed) 2965 if err != nil { 2966 return nil, err 2967 } 2968 dbPath := name 2969 2970 if dbPath == "" { 2971 dbPath = w.txHistoryDBPath(encSeed) 2972 } 2973 2974 w.log.Debugf("Using tx history db at %s", dbPath) 2975 2976 db := btc.NewBadgerTxDB(dbPath, w.log) 2977 w.txHistoryDB.Store(db) 2978 2979 wg, err := db.Connect(ctx) 2980 if err != nil { 2981 return nil, fmt.Errorf("error connecting to tx history db: %w", err) 2982 } 2983 2984 pendingTxs, err := db.GetPendingTxs() 2985 if err != nil { 2986 return nil, fmt.Errorf("failed to load unconfirmed txs: %v", err) 2987 } 2988 2989 w.pendingTxsMtx.Lock() 2990 for _, tx := range pendingTxs { 2991 txHash, err := chainhash.NewHashFromStr(tx.ID) 2992 if err != nil { 2993 w.log.Errorf("Invalid txid %v from tx history db: %v", tx.ID, err) 2994 continue 2995 } 2996 w.pendingTxs[*txHash] = tx 2997 } 2998 w.pendingTxsMtx.Unlock() 2999 3000 lastQuery, err := db.GetLastReceiveTxQuery() 3001 if errors.Is(err, btc.ErrNeverQueried) { 3002 lastQuery = 0 3003 } else if err != nil { 3004 return nil, fmt.Errorf("failed to load last query time: %v", err) 3005 } 3006 3007 w.receiveTxLastQuery.Store(lastQuery) 3008 3009 return wg, nil 3010 } 3011 3012 // WalletTransaction returns a transaction that either the wallet has made or 3013 // one in which the wallet has received funds. 3014 func (w *zecWallet) WalletTransaction(_ context.Context, txID string) (*asset.WalletTransaction, error) { 3015 coinID, err := hex.DecodeString(txID) 3016 if err == nil { 3017 txHash, _, err := decodeCoinID(coinID) 3018 if err == nil { 3019 txID = txHash.String() 3020 } 3021 } 3022 3023 txHistoryDB := w.txDB() 3024 tx, err := txHistoryDB.GetTx(txID) 3025 if err != nil { 3026 return nil, err 3027 } 3028 3029 if tx == nil { 3030 txHash, err := chainhash.NewHashFromStr(txID) 3031 if err != nil { 3032 return nil, fmt.Errorf("error decoding txid %s: %w", txID, err) 3033 } 3034 3035 gtr, err := getTransaction(w, txHash) 3036 if err != nil { 3037 return nil, fmt.Errorf("error getting transaction %s: %w", txID, err) 3038 } 3039 3040 var ( 3041 blockHeight int32 3042 blockTime int64 3043 ) 3044 if gtr.blockHash != nil { 3045 block, _, err := getVerboseBlockHeader(w, gtr.blockHash) 3046 if err != nil { 3047 return nil, fmt.Errorf("error getting block height for %s: %v", gtr.blockHash, err) 3048 } 3049 blockHeight = int32(block.Height) 3050 blockTime = block.Time 3051 } 3052 3053 tx, err = w.idUnknownTx(&btcjson.ListTransactionsResult{ 3054 BlockHeight: &blockHeight, 3055 BlockTime: blockTime, 3056 TxID: txID, 3057 }) 3058 if err != nil { 3059 return nil, fmt.Errorf("error identifying transaction: %v", err) 3060 } 3061 3062 tx.BlockNumber = uint64(blockHeight) 3063 tx.Timestamp = uint64(blockTime) 3064 tx.Confirmed = true 3065 w.addTxToHistory(tx, txHash, true, false) 3066 } 3067 3068 // If the wallet knows about the transaction, it will be part of the 3069 // available balance, so we always return Confirmed = true. 3070 tx.Confirmed = true 3071 3072 return tx, nil 3073 } 3074 3075 // TxHistory returns all the transactions the wallet has made. If refID is nil, 3076 // then transactions starting from the most recent are returned (past is ignored). 3077 // If past is true, the transactions prior to the refID are returned, otherwise 3078 // the transactions after the refID are returned. n is the number of 3079 // transactions to return. If n is <= 0, all the transactions will be returned. 3080 func (w *zecWallet) TxHistory(n int, refID *string, past bool) ([]*asset.WalletTransaction, error) { 3081 txHistoryDB := w.txDB() 3082 if txHistoryDB == nil { 3083 return nil, fmt.Errorf("tx database not initialized") 3084 } 3085 3086 return txHistoryDB.GetTxs(n, refID, past) 3087 } 3088 3089 const sendCategory = "send" 3090 3091 // idUnknownTx identifies the type and details of a transaction either made 3092 // or recieved by the wallet. 3093 func (w *zecWallet) idUnknownTx(tx *btcjson.ListTransactionsResult) (*asset.WalletTransaction, error) { 3094 txHash, err := chainhash.NewHashFromStr(tx.TxID) 3095 if err != nil { 3096 return nil, fmt.Errorf("error decoding tx hash %s: %v", tx.TxID, err) 3097 } 3098 msgTx, err := getTransaction(w, txHash) 3099 if err != nil { 3100 return nil, err 3101 } 3102 3103 var totalOut uint64 3104 for _, txOut := range msgTx.TxOut { 3105 totalOut += uint64(txOut.Value) 3106 } 3107 3108 fee := msgTx.RequiredTxFeesZIP317() 3109 3110 txIsBond := func(msgTx *zTx) (bool, *asset.BondTxInfo) { 3111 if len(msgTx.TxOut) < 2 { 3112 return false, nil 3113 } 3114 const scriptVer = 0 3115 acctID, lockTime, pkHash, err := dexbtc.ExtractBondCommitDataV0(scriptVer, msgTx.TxOut[1].PkScript) 3116 if err != nil { 3117 return false, nil 3118 } 3119 return true, &asset.BondTxInfo{ 3120 AccountID: acctID[:], 3121 LockTime: uint64(lockTime), 3122 BondID: pkHash[:], 3123 } 3124 } 3125 if isBond, bondInfo := txIsBond(msgTx); isBond { 3126 return &asset.WalletTransaction{ 3127 Type: asset.CreateBond, 3128 ID: tx.TxID, 3129 Amount: uint64(msgTx.TxOut[0].Value), 3130 Fees: fee, 3131 BondInfo: bondInfo, 3132 }, nil 3133 } 3134 3135 // Any other P2SH may be a swap or a send. We cannot determine unless we 3136 // look up the transaction that spends this UTXO. 3137 txPaysToScriptHash := func(msgTx *zTx) (v uint64) { 3138 for _, txOut := range msgTx.TxOut { 3139 if txscript.IsPayToScriptHash(txOut.PkScript) { 3140 v += uint64(txOut.Value) 3141 } 3142 } 3143 return 3144 } 3145 if v := txPaysToScriptHash(msgTx); v > 0 { 3146 return &asset.WalletTransaction{ 3147 Type: asset.SwapOrSend, 3148 ID: tx.TxID, 3149 Amount: v, 3150 Fees: fee, 3151 }, nil 3152 } 3153 3154 // Helper function will help us identify inputs that spend P2SH contracts. 3155 containsContractAtPushIndex := func(msgTx *zTx, idx int, isContract func(contract []byte) bool) bool { 3156 txinloop: 3157 for _, txIn := range msgTx.TxIn { 3158 // not segwit 3159 const scriptVer = 0 3160 tokenizer := txscript.MakeScriptTokenizer(scriptVer, txIn.SignatureScript) 3161 for i := 0; i <= idx; i++ { // contract is 5th item item in redemption and 4th in refund 3162 if !tokenizer.Next() { 3163 continue txinloop 3164 } 3165 } 3166 if isContract(tokenizer.Data()) { 3167 return true 3168 } 3169 } 3170 return false 3171 } 3172 3173 // Swap redemptions and refunds 3174 contractIsSwap := func(contract []byte) bool { 3175 _, _, _, _, err := dexbtc.ExtractSwapDetails(contract, false /* segwit */, w.btcParams) 3176 return err == nil 3177 } 3178 redeemsSwap := func(msgTx *zTx) bool { 3179 return containsContractAtPushIndex(msgTx, 4, contractIsSwap) 3180 } 3181 if redeemsSwap(msgTx) { 3182 return &asset.WalletTransaction{ 3183 Type: asset.Redeem, 3184 ID: tx.TxID, 3185 Amount: totalOut + fee, 3186 Fees: fee, 3187 }, nil 3188 } 3189 refundsSwap := func(msgTx *zTx) bool { 3190 return containsContractAtPushIndex(msgTx, 3, contractIsSwap) 3191 } 3192 if refundsSwap(msgTx) { 3193 return &asset.WalletTransaction{ 3194 Type: asset.Refund, 3195 ID: tx.TxID, 3196 Amount: totalOut + fee, 3197 Fees: fee, 3198 }, nil 3199 } 3200 3201 // Bond refunds 3202 redeemsBond := func(msgTx *zTx) (bool, *asset.BondTxInfo) { 3203 var bondInfo *asset.BondTxInfo 3204 isBond := func(contract []byte) bool { 3205 const scriptVer = 0 3206 lockTime, pkHash, err := dexbtc.ExtractBondDetailsV0(scriptVer, contract) 3207 if err != nil { 3208 return false 3209 } 3210 bondInfo = &asset.BondTxInfo{ 3211 AccountID: []byte{}, // Could look for the bond tx to get this, I guess. 3212 LockTime: uint64(lockTime), 3213 BondID: pkHash[:], 3214 } 3215 return true 3216 } 3217 return containsContractAtPushIndex(msgTx, 2, isBond), bondInfo 3218 } 3219 if isBondRedemption, bondInfo := redeemsBond(msgTx); isBondRedemption { 3220 return &asset.WalletTransaction{ 3221 Type: asset.RedeemBond, 3222 ID: tx.TxID, 3223 Amount: totalOut, 3224 Fees: fee, 3225 BondInfo: bondInfo, 3226 }, nil 3227 } 3228 3229 const scriptVersion = 0 3230 3231 allOutputsPayUs := func(msgTx *zTx) bool { 3232 for _, txOut := range msgTx.TxOut { 3233 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams) 3234 if err != nil { 3235 w.log.Errorf("ExtractAddrs error: %w", err) 3236 return false 3237 } 3238 if len(addrs) != 1 { // sanity check 3239 return false 3240 } 3241 3242 addr, err := dexzec.EncodeAddress(addrs[0], w.addrParams) 3243 if err != nil { 3244 w.log.Errorf("unable to encode address: %w", err) 3245 return false 3246 } 3247 owns, err := w.OwnsDepositAddress(addr) 3248 if err != nil { 3249 w.log.Errorf("w.OwnsDepositAddress error: %w", err) 3250 return false 3251 } 3252 if !owns { 3253 return false 3254 } 3255 } 3256 3257 return true 3258 } 3259 3260 if tx.Category == sendCategory && allOutputsPayUs(msgTx) && len(msgTx.TxIn) == 1 { 3261 return &asset.WalletTransaction{ 3262 Type: asset.Split, 3263 ID: tx.TxID, 3264 Fees: fee, 3265 }, nil 3266 } 3267 3268 txOutDirection := func(msgTx *zTx) (in, out uint64) { 3269 for _, txOut := range msgTx.TxOut { 3270 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams) 3271 if err != nil { 3272 w.log.Errorf("ExtractAddrs error: %w", err) 3273 continue 3274 } 3275 if len(addrs) != 1 { // sanity check 3276 continue 3277 } 3278 3279 addr, err := dexzec.EncodeAddress(addrs[0], w.addrParams) 3280 if err != nil { 3281 w.log.Errorf("unable to encode address: %w", err) 3282 continue 3283 } 3284 owns, err := w.OwnsDepositAddress(addr) 3285 if err != nil { 3286 w.log.Errorf("w.OwnsDepositAddress error: %w", err) 3287 continue 3288 } 3289 if owns { 3290 in += uint64(txOut.Value) 3291 } else { 3292 out += uint64(txOut.Value) 3293 } 3294 } 3295 return 3296 } 3297 3298 in, out := txOutDirection(msgTx) 3299 3300 getRecipient := func(msgTx *zTx, receive bool) *string { 3301 for _, txOut := range msgTx.TxOut { 3302 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, w.btcParams) 3303 if err != nil { 3304 w.log.Errorf("ExtractAddrs error: %w", err) 3305 continue 3306 } 3307 if len(addrs) != 1 { // sanity check 3308 continue 3309 } 3310 3311 addr, err := dexzec.EncodeAddress(addrs[0], w.addrParams) 3312 if err != nil { 3313 w.log.Errorf("unable to encode address: %w", err) 3314 continue 3315 } 3316 owns, err := w.OwnsDepositAddress(addr) 3317 if err != nil { 3318 w.log.Errorf("w.OwnsDepositAddress error: %w", err) 3319 continue 3320 } 3321 3322 if receive == owns { 3323 return &addr 3324 } 3325 } 3326 return nil 3327 } 3328 3329 if tx.Category == sendCategory { 3330 txType := asset.Send 3331 if allOutputsPayUs(msgTx) { 3332 txType = asset.SelfSend 3333 } 3334 return &asset.WalletTransaction{ 3335 Type: txType, 3336 ID: tx.TxID, 3337 Amount: out, 3338 Fees: fee, 3339 Recipient: getRecipient(msgTx, false), 3340 }, nil 3341 } 3342 3343 return &asset.WalletTransaction{ 3344 Type: asset.Receive, 3345 ID: tx.TxID, 3346 Amount: in, 3347 Fees: fee, 3348 Recipient: getRecipient(msgTx, true), 3349 }, nil 3350 } 3351 3352 // addUnknownTransactionsToHistory checks for any transactions the wallet has 3353 // made or recieved that are not part of the transaction history. It scans 3354 // from the last point to which it had previously scanned to the current tip. 3355 func (w *zecWallet) addUnknownTransactionsToHistory(tip uint64) { 3356 txHistoryDB := w.txDB() 3357 3358 // Zcash has a maximum reorg length of 100 blocks. 3359 const blockQueryBuffer = 100 3360 var blockToQuery uint64 3361 lastQuery := w.receiveTxLastQuery.Load() 3362 if lastQuery == 0 { 3363 // TODO: use wallet birthday instead of block 0. 3364 // blockToQuery = 0 3365 } else if lastQuery < tip-blockQueryBuffer { 3366 blockToQuery = lastQuery - blockQueryBuffer 3367 } else { 3368 blockToQuery = tip - blockQueryBuffer 3369 } 3370 3371 txs, err := w.listSinceBlock(int64(blockToQuery)) 3372 if err != nil { 3373 w.log.Errorf("Error listing transactions since block %d: %v", blockToQuery, err) 3374 return 3375 } 3376 3377 for _, tx := range txs { 3378 if w.ctx.Err() != nil { 3379 return 3380 } 3381 txHash, err := chainhash.NewHashFromStr(tx.TxID) 3382 if err != nil { 3383 w.log.Errorf("Error decoding tx hash %s: %v", tx.TxID, err) 3384 continue 3385 } 3386 _, err = txHistoryDB.GetTx(txHash.String()) 3387 if err == nil { 3388 continue 3389 } 3390 if !errors.Is(err, asset.CoinNotFoundError) { 3391 w.log.Errorf("Error getting tx %s: %v", txHash.String(), err) 3392 continue 3393 } 3394 wt, err := w.idUnknownTx(&tx) 3395 if err != nil { 3396 w.log.Errorf("error identifying transaction: %v", err) 3397 continue 3398 } 3399 3400 if tx.BlockIndex != nil && *tx.BlockIndex > 0 && *tx.BlockIndex < int64(tip-blockQueryBuffer) { 3401 wt.BlockNumber = uint64(*tx.BlockIndex) 3402 wt.Timestamp = uint64(tx.BlockTime) 3403 } 3404 3405 // Don't send notifications for the initial sync to avoid spamming the 3406 // front end. A notification is sent at the end of the initial sync. 3407 w.addTxToHistory(wt, txHash, true, blockToQuery == 0) 3408 } 3409 3410 w.receiveTxLastQuery.Store(tip) 3411 err = txHistoryDB.SetLastReceiveTxQuery(tip) 3412 if err != nil { 3413 w.log.Errorf("Error setting last query to %d: %v", tip, err) 3414 } 3415 3416 if blockToQuery == 0 { 3417 w.emit.TransactionHistorySyncedNote() 3418 } 3419 } 3420 3421 // syncTxHistory checks to see if there are any transactions which the wallet 3422 // has made or recieved that are not part of the transaction history, then 3423 // identifies and adds them. It also checks all the pending transactions to see 3424 // if they have been mined into a block, and if so, updates the transaction 3425 // history to reflect the block height. 3426 func (w *zecWallet) syncTxHistory(tip uint64) { 3427 if !w.syncingTxHistory.CompareAndSwap(false, true) { 3428 return 3429 } 3430 defer w.syncingTxHistory.Store(false) 3431 3432 txHistoryDB := w.txDB() 3433 if txHistoryDB == nil { 3434 // It's actually impossible to get here, because we error and return 3435 // early in Connect if startTxHistoryDB returns an error, but we'll 3436 // log this for good measure anyway. 3437 w.log.Error("Transaction history database was not initialized") 3438 return 3439 } 3440 3441 ss, err := w.SyncStatus() 3442 if err != nil { 3443 w.log.Errorf("Error getting sync status: %v", err) 3444 return 3445 } 3446 if !ss.Synced { 3447 return 3448 } 3449 3450 w.addUnknownTransactionsToHistory(tip) 3451 3452 pendingTxsCopy := make(map[chainhash.Hash]btc.ExtendedWalletTx, len(w.pendingTxs)) 3453 w.pendingTxsMtx.RLock() 3454 for hash, tx := range w.pendingTxs { 3455 pendingTxsCopy[hash] = *tx 3456 } 3457 w.pendingTxsMtx.RUnlock() 3458 3459 handlePendingTx := func(txHash chainhash.Hash, tx *btc.ExtendedWalletTx) { 3460 if !tx.Submitted { 3461 return 3462 } 3463 3464 gtr, err := getTransaction(w, &txHash) 3465 if errors.Is(err, asset.CoinNotFoundError) { 3466 err = txHistoryDB.RemoveTx(txHash.String()) 3467 if err == nil { 3468 w.pendingTxsMtx.Lock() 3469 delete(w.pendingTxs, txHash) 3470 w.pendingTxsMtx.Unlock() 3471 } else { 3472 // Leave it in the pendingPendingTxs and attempt to remove it 3473 // again next time. 3474 w.log.Errorf("Error removing tx %s from the history store: %v", txHash.String(), err) 3475 } 3476 return 3477 } 3478 if err != nil { 3479 if w.ctx.Err() != nil { 3480 return 3481 } 3482 w.log.Errorf("Error getting transaction %s: %v", txHash, err) 3483 return 3484 } 3485 3486 var updated bool 3487 if gtr.blockHash != nil && *gtr.blockHash != (chainhash.Hash{}) { 3488 block, _, err := getVerboseBlockHeader(w, gtr.blockHash) 3489 if err != nil { 3490 w.log.Errorf("Error getting block height for %s: %v", gtr.blockHash, err) 3491 return 3492 } 3493 blockHeight := block.Height 3494 if tx.BlockNumber != uint64(blockHeight) || tx.Timestamp != uint64(block.Time) { 3495 tx.BlockNumber = uint64(blockHeight) 3496 tx.Timestamp = uint64(block.Time) 3497 updated = true 3498 } 3499 } else if gtr.blockHash == nil && tx.BlockNumber != 0 { 3500 tx.BlockNumber = 0 3501 tx.Timestamp = 0 3502 updated = true 3503 } 3504 3505 var confs uint64 3506 if tx.BlockNumber > 0 && tip >= tx.BlockNumber { 3507 confs = tip - tx.BlockNumber + 1 3508 } 3509 if confs >= defaultConfTarget { 3510 tx.Confirmed = true 3511 updated = true 3512 } 3513 3514 if updated { 3515 err = txHistoryDB.StoreTx(tx) 3516 if err != nil { 3517 w.log.Errorf("Error updating tx %s: %v", txHash, err) 3518 return 3519 } 3520 3521 w.pendingTxsMtx.Lock() 3522 if tx.Confirmed { 3523 delete(w.pendingTxs, txHash) 3524 } else { 3525 w.pendingTxs[txHash] = tx 3526 } 3527 w.pendingTxsMtx.Unlock() 3528 3529 w.emit.TransactionNote(tx.WalletTransaction, false) 3530 } 3531 } 3532 3533 for hash, tx := range pendingTxsCopy { 3534 handlePendingTx(hash, &tx) 3535 } 3536 }