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