decred.org/dcrdex@v1.0.3/client/asset/btc/btc.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 btc 5 6 import ( 7 "bytes" 8 "context" 9 "crypto/sha256" 10 "encoding/binary" 11 "encoding/hex" 12 "errors" 13 "fmt" 14 "io" 15 "math" 16 "os" 17 "path/filepath" 18 "regexp" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 "sync/atomic" 24 "time" 25 26 "decred.org/dcrdex/client/asset" 27 "decred.org/dcrdex/dex" 28 "decred.org/dcrdex/dex/calc" 29 "decred.org/dcrdex/dex/config" 30 "decred.org/dcrdex/dex/dexnet" 31 dexbtc "decred.org/dcrdex/dex/networks/btc" 32 "github.com/btcsuite/btcd/btcec/v2" 33 "github.com/btcsuite/btcd/btcec/v2/ecdsa" 34 "github.com/btcsuite/btcd/btcjson" 35 "github.com/btcsuite/btcd/btcutil" 36 "github.com/btcsuite/btcd/chaincfg" 37 "github.com/btcsuite/btcd/chaincfg/chainhash" 38 "github.com/btcsuite/btcd/txscript" 39 "github.com/btcsuite/btcd/wire" 40 "github.com/btcsuite/btcwallet/wallet" 41 "github.com/decred/dcrd/dcrec/secp256k1/v4" 42 "github.com/decred/dcrd/rpcclient/v8" 43 ) 44 45 const ( 46 version = 0 47 48 // BipID is the BIP-0044 asset ID. 49 BipID = 0 50 51 // The default fee is passed to the user as part of the asset.WalletInfo 52 // structure. 53 defaultFee = 100 54 // defaultFeeRateLimit is the default value for the feeratelimit. 55 defaultFeeRateLimit = 1400 56 // defaultRedeemConfTarget is the default redeem transaction confirmation 57 // target in blocks used by estimatesmartfee to get the optimal fee for a 58 // redeem transaction. 59 defaultRedeemConfTarget = 2 60 61 minNetworkVersion = 270000 62 minProtocolVersion = 70015 63 // version which descriptor wallets have been introduced. 64 minDescriptorVersion = 220000 65 66 // splitTxBaggage is the total number of additional bytes associated with 67 // using a split transaction to fund a swap. 68 splitTxBaggage = dexbtc.MinimumTxOverhead + dexbtc.RedeemP2PKHInputSize + 2*dexbtc.P2PKHOutputSize 69 // splitTxBaggageSegwit it the analogue of splitTxBaggage for segwit. 70 // We include the 2 bytes for marker and flag. 71 splitTxBaggageSegwit = dexbtc.MinimumTxOverhead + 2*dexbtc.P2WPKHOutputSize + 72 dexbtc.RedeemP2WPKHInputSize + ((dexbtc.RedeemP2WPKHInputWitnessWeight + dexbtc.SegwitMarkerAndFlagWeight + 3) / 4) 73 74 walletTypeLegacy = "" 75 walletTypeRPC = "bitcoindRPC" 76 walletTypeSPV = "SPV" 77 walletTypeElectrum = "electrumRPC" 78 79 swapFeeBumpKey = "swapfeebump" 80 splitKey = "swapsplit" 81 multiSplitKey = "multisplit" 82 multiSplitBufferKey = "multisplitbuffer" 83 redeemFeeBumpFee = "redeemfeebump" 84 85 // requiredRedeemConfirms is the amount of confirms a redeem transaction 86 // needs before the trade is considered confirmed. The redeem is 87 // monitored until this number of confirms is reached. 88 requiredRedeemConfirms = 1 89 ) 90 91 const ( 92 minTimeBeforeAcceleration uint64 = 3600 // 1 hour 93 ) 94 95 var ( 96 // ContractSearchLimit is how far back in time AuditContract in SPV mode 97 // will search for a contract if no txData is provided. This should be a 98 // positive duration. 99 ContractSearchLimit = 48 * time.Hour 100 101 // blockTicker is the delay between calls to check for new blocks. 102 blockTicker = time.Second 103 peerCountTicker = 5 * time.Second 104 walletBlockAllowance = time.Second * 10 105 conventionalConversionFactor = float64(dexbtc.UnitInfo.Conventional.ConversionFactor) 106 107 ElectrumConfigOpts = []*asset.ConfigOption{ 108 { 109 Key: "rpcuser", 110 DisplayName: "JSON-RPC Username", 111 Description: "Electrum's 'rpcuser' setting", 112 }, 113 { 114 Key: "rpcpassword", 115 DisplayName: "JSON-RPC Password", 116 Description: "Electrum's 'rpcpassword' setting", 117 NoEcho: true, 118 }, 119 { 120 Key: "rpcport", 121 DisplayName: "JSON-RPC Port", 122 Description: "Electrum's 'rpcport' (if not set with rpcbind)", 123 }, 124 { 125 Key: "rpcbind", // match RPCConfig struct field tags 126 DisplayName: "JSON-RPC Address", 127 Description: "Electrum's 'rpchost' <addr> or <addr>:<port>", 128 DefaultValue: "127.0.0.1", 129 }, 130 { 131 Key: "walletname", // match RPCConfig struct field tags 132 DisplayName: "Wallet File", 133 Description: "Full path to the wallet file (empty is default_wallet)", 134 DefaultValue: "", // empty string, not a nil interface 135 }, 136 } 137 138 // 02 Jun 21 21:12 CDT 139 defaultWalletBirthdayUnix = 1622668320 140 DefaultWalletBirthday = time.Unix(int64(defaultWalletBirthdayUnix), 0) 141 142 MultiFundingOpts = []*asset.OrderOption{ 143 { 144 ConfigOption: asset.ConfigOption{ 145 Key: multiSplitKey, 146 DisplayName: "Allow multi split", 147 Description: "Allow split funding transactions that pre-size outputs to " + 148 "prevent excessive overlock.", 149 IsBoolean: true, 150 DefaultValue: true, 151 }, 152 }, 153 { 154 ConfigOption: asset.ConfigOption{ 155 Key: multiSplitBufferKey, 156 DisplayName: "Multi split buffer", 157 Description: "Add an integer percent buffer to split output amounts to " + 158 "facilitate output reuse. This is only required for quote assets.", 159 DefaultValue: 5, 160 DependsOn: multiSplitKey, 161 }, 162 QuoteAssetOnly: true, 163 XYRange: &asset.XYRange{ 164 Start: asset.XYRangePoint{ 165 Label: "0%", 166 X: 0, 167 Y: 0, 168 }, 169 End: asset.XYRangePoint{ 170 Label: "100%", 171 X: 100, 172 Y: 100, 173 }, 174 XUnit: "%", 175 YUnit: "%", 176 RoundX: true, 177 RoundY: true, 178 }, 179 }, 180 } 181 182 rpcWalletDefinition = &asset.WalletDefinition{ 183 Type: walletTypeRPC, 184 Tab: "External", 185 Description: "Connect to bitcoind", 186 DefaultConfigPath: dexbtc.SystemConfigPath("bitcoin"), 187 ConfigOpts: append(RPCConfigOpts("Bitcoin", "8332"), CommonConfigOpts("BTC", false)...), 188 MultiFundingOpts: MultiFundingOpts, 189 } 190 spvWalletDefinition = &asset.WalletDefinition{ 191 Type: walletTypeSPV, 192 Tab: "Native", 193 Description: "Use the built-in SPV wallet", 194 ConfigOpts: CommonConfigOpts("BTC", true), 195 Seeded: true, 196 MultiFundingOpts: MultiFundingOpts, 197 } 198 199 electrumWalletDefinition = &asset.WalletDefinition{ 200 Type: walletTypeElectrum, 201 Tab: "Electrum (external)", 202 Description: "Use an external Electrum Wallet", 203 // json: DefaultConfigPath: filepath.Join(btcutil.AppDataDir("electrum", false), "config"), // e.g. ~/.electrum/config 204 ConfigOpts: append(ElectrumConfigOpts, CommonConfigOpts("BTC", false)...), 205 MultiFundingOpts: MultiFundingOpts, 206 } 207 208 // WalletInfo defines some general information about a Bitcoin wallet. 209 WalletInfo = &asset.WalletInfo{ 210 Name: "Bitcoin", 211 SupportedVersions: []uint32{version}, 212 UnitInfo: dexbtc.UnitInfo, 213 AvailableWallets: []*asset.WalletDefinition{ 214 spvWalletDefinition, 215 rpcWalletDefinition, 216 electrumWalletDefinition, 217 }, 218 LegacyWalletIndex: 1, 219 } 220 ) 221 222 func apiFallbackOpt(defaultV bool) *asset.ConfigOption { 223 return &asset.ConfigOption{ 224 Key: "apifeefallback", 225 DisplayName: "External fee rate estimates", 226 Description: "Allow fee rate estimation from a block explorer API. " + 227 "This is useful as a fallback for SPV wallets and RPC wallets " + 228 "that have recently been started.", 229 IsBoolean: true, 230 DefaultValue: defaultV, 231 } 232 } 233 234 // CommonConfigOpts are the common options that the Wallets recognize. 235 func CommonConfigOpts(symbol string /* upper-case */, withApiFallback bool) []*asset.ConfigOption { 236 opts := []*asset.ConfigOption{ 237 { 238 Key: "fallbackfee", 239 DisplayName: "Fallback fee rate", 240 Description: fmt.Sprintf("The fee rate to use for sending or withdrawing funds and fee payment when"+ 241 " estimatesmartfee is not available. Units: %s/kB", symbol), 242 DefaultValue: defaultFee * 1000 / 1e8, 243 }, 244 { 245 Key: "feeratelimit", 246 DisplayName: "Highest acceptable fee rate", 247 Description: fmt.Sprintf("This is the highest network fee rate you are willing to "+ 248 "pay on swap transactions. If feeratelimit is lower than a market's "+ 249 "maxfeerate, you will not be able to trade on that market with this "+ 250 "wallet. Units: %s/kB", symbol), 251 DefaultValue: defaultFeeRateLimit * 1000 / 1e8, 252 }, 253 { 254 Key: "redeemconftarget", 255 DisplayName: "Redeem confirmation target", 256 Description: "The target number of blocks for the redeem transaction " + 257 "to be mined. Used to set the transaction's fee rate. " + 258 "(default: 2 blocks)", 259 DefaultValue: defaultRedeemConfTarget, 260 }, 261 { 262 Key: "txsplit", 263 DisplayName: "Pre-size funding inputs", 264 Description: "When placing an order, create a \"split\" transaction to " + 265 "fund the order without locking more of the wallet balance than " + 266 "necessary. Otherwise, excess funds may be reserved to fund the order " + 267 "until the first swap contract is broadcast during match settlement, " + 268 "or the order is canceled. This an extra transaction for which network " + 269 "mining fees are paid.", 270 IsBoolean: true, 271 DefaultValue: false, 272 }, 273 } 274 275 if withApiFallback { 276 opts = append(opts, apiFallbackOpt(true)) 277 } 278 return opts 279 } 280 281 // RPCConfigOpts are the settings that are used to connect to an external RPC 282 // wallet. 283 func RPCConfigOpts(name, rpcPort string) []*asset.ConfigOption { 284 return []*asset.ConfigOption{ 285 { 286 Key: "walletname", 287 DisplayName: "Wallet Name", 288 Description: "The wallet name", 289 }, 290 { 291 Key: "rpcuser", 292 DisplayName: "JSON-RPC Username", 293 Description: fmt.Sprintf("%s's 'rpcuser' setting", name), 294 }, 295 { 296 Key: "rpcpassword", 297 DisplayName: "JSON-RPC Password", 298 Description: fmt.Sprintf("%s's 'rpcpassword' setting", name), 299 NoEcho: true, 300 }, 301 { 302 Key: "rpcbind", 303 DisplayName: "JSON-RPC Address", 304 Description: "<addr> or <addr>:<port> (default 'localhost')", 305 DefaultValue: "127.0.0.1", 306 }, 307 { 308 Key: "rpcport", 309 DisplayName: "JSON-RPC Port", 310 Description: "Port for RPC connections (if not set in rpcbind)", 311 DefaultValue: rpcPort, 312 }, 313 } 314 } 315 316 // TxInSigner is a transaction input signer. In addition to the standard Bitcoin 317 // arguments, TxInSigner receives all values and pubkey scripts for previous 318 // outpoints spent in this transaction. 319 type TxInSigner func(tx *wire.MsgTx, idx int, subScript []byte, hashType txscript.SigHashType, 320 key *btcec.PrivateKey, vals []int64, prevScripts [][]byte) ([]byte, error) 321 322 // BTCCloneCFG holds clone specific parameters. 323 type BTCCloneCFG struct { 324 WalletCFG *asset.WalletConfig 325 MinNetworkVersion uint64 326 MinElectrumVersion dex.Semver 327 WalletInfo *asset.WalletInfo 328 Symbol string 329 Logger dex.Logger 330 Network dex.Network 331 ChainParams *chaincfg.Params 332 // Ports is the default wallet RPC tcp ports used when undefined in 333 // WalletConfig. 334 Ports dexbtc.NetPorts 335 DefaultFallbackFee uint64 // sats/byte 336 DefaultFeeRateLimit uint64 // sats/byte 337 // LegacyBalance is for clones that don't yet support the 'getbalances' RPC 338 // call. 339 LegacyBalance bool 340 // BalanceFunc is a custom function for getting the wallet's balance. 341 // BalanceFunc precludes any other methods of balance retrieval. 342 BalanceFunc func(ctx context.Context, locked uint64) (*asset.Balance, error) 343 // If segwit is false, legacy addresses and contracts will be used. This 344 // setting must match the configuration of the server's asset backend. 345 Segwit bool 346 // LegacyRawFeeLimit can be true if the RPC only supports the boolean 347 // allowHighFees argument to the sendrawtransaction RPC. 348 LegacyRawFeeLimit bool 349 // InitTxSize is the size of a swap initiation transaction with a single 350 // input i.e. chained swaps. 351 InitTxSize uint32 352 // InitTxSizeBase is the size of a swap initiation transaction with no 353 // inputs. This is used to accurately determine the size of the first swap 354 // in a chain when considered with the actual inputs. 355 InitTxSizeBase uint32 356 // PrivKeyFunc is an optional function to get a private key for an address 357 // from the wallet. If not given the usual dumpprivkey RPC will be used. 358 PrivKeyFunc func(addr string) (*btcec.PrivateKey, error) 359 // AddressDecoder is an optional argument that can decode an address string 360 // into btcutil.Address. If AddressDecoder is not supplied, 361 // btcutil.DecodeAddress will be used. 362 AddressDecoder dexbtc.AddressDecoder // string => btcutil.Address 363 // AddressStringer is an optional argument that can encode a btcutil.Address 364 // into an address string. If AddressStringer is not supplied, the 365 // (btcutil.Address).String method will be used. 366 AddressStringer dexbtc.AddressStringer // btcutil.Address => string, may be an override or just the String method 367 // BlockDeserializer can be used in place of (*wire.MsgBlock).Deserialize. 368 BlockDeserializer func([]byte) (*wire.MsgBlock, error) 369 // ArglessChangeAddrRPC can be true if the getrawchangeaddress takes no 370 // address-type argument. 371 ArglessChangeAddrRPC bool 372 // NonSegwitSigner can be true if the transaction signature hash data is not 373 // the standard for non-segwit Bitcoin. If nil, txscript. 374 NonSegwitSigner TxInSigner 375 // ConnectFunc, if provided, is called by the RPC client at the end of the 376 // (*rpcClient).connect method. Errors returned by ConnectFunc will preclude 377 // the starting of goroutines associated with block and peer monitoring. 378 ConnectFunc func() error 379 // FeeEstimator provides a way to get fees given an RawRequest-enabled 380 // client and a confirmation target. 381 FeeEstimator func(context.Context, RawRequester, uint64) (uint64, error) 382 // ExternalFeeEstimator should be supplied if the clone provides the 383 // apifeefallback ConfigOpt. TODO: confTarget uint64 384 ExternalFeeEstimator func(context.Context, dex.Network) (uint64, error) 385 // ExternalFeeShelfLife can be set to adjust the time to staleness of 386 // external fee rates. Default is 5 minutes. 387 ExternalFeeShelfLife time.Duration 388 // OmitAddressType causes the address type (bech32, legacy) to be omitted 389 // from calls to getnewaddress. 390 OmitAddressType bool 391 // LegacySignTxRPC causes the RPC client to use the signrawtransaction 392 // endpoint instead of the signrawtransactionwithwallet endpoint. 393 LegacySignTxRPC bool 394 // BooleanGetBlockRPC causes the RPC client to use a boolean second argument 395 // for the getblock endpoint, instead of Bitcoin's numeric. 396 BooleanGetBlockRPC bool 397 // LegacyValidateAddressRPC uses the validateaddress endpoint instead of 398 // getaddressinfo in order to discover ownership of an address. 399 LegacyValidateAddressRPC bool 400 // SingularWallet signals that the node software supports only one wallet, 401 // so the RPC endpoint does not have a /wallet/{walletname} path. 402 SingularWallet bool 403 // UnlockSpends manually unlocks outputs as they are spent. Most assets will 404 // unlock wallet outputs automatically as they are spent. 405 UnlockSpends bool 406 // TxDeserializer is an optional function used to deserialize a transaction. 407 TxDeserializer func([]byte) (*wire.MsgTx, error) 408 // TxSerializer is an optional function used to serialize a transaction. 409 TxSerializer func(*wire.MsgTx) ([]byte, error) 410 // TxHasher is a function that generates a tx hash from a MsgTx. 411 TxHasher func(*wire.MsgTx) *chainhash.Hash 412 // TxSizeCalculator is an optional function that will be used to calculate 413 // the size of a transaction. 414 TxSizeCalculator func(*wire.MsgTx) uint64 415 // NumericGetRawRPC uses a numeric boolean indicator for the 416 // getrawtransaction RPC. 417 NumericGetRawRPC bool 418 // TxVersion is an optional function that returns a version to use for 419 // new transactions. 420 TxVersion func() int32 421 // ManualMedianTime causes the median time to be calculated manually. 422 ManualMedianTime bool 423 // ConstantDustLimit is used if an asset enforces a dust limit (minimum 424 // output value) that doesn't depend on the serialized size of the output. 425 // If ConstantDustLimit is zero, dexbtc.IsDust is used. 426 ConstantDustLimit uint64 427 // OmitRPCOptionsArg is for clones that don't take an options argument. 428 OmitRPCOptionsArg bool 429 // AssetID is the asset ID of the clone. 430 AssetID uint32 431 } 432 433 // PaymentScripter can be implemented to make non-standard payment scripts. 434 type PaymentScripter interface { 435 PaymentScript() ([]byte, error) 436 } 437 438 // RPCConfig adds a wallet name to the basic configuration. 439 type RPCConfig struct { 440 dexbtc.RPCConfig `ini:",extends"` 441 WalletName string `ini:"walletname"` 442 } 443 444 // RPCWalletConfig is a combination of RPCConfig and WalletConfig. Used for a 445 // wallet based on a bitcoind-like RPC API. 446 type RPCWalletConfig struct { 447 RPCConfig `ini:",extends"` 448 WalletConfig `ini:",extends"` 449 } 450 451 // WalletConfig are wallet-level configuration settings. 452 type WalletConfig struct { 453 UseSplitTx bool `ini:"txsplit"` 454 FallbackFeeRate float64 `ini:"fallbackfee"` 455 FeeRateLimit float64 `ini:"feeratelimit"` 456 RedeemConfTarget uint64 `ini:"redeemconftarget"` 457 ActivelyUsed bool `ini:"special_activelyUsed"` // injected by core 458 ApiFeeFallback bool `ini:"apifeefallback"` 459 } 460 461 func readBaseWalletConfig(walletCfg *WalletConfig) (*baseWalletConfig, error) { 462 cfg := &baseWalletConfig{} 463 // if values not specified, use defaults. As they are validated as BTC/KB, 464 // we need to convert first. 465 if walletCfg.FallbackFeeRate == 0 { 466 walletCfg.FallbackFeeRate = float64(defaultFee) * 1000 / 1e8 467 } 468 if walletCfg.FeeRateLimit == 0 { 469 walletCfg.FeeRateLimit = float64(defaultFeeRateLimit) * 1000 / 1e8 470 } 471 if walletCfg.RedeemConfTarget == 0 { 472 walletCfg.RedeemConfTarget = defaultRedeemConfTarget 473 } 474 // If set in the user config, the fallback fee will be in conventional units 475 // per kB, e.g. BTC/kB. Translate that to sats/byte. 476 cfg.fallbackFeeRate = toSatoshi(walletCfg.FallbackFeeRate / 1000) 477 if cfg.fallbackFeeRate == 0 { 478 return nil, fmt.Errorf("fallback fee rate limit is smaller than the minimum 1000 sats/byte: %v", 479 walletCfg.FallbackFeeRate) 480 } 481 // If set in the user config, the fee rate limit will be in units of BTC/KB. 482 // Convert to sats/byte & error if value is smaller than smallest unit. 483 cfg.feeRateLimit = toSatoshi(walletCfg.FeeRateLimit / 1000) 484 if cfg.feeRateLimit == 0 { 485 return nil, fmt.Errorf("fee rate limit is smaller than the minimum 1000 sats/byte: %v", 486 walletCfg.FeeRateLimit) 487 } 488 489 cfg.redeemConfTarget = walletCfg.RedeemConfTarget 490 cfg.useSplitTx = walletCfg.UseSplitTx 491 cfg.apiFeeFallback = walletCfg.ApiFeeFallback 492 493 return cfg, nil 494 } 495 496 // readRPCWalletConfig parses the settings map into a *RPCWalletConfig. 497 func readRPCWalletConfig(settings map[string]string, symbol string, net dex.Network, ports dexbtc.NetPorts) (cfg *RPCWalletConfig, err error) { 498 cfg = new(RPCWalletConfig) 499 err = config.Unmapify(settings, cfg) 500 if err != nil { 501 return nil, fmt.Errorf("error parsing rpc wallet config: %w", err) 502 } 503 err = dexbtc.CheckRPCConfig(&cfg.RPCConfig.RPCConfig, symbol, net, ports) 504 return 505 } 506 507 // parseRPCWalletConfig parses a *RPCWalletConfig from the settings map and 508 // creates the unconnected *rpcclient.Client. 509 func parseRPCWalletConfig(settings map[string]string, symbol string, net dex.Network, 510 ports dexbtc.NetPorts, singularWallet bool) (*RPCWalletConfig, *rpcclient.Client, error) { 511 cfg, err := readRPCWalletConfig(settings, symbol, net, ports) 512 if err != nil { 513 return nil, nil, err 514 } 515 516 // For BTC, external fee rates are the default because of the instability 517 // of estimatesmartfee. 518 if symbol == "btc" { 519 cfg.ApiFeeFallback = true 520 } 521 522 cl, err := newRPCConnection(cfg, singularWallet) 523 if err != nil { 524 return nil, nil, err 525 } 526 527 return cfg, cl, nil 528 } 529 530 // newRPCConnection creates a new RPC client. 531 func newRPCConnection(cfg *RPCWalletConfig, singularWallet bool) (*rpcclient.Client, error) { 532 endpoint := cfg.RPCBind 533 if !singularWallet { 534 endpoint += "/wallet/" + cfg.WalletName 535 } 536 537 return rpcclient.New(&rpcclient.ConnConfig{ 538 HTTPPostMode: true, 539 DisableTLS: true, 540 Host: endpoint, 541 User: cfg.RPCUser, 542 Pass: cfg.RPCPass, 543 }, nil) 544 } 545 546 // Driver implements asset.Driver. 547 type Driver struct{} 548 549 // Check that Driver implements Driver and Creator. 550 var _ asset.Driver = (*Driver)(nil) 551 var _ asset.Creator = (*Driver)(nil) 552 553 // Exists checks the existence of the wallet. Part of the Creator interface, so 554 // only used for wallets with WalletDefinition.Seeded = true. 555 func (d *Driver) Exists(walletType, dataDir string, settings map[string]string, net dex.Network) (bool, error) { 556 if walletType != walletTypeSPV { 557 return false, fmt.Errorf("no Bitcoin wallet of type %q available", walletType) 558 } 559 560 chainParams, err := parseChainParams(net) 561 if err != nil { 562 return false, err 563 } 564 565 dir := filepath.Join(dataDir, chainParams.Name) 566 return walletExists(dir, chainParams) 567 } 568 569 // walletExists checks the existence of the wallet. 570 func walletExists(dir string, chainParams *chaincfg.Params) (bool, error) { 571 // timeout and recoverWindow arguments borrowed from btcwallet directly. 572 loader := wallet.NewLoader(chainParams, dir, true, dbTimeout, 250) 573 return loader.WalletExists() 574 } 575 576 // createConfig combines the configuration settings used for wallet creation. 577 type createConfig struct { 578 WalletConfig `ini:",extends"` 579 RecoveryCfg `ini:",extends"` 580 } 581 582 // Create creates a new SPV wallet. 583 func (d *Driver) Create(params *asset.CreateWalletParams) error { 584 if params.Type != walletTypeSPV { 585 return fmt.Errorf("SPV is the only seeded wallet type. required = %q, requested = %q", walletTypeSPV, params.Type) 586 } 587 if len(params.Seed) == 0 { 588 return errors.New("wallet seed cannot be empty") 589 } 590 if len(params.DataDir) == 0 { 591 return errors.New("must specify wallet data directory") 592 } 593 chainParams, err := parseChainParams(params.Net) 594 if err != nil { 595 return fmt.Errorf("error parsing chain: %w", err) 596 } 597 598 cfg := new(createConfig) 599 err = config.Unmapify(params.Settings, cfg) 600 if err != nil { 601 return err 602 } 603 604 _, err = readBaseWalletConfig(&cfg.WalletConfig) 605 if err != nil { 606 return err 607 } 608 609 bday := DefaultWalletBirthday 610 if params.Birthday != 0 { 611 bday = time.Unix(int64(params.Birthday), 0) 612 } 613 614 dir := filepath.Join(params.DataDir, chainParams.Name) 615 return createSPVWallet(params.Pass, params.Seed, bday, dir, 616 params.Logger, cfg.NumExternalAddresses, cfg.NumInternalAddresses, chainParams) 617 } 618 619 // Open opens or connects to the BTC exchange wallet. Start the wallet with its 620 // Run method. 621 func (d *Driver) Open(cfg *asset.WalletConfig, logger dex.Logger, network dex.Network) (asset.Wallet, error) { 622 return NewWallet(cfg, logger, network) 623 } 624 625 // DecodeCoinID creates a human-readable representation of a coin ID for 626 // Bitcoin. 627 func (d *Driver) DecodeCoinID(coinID []byte) (string, error) { 628 txid, vout, err := decodeCoinID(coinID) 629 if err != nil { 630 return "<invalid>", err 631 } 632 return fmt.Sprintf("%v:%d", txid, vout), err 633 } 634 635 // Info returns basic information about the wallet and asset. 636 func (d *Driver) Info() *asset.WalletInfo { 637 return WalletInfo 638 } 639 640 // MinLotSize calculates the minimum bond size for a given fee rate that avoids 641 // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't 642 // change. 643 func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 { 644 return dexbtc.MinLotSize(maxFeeRate, true) 645 } 646 647 type CustomSPVWalletConstructor func(settings map[string]string, params *chaincfg.Params) (BTCWallet, error) 648 649 // customSPVWalletConstructors are functions for setting up custom 650 // implementations of the BTCWallet interface that may be used by the 651 // ExchangeWalletSPV instead of the default spv implementation. 652 var customSPVWalletConstructors = map[string]CustomSPVWalletConstructor{} 653 654 // RegisterCustomSPVWallet registers a function that should be used in creating 655 // a BTCWallet implementation that the ExchangeWalletSPV will use in place of 656 // the default spv wallet implementation. External consumers can use this 657 // function to provide alternative BTCWallet implementations, and must do so 658 // before attempting to create an ExchangeWalletSPV instance of this type. It'll 659 // panic if callers try to register a wallet twice. 660 func RegisterCustomSPVWallet(constructor CustomSPVWalletConstructor, def *asset.WalletDefinition) { 661 for _, availableWallets := range WalletInfo.AvailableWallets { 662 if def.Type == availableWallets.Type { 663 panic(fmt.Sprintf("wallet type (%q) is already registered", def.Type)) 664 } 665 } 666 customSPVWalletConstructors[def.Type] = constructor 667 WalletInfo.AvailableWallets = append(WalletInfo.AvailableWallets, def) 668 } 669 670 // swapOptions captures the available Swap options. Tagged to be used with 671 // config.Unmapify to decode e.g. asset.Order.Options. 672 type swapOptions struct { 673 Split *bool `ini:"swapsplit"` 674 FeeBump *float64 `ini:"swapfeebump"` 675 } 676 677 func (s *swapOptions) feeBump() (float64, error) { 678 bump := 1.0 679 if s.FeeBump != nil { 680 bump = *s.FeeBump 681 if bump > 2.0 { 682 return 0, fmt.Errorf("fee bump %f is higher than the 2.0 limit", bump) 683 } 684 if bump < 1.0 { 685 return 0, fmt.Errorf("fee bump %f is lower than 1", bump) 686 } 687 } 688 return bump, nil 689 } 690 691 // fundMultiOptions are the possible order options when calling FundMultiOrder. 692 type fundMultiOptions struct { 693 // Split, if true, and multi-order cannot be funded with the existing UTXOs 694 // in the wallet without going over the maxLock limit, a split transaction 695 // will be created with one output per order. 696 // 697 // Use the multiSplitKey const defined above in the options map to set this option. 698 Split bool `ini:"multisplit"` 699 // SplitBuffer, if set, will instruct the wallet to add a buffer onto each 700 // output of the multi-order split transaction (if the split is needed). 701 // SplitBuffer is defined as a percentage of the output. If a .1 BTC output 702 // is required for an order and SplitBuffer is set to 5, a .105 BTC output 703 // will be created. 704 // 705 // The motivation for this is to assist market makers in having to do the 706 // least amount of splits as possible. It is useful when BTC is the quote 707 // asset on a market, and the price is increasing. During a market maker's 708 // operation, it will frequently have to cancel and replace orders as the 709 // rate moves. If BTC is the quote asset on a market, and the rate has 710 // lightly increased, the market maker will need to lock slightly more of 711 // the quote asset for the same amount of lots of the base asset. If there 712 // is no split buffer, this may necessitate a new split transaction. 713 // 714 // Use the multiSplitBufferKey const defined above in the options map to set this. 715 SplitBuffer float64 `ini:"multisplitbuffer"` 716 } 717 718 func decodeFundMultiOptions(options map[string]string) (*fundMultiOptions, error) { 719 opts := new(fundMultiOptions) 720 return opts, config.Unmapify(options, opts) 721 } 722 723 // redeemOptions are order options that apply to redemptions. 724 type redeemOptions struct { 725 FeeBump *float64 `ini:"redeemfeebump"` 726 } 727 728 func init() { 729 asset.Register(BipID, &Driver{}) 730 } 731 732 // baseWalletConfig is the validated, unit-converted, user-configurable wallet 733 // settings. 734 type baseWalletConfig struct { 735 fallbackFeeRate uint64 // atoms/byte 736 feeRateLimit uint64 // atoms/byte 737 redeemConfTarget uint64 738 useSplitTx bool 739 apiFeeFallback bool 740 } 741 742 // feeRateCache wraps a ExternalFeeEstimator function and caches results. 743 type feeRateCache struct { 744 f func(context.Context, dex.Network) (uint64, error) 745 shelfLife time.Duration 746 747 mtx sync.Mutex 748 fetchStamp time.Time 749 lastRate uint64 750 errorStamp time.Time 751 lastError error 752 } 753 754 func (c *feeRateCache) rate(ctx context.Context, net dex.Network) (uint64, error) { 755 c.mtx.Lock() 756 defer c.mtx.Unlock() 757 const defaultShelfLife = time.Minute * 5 758 shelfLife := defaultShelfLife 759 if c.shelfLife > 0 { 760 shelfLife = c.shelfLife 761 } 762 if time.Since(c.fetchStamp) < shelfLife { 763 return c.lastRate, nil 764 } 765 const errorDelay = time.Minute 766 if time.Since(c.errorStamp) < errorDelay { 767 return 0, c.lastError 768 } 769 feeRate, err := c.f(ctx, net) 770 if err != nil { 771 c.errorStamp = time.Now() 772 c.lastError = err 773 return 0, err 774 } 775 c.fetchStamp = time.Now() 776 c.lastRate = feeRate 777 return feeRate, nil 778 } 779 780 // baseWallet is a wallet backend for Bitcoin. The backend is how the DEX 781 // client app communicates with the BTC blockchain and wallet. baseWallet 782 // satisfies the dex.Wallet interface. 783 type baseWallet struct { 784 // 64-bit atomic variables first. See 785 // https://golang.org/pkg/sync/atomic/#pkg-note-BUG 786 tipAtConnect int64 787 788 cfgV atomic.Value // *baseWalletConfig 789 node Wallet 790 walletInfo *asset.WalletInfo 791 cloneParams *BTCCloneCFG 792 chainParams *chaincfg.Params 793 log dex.Logger 794 symbol string 795 emit *asset.WalletEmitter 796 lastPeerCount uint32 797 peersChange func(uint32, error) 798 minNetworkVersion uint64 799 dustLimit uint64 800 initTxSize uint64 801 initTxSizeBase uint64 802 useLegacyBalance bool 803 balanceFunc func(ctx context.Context, locked uint64) (*asset.Balance, error) 804 segwit bool 805 signNonSegwit TxInSigner 806 localFeeRate func(context.Context, RawRequester, uint64) (uint64, error) 807 feeCache *feeRateCache 808 decodeAddr dexbtc.AddressDecoder 809 walletDir string 810 811 deserializeTx func([]byte) (*wire.MsgTx, error) 812 serializeTx func(*wire.MsgTx) ([]byte, error) 813 calcTxSize func(*wire.MsgTx) uint64 814 hashTx func(*wire.MsgTx) *chainhash.Hash 815 816 stringAddr dexbtc.AddressStringer 817 818 txVersion func() int32 819 820 Network dex.Network 821 ctx context.Context // the asset subsystem starts with Connect(ctx) 822 823 // TODO: remove currentTip and the mutex, and make it local to the 824 // watchBlocks->reportNewTip call stack. The tests are reliant on current 825 // internals, so this will take a little work. 826 tipMtx sync.RWMutex 827 currentTip *BlockVector 828 829 cm *CoinManager 830 831 rf *RedemptionFinder 832 833 bondReserves atomic.Uint64 834 835 pendingTxsMtx sync.RWMutex 836 pendingTxs map[chainhash.Hash]ExtendedWalletTx 837 838 // receiveTxLastQuery stores the last block height at which the wallet 839 // was queried for recieve transactions. This is also stored in the 840 // txHistoryDB. 841 receiveTxLastQuery atomic.Uint64 842 843 txHistoryDB atomic.Value // *BadgerTxDB 844 845 ar *AddressRecycler 846 } 847 848 func (w *baseWallet) fallbackFeeRate() uint64 { 849 return w.cfgV.Load().(*baseWalletConfig).fallbackFeeRate 850 } 851 852 func (w *baseWallet) feeRateLimit() uint64 { 853 return w.cfgV.Load().(*baseWalletConfig).feeRateLimit 854 } 855 856 func (w *baseWallet) redeemConfTarget() uint64 { 857 return w.cfgV.Load().(*baseWalletConfig).redeemConfTarget 858 } 859 860 func (w *baseWallet) useSplitTx() bool { 861 return w.cfgV.Load().(*baseWalletConfig).useSplitTx 862 } 863 864 func (w *baseWallet) UseSplitTx() bool { 865 return w.useSplitTx() 866 } 867 868 func (w *baseWallet) apiFeeFallback() bool { 869 return w.cfgV.Load().(*baseWalletConfig).apiFeeFallback 870 } 871 872 type intermediaryWallet struct { 873 *baseWallet 874 txFeeEstimator txFeeEstimator 875 tipRedeemer tipRedemptionWallet 876 877 syncingTxHistory atomic.Bool 878 } 879 880 // ExchangeWalletSPV embeds a ExchangeWallet, but also provides the Rescan 881 // method to implement asset.Rescanner. 882 type ExchangeWalletSPV struct { 883 *intermediaryWallet 884 *authAddOn 885 886 spvNode *spvWallet 887 } 888 889 // ExchangeWalletFullNode implements Wallet and adds the FeeRate method. 890 type ExchangeWalletFullNode struct { 891 *intermediaryWallet 892 *authAddOn 893 } 894 895 type ExchangeWalletNoAuth struct { 896 *intermediaryWallet 897 } 898 899 // ExchangeWalletAccelerator implements the Accelerator interface on an 900 // ExchangeWalletFullNode. 901 type ExchangeWalletAccelerator struct { 902 *ExchangeWalletFullNode 903 } 904 905 // Check that wallets satisfy their supported interfaces. 906 var _ asset.Wallet = (*intermediaryWallet)(nil) 907 var _ asset.Accelerator = (*ExchangeWalletAccelerator)(nil) 908 var _ asset.Accelerator = (*ExchangeWalletSPV)(nil) 909 var _ asset.Withdrawer = (*baseWallet)(nil) 910 var _ asset.FeeRater = (*baseWallet)(nil) 911 var _ asset.Rescanner = (*ExchangeWalletSPV)(nil) 912 var _ asset.LogFiler = (*ExchangeWalletSPV)(nil) 913 var _ asset.Recoverer = (*ExchangeWalletSPV)(nil) 914 var _ asset.PeerManager = (*ExchangeWalletSPV)(nil) 915 var _ asset.TxFeeEstimator = (*intermediaryWallet)(nil) 916 var _ asset.Bonder = (*baseWallet)(nil) 917 var _ asset.Authenticator = (*ExchangeWalletSPV)(nil) 918 var _ asset.Authenticator = (*ExchangeWalletFullNode)(nil) 919 var _ asset.Authenticator = (*ExchangeWalletAccelerator)(nil) 920 var _ asset.AddressReturner = (*baseWallet)(nil) 921 var _ asset.WalletHistorian = (*ExchangeWalletSPV)(nil) 922 923 // RecoveryCfg is the information that is transferred from the old wallet 924 // to the new one when the wallet is recovered. 925 type RecoveryCfg struct { 926 NumExternalAddresses uint32 `ini:"numexternaladdr"` 927 NumInternalAddresses uint32 `ini:"numinternaladdr"` 928 } 929 930 // GetRecoveryCfg returns information that will help the wallet get 931 // back to its previous state after it is recreated. Part of the 932 // Recoverer interface. 933 func (btc *ExchangeWalletSPV) GetRecoveryCfg() (map[string]string, error) { 934 internal, external, err := btc.spvNode.numDerivedAddresses() 935 if err != nil { 936 return nil, err 937 } 938 939 reCfg := &RecoveryCfg{ 940 NumInternalAddresses: internal, 941 NumExternalAddresses: external, 942 } 943 cfg, err := config.Mapify(reCfg) 944 if err != nil { 945 return nil, err 946 } 947 948 return cfg, nil 949 } 950 951 // Destroy will delete all the wallet files so the wallet can be recreated. 952 // Part of the Recoverer interface. 953 func (btc *ExchangeWalletSPV) Move(backupDir string) error { 954 err := btc.spvNode.moveWalletData(backupDir) 955 if err != nil { 956 return fmt.Errorf("unable to move wallet data: %w", err) 957 } 958 959 return nil 960 } 961 962 // Rescan satisfies the asset.Rescanner interface, and issues a rescan wallet 963 // command if the backend is an SPV wallet. 964 func (btc *ExchangeWalletSPV) Rescan(_ context.Context, _ /* bday already stored internally */ uint64) error { 965 atomic.StoreInt64(&btc.tipAtConnect, 0) // for progress 966 // Caller should start calling SyncStatus on a ticker. 967 if err := btc.spvNode.wallet.RescanAsync(); err != nil { 968 return err 969 } 970 btc.receiveTxLastQuery.Store(0) 971 // Rescan is occuring asynchronously, so there's probably no point in 972 // running checkPendingTxs. 973 return nil 974 } 975 976 // Peers returns a list of peers that the wallet is connected to. 977 func (btc *ExchangeWalletSPV) Peers() ([]*asset.WalletPeer, error) { 978 return btc.spvNode.peers() 979 } 980 981 // AddPeer connects the wallet to a new peer. The peer's address will be 982 // persisted and connected to each time the wallet is started up. 983 func (btc *ExchangeWalletSPV) AddPeer(addr string) error { 984 return btc.spvNode.addPeer(addr) 985 } 986 987 // RemovePeer will remove a peer that was added by AddPeer. This peer may 988 // still be connected to by the wallet if it discovers it on it's own. 989 func (btc *ExchangeWalletSPV) RemovePeer(addr string) error { 990 return btc.spvNode.removePeer(addr) 991 } 992 993 var _ asset.FeeRater = (*ExchangeWalletFullNode)(nil) 994 var _ asset.FeeRater = (*ExchangeWalletNoAuth)(nil) 995 996 // FeeRate satisfies asset.FeeRater. 997 func (btc *baseWallet) FeeRate() uint64 { 998 rate, err := btc.feeRate(1) 999 if err != nil { 1000 btc.log.Tracef("Failed to get fee rate: %v", err) 1001 return 0 1002 } 1003 return rate 1004 } 1005 1006 // LogFilePath returns the path to the neutrino log file. 1007 func (btc *ExchangeWalletSPV) LogFilePath() string { 1008 return btc.spvNode.logFilePath() 1009 } 1010 1011 // WithdrawTx generates a transaction that withdraws all funds to the specified 1012 // address. 1013 func (btc *ExchangeWalletSPV) WithdrawTx(ctx context.Context, walletPW []byte, addr btcutil.Address) (_ *wire.MsgTx, err error) { 1014 btc.ctx = ctx 1015 spvw := btc.node.(*spvWallet) 1016 spvw.cl, err = spvw.wallet.Start() 1017 if err != nil { 1018 return nil, fmt.Errorf("error starting wallet") 1019 } 1020 1021 defer spvw.wallet.Stop() 1022 1023 if err := spvw.Unlock(walletPW); err != nil { 1024 return nil, fmt.Errorf("error unlocking wallet: %w", err) 1025 } 1026 1027 feeRate := btc.FeeRate() 1028 if feeRate == 0 { 1029 return nil, errors.New("no fee rate") 1030 } 1031 utxos, _, _, err := btc.cm.SpendableUTXOs(0) 1032 if err != nil { 1033 return nil, err 1034 } 1035 var inputsSize uint64 1036 coins := make(asset.Coins, 0, len(utxos)) 1037 for _, utxo := range utxos { 1038 op := NewOutput(utxo.TxHash, utxo.Vout, utxo.Amount) 1039 coins = append(coins, op) 1040 inputsSize += uint64(utxo.Input.VBytes()) 1041 } 1042 1043 var baseSize uint64 = dexbtc.MinimumTxOverhead 1044 if btc.segwit { 1045 baseSize += dexbtc.P2WPKHOutputSize * 2 1046 } else { 1047 baseSize += dexbtc.P2PKHOutputSize * 2 1048 } 1049 1050 fundedTx, totalIn, _, err := btc.fundedTx(coins) 1051 if err != nil { 1052 return nil, fmt.Errorf("error adding inputs to transaction: %w", err) 1053 } 1054 1055 fees := feeRate * (inputsSize + baseSize) 1056 toSend := totalIn - fees 1057 1058 signedTx, _, _, err := btc.signTxAndAddChange(fundedTx, addr, toSend, 0, feeRate) 1059 if err != nil { 1060 return nil, err 1061 } 1062 1063 return signedTx, nil 1064 } 1065 1066 func parseChainParams(net dex.Network) (*chaincfg.Params, error) { 1067 switch net { 1068 case dex.Mainnet: 1069 return &chaincfg.MainNetParams, nil 1070 case dex.Testnet: 1071 return &chaincfg.TestNet3Params, nil 1072 case dex.Regtest: 1073 return &chaincfg.RegressionNetParams, nil 1074 } 1075 return nil, fmt.Errorf("unknown network ID %v", net) 1076 } 1077 1078 // NewWallet is the exported constructor by which the DEX will import the 1079 // exchange wallet. 1080 func NewWallet(cfg *asset.WalletConfig, logger dex.Logger, net dex.Network) (asset.Wallet, error) { 1081 params, err := parseChainParams(net) 1082 if err != nil { 1083 return nil, err 1084 } 1085 1086 cloneCFG := &BTCCloneCFG{ 1087 WalletCFG: cfg, 1088 MinNetworkVersion: minNetworkVersion, 1089 WalletInfo: WalletInfo, 1090 Symbol: "btc", 1091 Logger: logger, 1092 Network: net, 1093 ChainParams: params, 1094 Ports: dexbtc.RPCPorts, 1095 DefaultFallbackFee: defaultFee, 1096 DefaultFeeRateLimit: defaultFeeRateLimit, 1097 Segwit: true, 1098 // FeeEstimator must default to rpcFeeRate if not set, but set a 1099 // specific external estimator: 1100 ExternalFeeEstimator: externalFeeRate, 1101 AssetID: BipID, 1102 } 1103 1104 switch cfg.Type { 1105 case walletTypeSPV: 1106 return OpenSPVWallet(cloneCFG, openSPVWallet) 1107 case walletTypeRPC, walletTypeLegacy: 1108 rpcWallet, err := BTCCloneWallet(cloneCFG) 1109 if err != nil { 1110 return nil, err 1111 } 1112 return &ExchangeWalletAccelerator{rpcWallet}, nil 1113 case walletTypeElectrum: 1114 cloneCFG.Ports = dexbtc.NetPorts{} // no default ports 1115 ver, err := dex.SemverFromString(needElectrumVersion) 1116 if err != nil { 1117 return nil, err 1118 } 1119 cloneCFG.MinElectrumVersion = *ver 1120 return ElectrumWallet(cloneCFG) 1121 default: 1122 makeCustomWallet, ok := customSPVWalletConstructors[cfg.Type] 1123 if !ok { 1124 return nil, fmt.Errorf("unknown wallet type %q", cfg.Type) 1125 } 1126 1127 // Create custom wallet first and return early if we encounter any 1128 // error. 1129 btcWallet, err := makeCustomWallet(cfg.Settings, cloneCFG.ChainParams) 1130 if err != nil { 1131 return nil, fmt.Errorf("btc custom wallet setup error: %v", err) 1132 } 1133 1134 walletConstructor := func(_ string, _ *WalletConfig, _ *chaincfg.Params, _ dex.Logger) BTCWallet { 1135 return btcWallet 1136 } 1137 return OpenSPVWallet(cloneCFG, walletConstructor) 1138 } 1139 } 1140 1141 // BTCCloneWallet creates a wallet backend for a set of network parameters and 1142 // default network ports. A BTC clone can use this method, possibly in 1143 // conjunction with ReadCloneParams, to create a ExchangeWallet for other assets 1144 // with minimal coding. 1145 func BTCCloneWallet(cfg *BTCCloneCFG) (*ExchangeWalletFullNode, error) { 1146 iw, err := btcCloneWallet(cfg) 1147 if err != nil { 1148 return nil, err 1149 } 1150 return &ExchangeWalletFullNode{iw, &authAddOn{iw.node}}, nil 1151 } 1152 1153 // BTCCloneWalletNoAuth is like BTCCloneWallet but the wallet created does not 1154 // implement asset.Authenticator. 1155 func BTCCloneWalletNoAuth(cfg *BTCCloneCFG) (*ExchangeWalletNoAuth, error) { 1156 iw, err := btcCloneWallet(cfg) 1157 if err != nil { 1158 return nil, err 1159 } 1160 return &ExchangeWalletNoAuth{iw}, nil 1161 } 1162 1163 // btcCloneWallet creates a wallet backend for a set of network parameters and 1164 // default network ports. 1165 func btcCloneWallet(cfg *BTCCloneCFG) (*intermediaryWallet, error) { 1166 clientCfg, client, err := parseRPCWalletConfig(cfg.WalletCFG.Settings, cfg.Symbol, cfg.Network, cfg.Ports, cfg.SingularWallet) 1167 if err != nil { 1168 return nil, err 1169 } 1170 1171 iw, err := newRPCWallet(client, cfg, clientCfg) 1172 if err != nil { 1173 return nil, fmt.Errorf("error creating %s exchange wallet: %v", cfg.Symbol, 1174 err) 1175 } 1176 1177 return iw, nil 1178 } 1179 1180 // newRPCWallet creates the ExchangeWallet and starts the block monitor. 1181 func newRPCWallet(requester RawRequester, cfg *BTCCloneCFG, parsedCfg *RPCWalletConfig) (*intermediaryWallet, error) { 1182 btc, err := newUnconnectedWallet(cfg, &parsedCfg.WalletConfig) 1183 if err != nil { 1184 return nil, err 1185 } 1186 1187 blockDeserializer := cfg.BlockDeserializer 1188 if blockDeserializer == nil { 1189 blockDeserializer = deserializeBlock 1190 } 1191 1192 core := &rpcCore{ 1193 rpcConfig: &parsedCfg.RPCConfig, 1194 cloneParams: cfg, 1195 segwit: cfg.Segwit, 1196 decodeAddr: btc.decodeAddr, 1197 stringAddr: btc.stringAddr, 1198 deserializeBlock: blockDeserializer, 1199 legacyRawSends: cfg.LegacyRawFeeLimit, 1200 minNetworkVersion: cfg.MinNetworkVersion, 1201 log: cfg.Logger.SubLogger("RPC"), 1202 chainParams: cfg.ChainParams, 1203 omitAddressType: cfg.OmitAddressType, 1204 legacySignTx: cfg.LegacySignTxRPC, 1205 booleanGetBlock: cfg.BooleanGetBlockRPC, 1206 unlockSpends: cfg.UnlockSpends, 1207 1208 deserializeTx: btc.deserializeTx, 1209 serializeTx: btc.serializeTx, 1210 hashTx: btc.hashTx, 1211 numericGetRawTxRPC: cfg.NumericGetRawRPC, 1212 manualMedianTime: cfg.ManualMedianTime, 1213 1214 legacyValidateAddressRPC: cfg.LegacyValidateAddressRPC, 1215 omitRPCOptionsArg: cfg.OmitRPCOptionsArg, 1216 privKeyFunc: cfg.PrivKeyFunc, 1217 } 1218 core.requesterV.Store(requester) 1219 node := newRPCClient(core) 1220 btc.setNode(node) 1221 w := &intermediaryWallet{ 1222 baseWallet: btc, 1223 txFeeEstimator: node, 1224 tipRedeemer: node, 1225 } 1226 1227 w.prepareRedemptionFinder() 1228 return w, nil 1229 } 1230 1231 func decodeAddress(addr string, params *chaincfg.Params) (btcutil.Address, error) { 1232 a, err := btcutil.DecodeAddress(addr, params) 1233 if err != nil { 1234 return nil, err 1235 } 1236 if !a.IsForNet(params) { 1237 return nil, errors.New("wrong network") 1238 } 1239 return a, nil 1240 } 1241 1242 func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWallet, error) { 1243 // Make sure we can use the specified wallet directory. 1244 walletDir := filepath.Join(cfg.WalletCFG.DataDir, cfg.ChainParams.Name) 1245 if err := os.MkdirAll(walletDir, 0744); err != nil { 1246 return nil, fmt.Errorf("error creating wallet directory: %w", err) 1247 } 1248 1249 baseCfg, err := readBaseWalletConfig(walletCfg) 1250 if err != nil { 1251 return nil, err 1252 } 1253 1254 addrDecoder := decodeAddress 1255 if cfg.AddressDecoder != nil { 1256 addrDecoder = cfg.AddressDecoder 1257 } 1258 1259 nonSegwitSigner := rawTxInSig 1260 if cfg.NonSegwitSigner != nil { 1261 nonSegwitSigner = cfg.NonSegwitSigner 1262 } 1263 1264 initTxSize := uint64(cfg.InitTxSize) 1265 if initTxSize == 0 { 1266 if cfg.Segwit { 1267 initTxSize = dexbtc.InitTxSizeSegwit 1268 } else { 1269 initTxSize = dexbtc.InitTxSize 1270 } 1271 } 1272 1273 initTxSizeBase := uint64(cfg.InitTxSizeBase) 1274 if initTxSizeBase == 0 { 1275 if cfg.Segwit { 1276 initTxSizeBase = dexbtc.InitTxSizeBaseSegwit 1277 } else { 1278 initTxSizeBase = dexbtc.InitTxSizeBase 1279 } 1280 } 1281 1282 txDeserializer := cfg.TxDeserializer 1283 if txDeserializer == nil { 1284 txDeserializer = msgTxFromBytes 1285 } 1286 1287 txSerializer := cfg.TxSerializer 1288 if txSerializer == nil { 1289 txSerializer = serializeMsgTx 1290 } 1291 1292 txSizeCalculator := cfg.TxSizeCalculator 1293 if txSizeCalculator == nil { 1294 txSizeCalculator = dexbtc.MsgTxVBytes 1295 } 1296 1297 txHasher := cfg.TxHasher 1298 if txHasher == nil { 1299 txHasher = hashTx 1300 } 1301 1302 addrStringer := cfg.AddressStringer 1303 if addrStringer == nil { 1304 addrStringer = stringifyAddress 1305 } 1306 1307 txVersion := cfg.TxVersion 1308 if txVersion == nil { 1309 txVersion = func() int32 { return wire.TxVersion } 1310 } 1311 1312 addressRecyler, err := NewAddressRecycler(filepath.Join(walletDir, "recycled-addrs.txt"), cfg.Logger) 1313 if err != nil { 1314 return nil, err 1315 } 1316 1317 var feeCache *feeRateCache 1318 if cfg.ExternalFeeEstimator != nil { 1319 feeCache = &feeRateCache{ 1320 f: cfg.ExternalFeeEstimator, 1321 shelfLife: cfg.ExternalFeeShelfLife, 1322 } 1323 } 1324 1325 w := &baseWallet{ 1326 symbol: cfg.Symbol, 1327 chainParams: cfg.ChainParams, 1328 cloneParams: cfg, 1329 log: cfg.Logger, 1330 emit: cfg.WalletCFG.Emit, 1331 peersChange: cfg.WalletCFG.PeersChange, 1332 minNetworkVersion: cfg.MinNetworkVersion, 1333 dustLimit: cfg.ConstantDustLimit, 1334 useLegacyBalance: cfg.LegacyBalance, 1335 balanceFunc: cfg.BalanceFunc, 1336 segwit: cfg.Segwit, 1337 initTxSize: initTxSize, 1338 initTxSizeBase: initTxSizeBase, 1339 signNonSegwit: nonSegwitSigner, 1340 localFeeRate: cfg.FeeEstimator, 1341 feeCache: feeCache, 1342 decodeAddr: addrDecoder, 1343 stringAddr: addrStringer, 1344 walletInfo: cfg.WalletInfo, 1345 deserializeTx: txDeserializer, 1346 serializeTx: txSerializer, 1347 hashTx: txHasher, 1348 calcTxSize: txSizeCalculator, 1349 txVersion: txVersion, 1350 Network: cfg.Network, 1351 pendingTxs: make(map[chainhash.Hash]ExtendedWalletTx), 1352 walletDir: walletDir, 1353 ar: addressRecyler, 1354 } 1355 w.cfgV.Store(baseCfg) 1356 1357 // Default to the BTC RPC estimator (see LTC). Consumers can use 1358 // noLocalFeeRate or a similar dummy function to power feeRate() requests 1359 // with only an external fee rate source available. Otherwise, all method 1360 // calls must provide a rate or accept the configured fallback. 1361 if w.localFeeRate == nil { 1362 w.localFeeRate = rpcFeeRate 1363 } 1364 1365 return w, nil 1366 } 1367 1368 // noLocalFeeRate is a dummy function for BTCCloneCFG.FeeEstimator for a wallet 1369 // instance that cannot support a local fee rate estimate but has an external 1370 // fee rate source. 1371 func noLocalFeeRate(ctx context.Context, rr RawRequester, u uint64) (uint64, error) { 1372 return 0, errors.New("no local fee rate estimate possible") 1373 } 1374 1375 // OpenSPVWallet opens the previously created native SPV wallet. 1376 func OpenSPVWallet(cfg *BTCCloneCFG, walletConstructor BTCWalletConstructor) (*ExchangeWalletSPV, error) { 1377 walletCfg := new(WalletConfig) 1378 err := config.Unmapify(cfg.WalletCFG.Settings, walletCfg) 1379 if err != nil { 1380 return nil, err 1381 } 1382 1383 // SPV wallets without a FeeEstimator will default to any enabled external 1384 // fee estimator. 1385 if cfg.FeeEstimator == nil { 1386 cfg.FeeEstimator = noLocalFeeRate 1387 } 1388 1389 btc, err := newUnconnectedWallet(cfg, walletCfg) 1390 if err != nil { 1391 return nil, err 1392 } 1393 1394 spvw := &spvWallet{ 1395 chainParams: cfg.ChainParams, 1396 cfg: walletCfg, 1397 dir: filepath.Join(cfg.WalletCFG.DataDir, cfg.ChainParams.Name), 1398 log: cfg.Logger.SubLogger("SPV"), 1399 tipChan: make(chan *BlockVector, 8), 1400 decodeAddr: btc.decodeAddr, 1401 } 1402 1403 spvw.BlockFiltersScanner = NewBlockFiltersScanner(spvw, spvw.log) 1404 spvw.wallet = walletConstructor(spvw.dir, spvw.cfg, spvw.chainParams, spvw.log) 1405 btc.setNode(spvw) 1406 1407 // Set account number for easy reference. 1408 spvw.acctNum = spvw.wallet.AccountInfo().AccountNumber 1409 1410 w := &ExchangeWalletSPV{ 1411 intermediaryWallet: &intermediaryWallet{ 1412 baseWallet: btc, 1413 txFeeEstimator: spvw, 1414 tipRedeemer: spvw, 1415 }, 1416 authAddOn: &authAddOn{spvw}, 1417 spvNode: spvw, 1418 } 1419 w.prepareRedemptionFinder() 1420 return w, nil 1421 } 1422 1423 func (btc *baseWallet) setNode(node Wallet) { 1424 btc.node = node 1425 btc.cm = NewCoinManager( 1426 btc.log, 1427 btc.chainParams, 1428 func(val, lots, maxFeeRate uint64, reportChange bool) EnoughFunc { 1429 return orderEnough(val, lots, maxFeeRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, reportChange) 1430 }, 1431 func() ([]*ListUnspentResult, error) { // list 1432 return node.listUnspent() 1433 }, 1434 func(unlock bool, ops []*Output) error { // lock 1435 return node.lockUnspent(unlock, ops) 1436 }, 1437 func() ([]*RPCOutpoint, error) { // listLocked 1438 return node.listLockUnspent() 1439 }, 1440 func(txHash *chainhash.Hash, vout uint32) (*wire.TxOut, error) { 1441 txRaw, _, err := btc.rawWalletTx(txHash) 1442 if err != nil { 1443 return nil, err 1444 } 1445 msgTx, err := btc.deserializeTx(txRaw) 1446 if err != nil { 1447 btc.log.Warnf("Invalid transaction %v (%x): %v", txHash, txRaw, err) 1448 return nil, nil 1449 } 1450 if vout >= uint32(len(msgTx.TxOut)) { 1451 btc.log.Warnf("Invalid vout %d for %v", vout, txHash) 1452 return nil, nil 1453 } 1454 return msgTx.TxOut[vout], nil 1455 }, 1456 func(addr btcutil.Address) (string, error) { 1457 return btc.stringAddr(addr, btc.chainParams) 1458 }, 1459 ) 1460 } 1461 1462 func (btc *intermediaryWallet) prepareRedemptionFinder() { 1463 btc.rf = NewRedemptionFinder( 1464 btc.log, 1465 btc.tipRedeemer.getWalletTransaction, 1466 btc.tipRedeemer.getBlockHeight, 1467 btc.tipRedeemer.getBlock, 1468 btc.tipRedeemer.getBlockHeader, 1469 btc.hashTx, 1470 btc.deserializeTx, 1471 btc.tipRedeemer.getBestBlockHeight, 1472 btc.tipRedeemer.searchBlockForRedemptions, 1473 btc.tipRedeemer.getBlockHash, 1474 btc.tipRedeemer.findRedemptionsInMempool, 1475 ) 1476 } 1477 1478 // Info returns basic information about the wallet and asset. 1479 func (btc *baseWallet) Info() *asset.WalletInfo { 1480 return btc.walletInfo 1481 } 1482 1483 func (btc *baseWallet) txHistoryDBPath(walletID string) string { 1484 return filepath.Join(btc.walletDir, fmt.Sprintf("txhistorydb-%s", walletID)) 1485 } 1486 1487 // findExistingAddressBasedTxHistoryDB finds the path of a tx history db that 1488 // was created using an address controlled by the wallet. This should only be 1489 // used for wallets that are unable to generate a fingerprint. 1490 func (btc *baseWallet) findExistingAddressBasedTxHistoryDB() (string, error) { 1491 dir, err := os.Open(btc.walletDir) 1492 if err != nil { 1493 return "", fmt.Errorf("error opening wallet directory: %w", err) 1494 } 1495 defer dir.Close() 1496 1497 entries, err := dir.Readdir(0) 1498 if err != nil { 1499 return "", fmt.Errorf("error reading wallet directory: %w", err) 1500 } 1501 1502 pattern := regexp.MustCompile(`^txhistorydb-(.+)$`) 1503 1504 for _, entry := range entries { 1505 if !entry.IsDir() { 1506 continue 1507 } 1508 1509 match := pattern.FindStringSubmatch(entry.Name()) 1510 if match == nil { 1511 continue 1512 } 1513 1514 address := match[1] 1515 owns, err := btc.OwnsDepositAddress(address) 1516 if err != nil { 1517 continue 1518 } 1519 if owns { 1520 return filepath.Join(btc.walletDir, entry.Name()), nil 1521 } 1522 } 1523 1524 return "", nil 1525 } 1526 1527 func (btc *baseWallet) startTxHistoryDB(ctx context.Context) (*sync.WaitGroup, error) { 1528 var dbPath string 1529 fingerPrint, err := btc.node.fingerprint() 1530 if err == nil && fingerPrint != "" { 1531 dbPath = btc.txHistoryDBPath(fingerPrint) 1532 } 1533 1534 if dbPath == "" { 1535 addressPath, err := btc.findExistingAddressBasedTxHistoryDB() 1536 if err != nil { 1537 return nil, err 1538 } 1539 if addressPath != "" { 1540 dbPath = addressPath 1541 } 1542 } 1543 1544 if dbPath == "" { 1545 depositAddr, err := btc.DepositAddress() 1546 if err != nil { 1547 return nil, fmt.Errorf("error getting deposit address: %w", err) 1548 } 1549 dbPath = btc.txHistoryDBPath(depositAddr) 1550 } 1551 1552 btc.log.Debugf("Using tx history db at %s", dbPath) 1553 1554 db := NewBadgerTxDB(dbPath, btc.log) 1555 btc.txHistoryDB.Store(db) 1556 1557 wg, err := db.Connect(ctx) 1558 if err != nil { 1559 return nil, err 1560 } 1561 1562 pendingTxs, err := db.GetPendingTxs() 1563 if err != nil { 1564 return nil, fmt.Errorf("failed to load unconfirmed txs: %v", err) 1565 } 1566 1567 btc.pendingTxsMtx.Lock() 1568 for _, tx := range pendingTxs { 1569 txHash, err := chainhash.NewHashFromStr(tx.ID) 1570 if err != nil { 1571 btc.log.Errorf("Invalid txid %v from tx history db: %v", tx.ID, err) 1572 continue 1573 } 1574 btc.pendingTxs[*txHash] = *tx 1575 } 1576 btc.pendingTxsMtx.Unlock() 1577 1578 lastQuery, err := db.GetLastReceiveTxQuery() 1579 if errors.Is(err, ErrNeverQueried) { 1580 lastQuery = 0 1581 } else if err != nil { 1582 return nil, fmt.Errorf("failed to load last query time: %v", err) 1583 } 1584 1585 btc.receiveTxLastQuery.Store(lastQuery) 1586 1587 return wg, nil 1588 } 1589 1590 // connect is shared between Wallet implementations that may have different 1591 // monitoring goroutines or other configuration set after connect. For example 1592 // an asset.Wallet implementation that embeds baseWallet may override Connect to 1593 // perform monitoring differently, but still use this connect method to start up 1594 // the btc.Wallet (the btc.node field). 1595 func (btc *baseWallet) connect(ctx context.Context) (*sync.WaitGroup, error) { 1596 btc.ctx = ctx 1597 var wg sync.WaitGroup 1598 if err := btc.node.connect(ctx, &wg); err != nil { 1599 return nil, err 1600 } 1601 1602 // Initialize the best block. 1603 bestBlockHdr, err := btc.node.getBestBlockHeader() 1604 if err != nil { 1605 return nil, fmt.Errorf("error initializing best block for %s: %w", btc.symbol, err) 1606 } 1607 bestBlockHash, err := chainhash.NewHashFromStr(bestBlockHdr.Hash) 1608 if err != nil { 1609 return nil, fmt.Errorf("invalid best block hash from %s node: %v", btc.symbol, err) 1610 } 1611 // Check for method unknown error for feeRate method. 1612 _, err = btc.feeRate(1) 1613 if isMethodNotFoundErr(err) { 1614 return nil, fmt.Errorf("fee estimation method not found. Are you configured for the correct RPC?") 1615 } 1616 1617 bestBlock := &BlockVector{bestBlockHdr.Height, *bestBlockHash} 1618 btc.log.Infof("Connected wallet with current best block %v (%d)", bestBlock.Hash, bestBlock.Height) 1619 btc.tipMtx.Lock() 1620 btc.currentTip = bestBlock 1621 btc.tipMtx.Unlock() 1622 atomic.StoreInt64(&btc.tipAtConnect, btc.currentTip.Height) 1623 1624 wg.Add(1) 1625 go func() { 1626 defer wg.Done() 1627 <-ctx.Done() 1628 btc.ar.WriteRecycledAddrsToFile() 1629 }() 1630 1631 return &wg, nil 1632 } 1633 1634 // Connect connects the wallet to the btc.Wallet backend and starts monitoring 1635 // blocks and peers. Satisfies the dex.Connector interface. 1636 func (btc *intermediaryWallet) Connect(ctx context.Context) (*sync.WaitGroup, error) { 1637 wg, err := btc.connect(ctx) 1638 if err != nil { 1639 return nil, err 1640 } 1641 1642 dbWG, err := btc.startTxHistoryDB(ctx) 1643 if err != nil { 1644 return nil, err 1645 } 1646 1647 wg.Add(1) 1648 go func() { 1649 defer wg.Done() 1650 dbWG.Wait() 1651 }() 1652 1653 wg.Add(1) 1654 go func() { 1655 defer wg.Done() 1656 btc.watchBlocks(ctx) 1657 btc.rf.CancelRedemptionSearches() 1658 }() 1659 1660 wg.Add(1) 1661 go func() { 1662 defer wg.Done() 1663 btc.monitorPeers(ctx) 1664 }() 1665 1666 wg.Add(1) 1667 func() { 1668 defer wg.Done() 1669 btc.tipMtx.RLock() 1670 tip := btc.currentTip 1671 btc.tipMtx.RUnlock() 1672 go btc.syncTxHistory(uint64(tip.Height)) 1673 }() 1674 1675 return wg, nil 1676 } 1677 1678 // Reconfigure attempts to reconfigure the wallet. 1679 func (btc *baseWallet) Reconfigure(ctx context.Context, cfg *asset.WalletConfig, currentAddress string) (restart bool, err error) { 1680 // See what the node says. 1681 restart, err = btc.node.reconfigure(cfg, currentAddress) 1682 if err != nil { 1683 return false, err 1684 } 1685 1686 parsedCfg := new(RPCWalletConfig) 1687 if err = config.Unmapify(cfg.Settings, parsedCfg); err != nil { 1688 return false, err 1689 } 1690 walletCfg := &parsedCfg.WalletConfig 1691 1692 // Make sure the configuration parameters are valid. If restart is required, 1693 // this validates the configuration parameters, preventing an ugly surprise 1694 // when the caller attempts to open the wallet. If no restart is required, 1695 // we'll swap out the configuration parameters right away. 1696 newCfg, err := readBaseWalletConfig(walletCfg) 1697 if err != nil { 1698 return false, err 1699 } 1700 btc.cfgV.Store(newCfg) // probably won't matter if restart/reinit required 1701 1702 return restart, nil 1703 } 1704 1705 // IsDust checks if the tx output's value is dust. If the dustLimit is set, it 1706 // is compared against that, otherwise the formula in dexbtc.IsDust is used. 1707 func (btc *baseWallet) IsDust(txOut *wire.TxOut, minRelayTxFee uint64) bool { 1708 if btc.dustLimit > 0 { 1709 return txOut.Value < int64(btc.dustLimit) 1710 } 1711 return dexbtc.IsDust(txOut, minRelayTxFee) 1712 } 1713 1714 // GetBlockchainInfoResult models the data returned from the getblockchaininfo 1715 // command. 1716 type GetBlockchainInfoResult struct { 1717 Chain string `json:"chain"` 1718 Blocks int64 `json:"blocks"` 1719 Headers int64 `json:"headers"` 1720 BestBlockHash string `json:"bestblockhash"` 1721 // InitialBlockDownload will be true if the node is still in the initial 1722 // block download mode. 1723 InitialBlockDownload *bool `json:"initialblockdownload"` 1724 // InitialBlockDownloadComplete will be true if this node has completed its 1725 // initial block download and is expected to be synced to the network. 1726 // Zcash uses this terminology instead of initialblockdownload. 1727 InitialBlockDownloadComplete *bool `json:"initial_block_download_complete"` 1728 } 1729 1730 func (r *GetBlockchainInfoResult) Syncing() bool { 1731 if r.InitialBlockDownloadComplete != nil && *r.InitialBlockDownloadComplete { 1732 return false 1733 } 1734 if r.InitialBlockDownload != nil && *r.InitialBlockDownload { 1735 return true 1736 } 1737 return r.Headers-r.Blocks > 1 1738 } 1739 1740 // SyncStatus is information about the blockchain sync status. 1741 func (btc *baseWallet) SyncStatus() (*asset.SyncStatus, error) { 1742 ss, err := btc.node.syncStatus() 1743 if err != nil { 1744 return nil, err 1745 } 1746 ss.StartingBlocks = uint64(atomic.LoadInt64(&btc.tipAtConnect)) 1747 if ss.Synced { 1748 numPeers, err := btc.node.peerCount() 1749 if err != nil { 1750 return nil, err 1751 } 1752 ss.Synced = numPeers > 0 1753 } 1754 return ss, nil 1755 } 1756 1757 // OwnsDepositAddress indicates if the provided address can be used 1758 // to deposit funds into the wallet. 1759 func (btc *baseWallet) OwnsDepositAddress(address string) (bool, error) { 1760 addr, err := btc.decodeAddr(address, btc.chainParams) // maybe move into the ownsAddress impls 1761 if err != nil { 1762 return false, err 1763 } 1764 return btc.node.ownsAddress(addr) 1765 } 1766 1767 func (btc *baseWallet) balance() (*asset.Balance, error) { 1768 if btc.balanceFunc != nil { 1769 locked, err := btc.lockedSats() 1770 if err != nil { 1771 return nil, fmt.Errorf("(legacy) lockedSats error: %w", err) 1772 } 1773 return btc.balanceFunc(btc.ctx, locked) 1774 } 1775 if btc.useLegacyBalance { 1776 return btc.legacyBalance() 1777 } 1778 balances, err := btc.node.balances() 1779 if err != nil { 1780 return nil, err 1781 } 1782 locked, err := btc.lockedSats() 1783 if err != nil { 1784 return nil, err 1785 } 1786 1787 return &asset.Balance{ 1788 Available: toSatoshi(balances.Mine.Trusted) - locked, 1789 Immature: toSatoshi(balances.Mine.Immature + balances.Mine.Untrusted), 1790 Locked: locked, 1791 Other: make(map[asset.BalanceCategory]asset.CustomBalance), 1792 }, nil 1793 } 1794 1795 // Balance should return the total available funds in the wallet. 1796 func (btc *baseWallet) Balance() (*asset.Balance, error) { 1797 bal, err := btc.balance() 1798 if err != nil { 1799 return nil, err 1800 } 1801 1802 reserves := btc.bondReserves.Load() 1803 if reserves > bal.Available { 1804 btc.log.Warnf("Available balance is below configured reserves: %f < %f", 1805 toBTC(bal.Available), toBTC(reserves)) 1806 bal.ReservesDeficit = reserves - bal.Available 1807 reserves = bal.Available 1808 } 1809 1810 bal.BondReserves = reserves 1811 bal.Available -= reserves 1812 bal.Locked += reserves 1813 1814 return bal, nil 1815 } 1816 1817 func bondsFeeBuffer(segwit bool, highFeeRate uint64) uint64 { 1818 const inputCount uint64 = 8 // plan for lots of inputs 1819 var largeBondTxSize uint64 1820 if segwit { 1821 largeBondTxSize = dexbtc.MinimumTxOverhead + dexbtc.P2WSHOutputSize + 1 + dexbtc.BondPushDataSize + 1822 dexbtc.P2WPKHOutputSize + inputCount*dexbtc.RedeemP2WPKHInputSize 1823 } else { 1824 largeBondTxSize = dexbtc.MinimumTxOverhead + dexbtc.P2SHOutputSize + 1 + dexbtc.BondPushDataSize + 1825 dexbtc.P2PKHOutputSize + inputCount*dexbtc.RedeemP2PKHInputSize 1826 } 1827 1828 // Normally we can plan on just 2 parallel "tracks" (single bond overlap 1829 // when bonds are expired and waiting to refund) but that may increase 1830 // temporarily if target tier is adjusted up. 1831 const parallelTracks uint64 = 4 1832 return parallelTracks * largeBondTxSize * highFeeRate 1833 } 1834 1835 // legacyBalance is used for clones that are < node version 0.18 and so don't 1836 // have 'getbalances'. 1837 func (btc *baseWallet) legacyBalance() (*asset.Balance, error) { 1838 cl, ok := btc.node.(*rpcClient) 1839 if !ok { 1840 return nil, fmt.Errorf("legacyBalance unimplemented for spv clients") 1841 } 1842 1843 locked, err := btc.lockedSats() 1844 if err != nil { 1845 return nil, fmt.Errorf("(legacy) lockedSats error: %w", err) 1846 } 1847 1848 walletInfo, err := cl.GetWalletInfo() 1849 if err != nil { 1850 return nil, fmt.Errorf("(legacy) GetWalletInfo error: %w", err) 1851 } 1852 1853 return &asset.Balance{ 1854 Available: toSatoshi(walletInfo.Balance+walletInfo.UnconfirmedBalance) - locked, 1855 Immature: toSatoshi(walletInfo.ImmatureBalance), 1856 Locked: locked, 1857 Other: make(map[asset.BalanceCategory]asset.CustomBalance), 1858 }, nil 1859 } 1860 1861 // feeRate returns the current optimal fee rate in sat / byte using the 1862 // estimatesmartfee RPC or an external API if configured and enabled. 1863 func (btc *baseWallet) feeRate(confTarget uint64) (feeRate uint64, err error) { 1864 allowExternalFeeRate := btc.apiFeeFallback() 1865 // Because of the problems Bitcoin's unstable estimatesmartfee has caused, 1866 // we won't use it. 1867 if btc.symbol != "btc" || !allowExternalFeeRate { 1868 feeRate, err := btc.localFeeRate(btc.ctx, btc.node, confTarget) // e.g. rpcFeeRate 1869 if err == nil { 1870 return feeRate, nil 1871 } else if !allowExternalFeeRate { 1872 return 0, fmt.Errorf("error getting local rate and external rates are disabled: %w", err) 1873 } 1874 } 1875 1876 if btc.feeCache == nil { 1877 return 0, fmt.Errorf("external fee rate fetcher not configured") 1878 } 1879 1880 // External estimate fallback. Error if it exceeds our limit, and the caller 1881 // may use btc.fallbackFeeRate(), as in targetFeeRateWithFallback. 1882 feeRate, err = btc.feeCache.rate(btc.ctx, btc.Network) // e.g. externalFeeRate 1883 if err != nil { 1884 btc.log.Meter("feeRate.rate.fail", time.Hour).Errorf("Failed to get fee rate from external API: %v", err) 1885 return 0, nil 1886 } 1887 if feeRate <= 0 || feeRate > btc.feeRateLimit() { // but fetcher shouldn't return <= 0 without error 1888 return 0, fmt.Errorf("external fee rate %v exceeds configured limit", feeRate) 1889 } 1890 btc.log.Tracef("Retrieved fee rate from external API: %v", feeRate) 1891 return feeRate, nil 1892 } 1893 1894 func rpcFeeRate(ctx context.Context, rr RawRequester, confTarget uint64) (uint64, error) { 1895 feeResult, err := estimateSmartFee(ctx, rr, confTarget, &btcjson.EstimateModeEconomical) 1896 if err != nil { 1897 return 0, err 1898 } 1899 1900 if len(feeResult.Errors) > 0 { 1901 return 0, errors.New(strings.Join(feeResult.Errors, "; ")) 1902 } 1903 if feeResult.FeeRate == nil { 1904 return 0, fmt.Errorf("no fee rate available") 1905 } 1906 satPerKB, err := btcutil.NewAmount(*feeResult.FeeRate) // satPerKB is 0 when err != nil 1907 if err != nil { 1908 return 0, err 1909 } 1910 if satPerKB <= 0 { 1911 return 0, errors.New("zero or negative fee rate") 1912 } 1913 return uint64(dex.IntDivUp(int64(satPerKB), 1000)), nil 1914 } 1915 1916 // externalFeeRate gets the fee rate from the external API and returns it 1917 // in sats/vByte. 1918 func externalFeeRate(ctx context.Context, net dex.Network) (uint64, error) { 1919 // https://mempool.space/docs/api 1920 var uri string 1921 if net == dex.Testnet { 1922 uri = "https://mempool.space/testnet/api/v1/fees/recommended" 1923 } else { 1924 uri = "https://mempool.space/api/v1/fees/recommended" 1925 } 1926 ctx, cancel := context.WithTimeout(ctx, 4*time.Second) 1927 defer cancel() 1928 var resp struct { 1929 Fastest uint64 `json:"fastestFee"` 1930 HalfHour uint64 `json:"halfHourFee"` 1931 Hour uint64 `json:"hourFee"` 1932 Economy uint64 `json:"economyFee"` 1933 Minimum uint64 `json:"minimumFee"` 1934 } 1935 if err := dexnet.Get(ctx, uri, &resp, dexnet.WithSizeLimit(1<<14)); err != nil { 1936 return 0, err 1937 } 1938 if resp.Fastest == 0 { 1939 return 0, errors.New("no fee rate found") 1940 } 1941 return resp.Fastest, nil 1942 } 1943 1944 type amount uint64 1945 1946 func (a amount) String() string { 1947 return strconv.FormatFloat(btcutil.Amount(a).ToBTC(), 'f', -1, 64) // dec, but no trailing zeros 1948 } 1949 1950 // targetFeeRateWithFallback attempts to get a fresh fee rate for the target 1951 // number of confirmations, but falls back to the suggestion or fallbackFeeRate 1952 // via feeRateWithFallback. 1953 func (btc *baseWallet) targetFeeRateWithFallback(confTarget, feeSuggestion uint64) uint64 { 1954 feeRate, err := btc.feeRate(confTarget) 1955 if err == nil && feeRate > 0 { 1956 btc.log.Tracef("Obtained estimate for %d-conf fee rate, %d", confTarget, feeRate) 1957 return feeRate 1958 } 1959 btc.log.Tracef("no %d-conf feeRate available: %v", confTarget, err) 1960 return btc.feeRateWithFallback(feeSuggestion) 1961 } 1962 1963 // feeRateWithFallback filters the suggested fee rate by ensuring it is within 1964 // limits. If not, the configured fallbackFeeRate is returned and a warning 1965 // logged. 1966 func (btc *baseWallet) feeRateWithFallback(feeSuggestion uint64) uint64 { 1967 if feeSuggestion > 0 && feeSuggestion < btc.feeRateLimit() { 1968 btc.log.Tracef("feeRateWithFallback using caller's suggestion for fee rate, %d.", 1969 feeSuggestion) 1970 return feeSuggestion 1971 } 1972 btc.log.Warnf("Unable to get optimal fee rate, using fallback of %d", btc.fallbackFeeRate) 1973 return btc.fallbackFeeRate() 1974 } 1975 1976 // MaxOrder generates information about the maximum order size and associated 1977 // fees that the wallet can support for the given DEX configuration. The fees are an 1978 // estimate based on current network conditions, and will be <= the fees 1979 // associated with nfo.MaxFeeRate. For quote assets, the caller will have to 1980 // calculate lotSize based on a rate conversion from the base asset's lot size. 1981 // lotSize must not be zero and will cause a panic if so. 1982 func (btc *baseWallet) MaxOrder(ord *asset.MaxOrderForm) (*asset.SwapEstimate, error) { 1983 _, maxEst, err := btc.maxOrder(ord.LotSize, ord.FeeSuggestion, ord.MaxFeeRate) 1984 return maxEst, err 1985 } 1986 1987 // maxOrder gets the estimate for MaxOrder, and also returns the 1988 // []*CompositeUTXO to be used for further order estimation without additional 1989 // calls to listunspent. 1990 func (btc *baseWallet) maxOrder(lotSize, feeSuggestion, maxFeeRate uint64) (utxos []*CompositeUTXO, est *asset.SwapEstimate, err error) { 1991 if lotSize == 0 { 1992 return nil, nil, errors.New("cannot divide by lotSize zero") 1993 } 1994 1995 utxos, _, avail, err := btc.cm.SpendableUTXOs(0) 1996 if err != nil { 1997 return nil, nil, fmt.Errorf("error parsing unspent outputs: %w", err) 1998 } 1999 2000 // Start by attempting max lots with a basic fee. 2001 basicFee := btc.initTxSize * maxFeeRate 2002 maxLotsInt := int(avail / (lotSize + basicFee)) 2003 oneLotTooMany := sort.Search(maxLotsInt+1, func(lots int) bool { 2004 _, _, _, err = btc.estimateSwap(uint64(lots), lotSize, feeSuggestion, maxFeeRate, utxos, true, 1.0) 2005 // The only failure mode of estimateSwap -> btc.fund is when there is 2006 // not enough funds. 2007 return err != nil 2008 }) 2009 2010 maxLots := uint64(oneLotTooMany - 1) 2011 if oneLotTooMany == 0 { 2012 maxLots = 0 2013 } 2014 2015 if maxLots > 0 { 2016 est, _, _, err = btc.estimateSwap(maxLots, lotSize, feeSuggestion, maxFeeRate, utxos, true, 1.0) 2017 return utxos, est, err 2018 } 2019 2020 return utxos, &asset.SwapEstimate{ 2021 FeeReservesPerLot: basicFee, 2022 }, nil 2023 } 2024 2025 // sizeUnit returns the short form of the unit used to measure size, either 2026 // vB if segwit, else B. 2027 func (btc *baseWallet) sizeUnit() string { 2028 if btc.segwit { 2029 return "vB" 2030 } 2031 return "B" 2032 } 2033 2034 // PreSwap get order estimates and order options based on the available funds 2035 // and user-selected options. 2036 func (btc *baseWallet) PreSwap(req *asset.PreSwapForm) (*asset.PreSwap, error) { 2037 // Start with the maxOrder at the default configuration. This gets us the 2038 // utxo set, the network fee rate, and the wallet's maximum order size. The 2039 // utxo set can then be used repeatedly in estimateSwap at virtually zero 2040 // cost since there are no more RPC calls. 2041 utxos, maxEst, err := btc.maxOrder(req.LotSize, req.FeeSuggestion, req.MaxFeeRate) 2042 if err != nil { 2043 return nil, err 2044 } 2045 if maxEst.Lots < req.Lots { 2046 return nil, fmt.Errorf("%d lots available for %d-lot order", maxEst.Lots, req.Lots) 2047 } 2048 2049 // Load the user's selected order-time options. 2050 customCfg := new(swapOptions) 2051 err = config.Unmapify(req.SelectedOptions, customCfg) 2052 if err != nil { 2053 return nil, fmt.Errorf("error parsing selected swap options: %w", err) 2054 } 2055 2056 // Parse the configured split transaction. 2057 split := btc.useSplitTx() 2058 if customCfg.Split != nil { 2059 split = *customCfg.Split 2060 } 2061 2062 // Parse the configured fee bump. 2063 bump, err := customCfg.feeBump() 2064 if err != nil { 2065 return nil, err 2066 } 2067 2068 // Get the estimate using the current configuration. 2069 est, _, _, err := btc.estimateSwap(req.Lots, req.LotSize, req.FeeSuggestion, 2070 req.MaxFeeRate, utxos, split, bump) 2071 if err != nil { 2072 btc.log.Warnf("estimateSwap failure: %v", err) 2073 } 2074 2075 // Always offer the split option, even for non-standing orders since 2076 // immediately spendable change many be desirable regardless. 2077 opts := []*asset.OrderOption{btc.splitOption(req, utxos, bump)} 2078 2079 // Figure out what our maximum available fee bump is, within our 2x hard 2080 // limit. 2081 var maxBump float64 2082 var maxBumpEst *asset.SwapEstimate 2083 for maxBump = 2.0; maxBump > 1.01; maxBump -= 0.1 { 2084 if est == nil { 2085 break 2086 } 2087 tryEst, splitUsed, _, err := btc.estimateSwap(req.Lots, req.LotSize, 2088 req.FeeSuggestion, req.MaxFeeRate, utxos, split, maxBump) 2089 // If the split used wasn't the configured value, this option is not 2090 // available. 2091 if err == nil && split == splitUsed { 2092 maxBumpEst = tryEst 2093 break 2094 } 2095 } 2096 2097 if maxBumpEst != nil { 2098 noBumpEst, _, _, err := btc.estimateSwap(req.Lots, req.LotSize, req.FeeSuggestion, 2099 req.MaxFeeRate, utxos, split, 1.0) 2100 if err != nil { 2101 // shouldn't be possible, since we already succeeded with a higher bump. 2102 return nil, fmt.Errorf("error getting no-bump estimate: %w", err) 2103 } 2104 2105 bumpLabel := "2X" 2106 if maxBump < 2.0 { 2107 bumpLabel = strconv.FormatFloat(maxBump, 'f', 1, 64) + "X" 2108 } 2109 2110 extraFees := maxBumpEst.RealisticWorstCase - noBumpEst.RealisticWorstCase 2111 desc := fmt.Sprintf("Add a fee multiplier up to %.1fx (up to ~%s %s more) for faster settlement when %s network traffic is high.", 2112 maxBump, prettyBTC(extraFees), btc.symbol, btc.walletInfo.Name) 2113 2114 opts = append(opts, &asset.OrderOption{ 2115 ConfigOption: asset.ConfigOption{ 2116 Key: swapFeeBumpKey, 2117 DisplayName: "Faster Swaps", 2118 Description: desc, 2119 DefaultValue: 1.0, 2120 }, 2121 XYRange: &asset.XYRange{ 2122 Start: asset.XYRangePoint{ 2123 Label: "1X", 2124 X: 1.0, 2125 Y: float64(req.FeeSuggestion), 2126 }, 2127 End: asset.XYRangePoint{ 2128 Label: bumpLabel, 2129 X: maxBump, 2130 Y: float64(req.FeeSuggestion) * maxBump, 2131 }, 2132 XUnit: "X", 2133 YUnit: btc.walletInfo.UnitInfo.AtomicUnit + "/" + btc.sizeUnit(), 2134 }, 2135 }) 2136 } 2137 2138 return &asset.PreSwap{ 2139 Estimate: est, // may be nil so we can present options, which in turn affect estimate feasibility 2140 Options: opts, 2141 }, nil 2142 } 2143 2144 // SingleLotSwapRefundFees returns the fees for a swap and refund transaction 2145 // for a single lot. 2146 func (btc *baseWallet) SingleLotSwapRefundFees(_ uint32, feeSuggestion uint64, useSafeTxSize bool) (swapFees uint64, refundFees uint64, err error) { 2147 var numInputs uint64 2148 if useSafeTxSize { 2149 numInputs = 12 2150 } else { 2151 numInputs = 2 2152 } 2153 2154 // TODO: The following is not correct for all BTC clones. e.g. Zcash has 2155 // a different MinimumTxOverhead (29). 2156 2157 var swapTxSize uint64 2158 if btc.segwit { 2159 inputSize := dexbtc.RedeemP2WPKHInputSize + uint64((dexbtc.RedeemP2PKSigScriptSize+2+3)/4) 2160 swapTxSize = dexbtc.MinimumTxOverhead + (numInputs * inputSize) + dexbtc.P2WSHOutputSize + dexbtc.P2WPKHOutputSize 2161 } else { 2162 swapTxSize = dexbtc.MinimumTxOverhead + (numInputs * dexbtc.RedeemP2PKHInputSize) + dexbtc.P2SHOutputSize + dexbtc.P2PKHOutputSize 2163 } 2164 2165 var refundTxSize uint64 2166 if btc.segwit { 2167 witnessVBytes := uint64((dexbtc.RefundSigScriptSize + 2 + 3) / 4) 2168 refundTxSize = dexbtc.MinimumTxOverhead + dexbtc.TxInOverhead + witnessVBytes + dexbtc.P2WPKHOutputSize 2169 } else { 2170 inputSize := uint64(dexbtc.TxInOverhead + dexbtc.RefundSigScriptSize) 2171 refundTxSize = dexbtc.MinimumTxOverhead + inputSize + dexbtc.P2PKHOutputSize 2172 } 2173 2174 return swapTxSize * feeSuggestion, refundTxSize * feeSuggestion, nil 2175 } 2176 2177 // splitOption constructs an *asset.OrderOption with customized text based on the 2178 // difference in fees between the configured and test split condition. 2179 func (btc *baseWallet) splitOption(req *asset.PreSwapForm, utxos []*CompositeUTXO, bump float64) *asset.OrderOption { 2180 opt := &asset.OrderOption{ 2181 ConfigOption: asset.ConfigOption{ 2182 Key: splitKey, 2183 DisplayName: "Pre-size Funds", 2184 IsBoolean: true, 2185 DefaultValue: btc.useSplitTx(), // not nil interface 2186 ShowByDefault: true, 2187 }, 2188 Boolean: &asset.BooleanConfig{}, 2189 } 2190 2191 noSplitEst, _, noSplitLocked, err := btc.estimateSwap(req.Lots, req.LotSize, 2192 req.FeeSuggestion, req.MaxFeeRate, utxos, false, bump) 2193 if err != nil { 2194 btc.log.Errorf("estimateSwap (no split) error: %v", err) 2195 opt.Boolean.Reason = fmt.Sprintf("estimate without a split failed with \"%v\"", err) 2196 return opt // utility and overlock report unavailable, but show the option 2197 } 2198 splitEst, splitUsed, splitLocked, err := btc.estimateSwap(req.Lots, req.LotSize, 2199 req.FeeSuggestion, req.MaxFeeRate, utxos, true, bump) 2200 if err != nil { 2201 btc.log.Errorf("estimateSwap (with split) error: %v", err) 2202 opt.Boolean.Reason = fmt.Sprintf("estimate with a split failed with \"%v\"", err) 2203 return opt // utility and overlock report unavailable, but show the option 2204 } 2205 symbol := strings.ToUpper(btc.symbol) 2206 2207 if !splitUsed || splitLocked >= noSplitLocked { // locked check should be redundant 2208 opt.Boolean.Reason = fmt.Sprintf("avoids no %s overlock for this order (ignored)", symbol) 2209 opt.Description = fmt.Sprintf("A split transaction for this order avoids no %s overlock, "+ 2210 "but adds additional fees.", symbol) 2211 opt.DefaultValue = false 2212 return opt // not enabled by default, but explain why 2213 } 2214 2215 overlock := noSplitLocked - splitLocked 2216 pctChange := (float64(splitEst.RealisticWorstCase)/float64(noSplitEst.RealisticWorstCase) - 1) * 100 2217 if pctChange > 1 { 2218 opt.Boolean.Reason = fmt.Sprintf("+%d%% fees, avoids %s %s overlock", int(math.Round(pctChange)), prettyBTC(overlock), symbol) 2219 } else { 2220 opt.Boolean.Reason = fmt.Sprintf("+%.1f%% fees, avoids %s %s overlock", pctChange, prettyBTC(overlock), symbol) 2221 } 2222 2223 xtraFees := splitEst.RealisticWorstCase - noSplitEst.RealisticWorstCase 2224 opt.Description = fmt.Sprintf("Using a split transaction to prevent temporary overlock of %s %s, but for additional fees of %s %s", 2225 prettyBTC(overlock), symbol, prettyBTC(xtraFees), symbol) 2226 2227 return opt 2228 } 2229 2230 // estimateSwap prepares an *asset.SwapEstimate. 2231 func (btc *baseWallet) estimateSwap(lots, lotSize, feeSuggestion, maxFeeRate uint64, utxos []*CompositeUTXO, 2232 trySplit bool, feeBump float64) (*asset.SwapEstimate, bool /*split used*/, uint64 /*amt locked*/, error) { 2233 2234 var avail uint64 2235 for _, utxo := range utxos { 2236 avail += utxo.Amount 2237 } 2238 reserves := btc.bondReserves.Load() 2239 2240 // If there is a fee bump, the networkFeeRate can be higher than the 2241 // MaxFeeRate 2242 bumpedMaxRate := maxFeeRate 2243 bumpedNetRate := feeSuggestion 2244 if feeBump > 1 { 2245 bumpedMaxRate = uint64(math.Ceil(float64(bumpedMaxRate) * feeBump)) 2246 bumpedNetRate = uint64(math.Ceil(float64(bumpedNetRate) * feeBump)) 2247 } 2248 2249 feeReservesPerLot := bumpedMaxRate * btc.initTxSize 2250 2251 val := lots * lotSize 2252 // The orderEnough func does not account for a split transaction at the start, 2253 // so it is possible that funding for trySplit would actually choose more 2254 // UTXOs. Actual order funding accounts for this. For this estimate, we will 2255 // just not use a split tx if the split-adjusted required funds exceeds the 2256 // total value of the UTXO selected with this enough closure. 2257 sum, _, inputsSize, _, _, _, _, err := TryFund(utxos, 2258 orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, trySplit)) 2259 if err != nil { 2260 return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", amount(val), err) 2261 } 2262 2263 digestInputs := func(inputsSize uint64) (reqFunds, maxFees, estHighFees, estLowFees uint64) { 2264 reqFunds = calc.RequiredOrderFunds(val, inputsSize, lots, 2265 btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) // same as in enough func 2266 maxFees = reqFunds - val 2267 2268 estHighFunds := calc.RequiredOrderFunds(val, inputsSize, lots, 2269 btc.initTxSizeBase, btc.initTxSize, bumpedNetRate) 2270 estHighFees = estHighFunds - val 2271 2272 estLowFunds := calc.RequiredOrderFunds(val, inputsSize, 1, 2273 btc.initTxSizeBase, btc.initTxSize, bumpedNetRate) // best means single multi-lot match, even better than batch 2274 estLowFees = estLowFunds - val 2275 return 2276 } 2277 2278 reqFunds, maxFees, estHighFees, estLowFees := digestInputs(inputsSize) 2279 2280 // Math for split transactions is a little different. 2281 if trySplit { 2282 _, splitMaxFees := btc.splitBaggageFees(bumpedMaxRate, false) 2283 _, splitFees := btc.splitBaggageFees(bumpedNetRate, false) 2284 reqTotal := reqFunds + splitMaxFees // ~ rather than actually fund()ing again 2285 if reqTotal <= sum && sum-reqTotal >= reserves { 2286 return &asset.SwapEstimate{ 2287 Lots: lots, 2288 Value: val, 2289 MaxFees: maxFees + splitMaxFees, 2290 RealisticBestCase: estLowFees + splitFees, 2291 RealisticWorstCase: estHighFees + splitFees, 2292 FeeReservesPerLot: feeReservesPerLot, 2293 }, true, reqFunds, nil // requires reqTotal, but locks reqFunds in the split output 2294 } 2295 } 2296 2297 if sum > avail-reserves { 2298 if trySplit { 2299 return nil, false, 0, errors.New("balance too low to both fund order and maintain bond reserves") 2300 } 2301 kept := leastOverFund(reserveEnough(reserves), utxos) 2302 utxos := UTxOSetDiff(utxos, kept) 2303 sum, _, inputsSize, _, _, _, _, err = TryFund(utxos, orderEnough(val, lots, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, false)) 2304 if err != nil { 2305 return nil, false, 0, fmt.Errorf("error funding swap value %s: %w", amount(val), err) 2306 } 2307 _, maxFees, estHighFees, estLowFees = digestInputs(inputsSize) 2308 } 2309 2310 return &asset.SwapEstimate{ 2311 Lots: lots, 2312 Value: val, 2313 MaxFees: maxFees, 2314 RealisticBestCase: estLowFees, 2315 RealisticWorstCase: estHighFees, 2316 FeeReservesPerLot: feeReservesPerLot, 2317 }, false, sum, nil 2318 } 2319 2320 // PreRedeem generates an estimate of the range of redemption fees that could 2321 // be assessed. 2322 func (btc *baseWallet) preRedeem(numLots, feeSuggestion uint64, options map[string]string) (*asset.PreRedeem, error) { 2323 feeRate := feeSuggestion 2324 if feeRate == 0 { 2325 feeRate = btc.targetFeeRateWithFallback(btc.redeemConfTarget(), 0) 2326 } 2327 // Best is one transaction with req.Lots inputs and 1 output. 2328 var best uint64 = dexbtc.MinimumTxOverhead 2329 // Worst is req.Lots transactions, each with one input and one output. 2330 var worst uint64 = dexbtc.MinimumTxOverhead * numLots 2331 var inputSize, outputSize uint64 2332 if btc.segwit { 2333 // Add the marker and flag weight here. 2334 inputSize = dexbtc.TxInOverhead + (dexbtc.RedeemSwapSigScriptSize+2+3)/4 2335 outputSize = dexbtc.P2WPKHOutputSize 2336 2337 } else { 2338 inputSize = dexbtc.TxInOverhead + dexbtc.RedeemSwapSigScriptSize 2339 outputSize = dexbtc.P2PKHOutputSize 2340 } 2341 best += inputSize*numLots + outputSize 2342 worst += (inputSize + outputSize) * numLots 2343 2344 // Read the order options. 2345 customCfg := new(redeemOptions) 2346 err := config.Unmapify(options, customCfg) 2347 if err != nil { 2348 return nil, fmt.Errorf("error parsing selected options: %w", err) 2349 } 2350 2351 // Parse the configured fee bump. 2352 currentBump := 1.0 2353 if customCfg.FeeBump != nil { 2354 bump := *customCfg.FeeBump 2355 if bump < 1.0 || bump > 2.0 { 2356 return nil, fmt.Errorf("invalid fee bump: %f", bump) 2357 } 2358 currentBump = bump 2359 } 2360 2361 opts := []*asset.OrderOption{{ 2362 ConfigOption: asset.ConfigOption{ 2363 Key: redeemFeeBumpFee, 2364 DisplayName: "Change Redemption Fees", 2365 Description: "Bump the redemption transaction fees up to 2x for faster confirmation of your redemption transaction.", 2366 DefaultValue: 1.0, 2367 }, 2368 XYRange: &asset.XYRange{ 2369 Start: asset.XYRangePoint{ 2370 Label: "1X", 2371 X: 1.0, 2372 Y: float64(feeRate), 2373 }, 2374 End: asset.XYRangePoint{ 2375 Label: "2X", 2376 X: 2.0, 2377 Y: float64(feeRate * 2), 2378 }, 2379 YUnit: btc.walletInfo.UnitInfo.AtomicUnit + "/" + btc.sizeUnit(), 2380 XUnit: "X", 2381 }, 2382 }} 2383 2384 return &asset.PreRedeem{ 2385 Estimate: &asset.RedeemEstimate{ 2386 RealisticWorstCase: uint64(math.Round(float64(worst*feeRate) * currentBump)), 2387 RealisticBestCase: uint64(math.Round(float64(best*feeRate) * currentBump)), 2388 }, 2389 Options: opts, 2390 }, nil 2391 } 2392 2393 // PreRedeem generates an estimate of the range of redemption fees that could 2394 // be assessed. 2395 func (btc *baseWallet) PreRedeem(form *asset.PreRedeemForm) (*asset.PreRedeem, error) { 2396 return btc.preRedeem(form.Lots, form.FeeSuggestion, form.SelectedOptions) 2397 } 2398 2399 // SingleLotRedeemFees returns the fees for a redeem transaction for a single lot. 2400 func (btc *baseWallet) SingleLotRedeemFees(_ uint32, feeSuggestion uint64) (uint64, error) { 2401 preRedeem, err := btc.preRedeem(1, feeSuggestion, nil) 2402 if err != nil { 2403 return 0, err 2404 } 2405 return preRedeem.Estimate.RealisticWorstCase, nil 2406 } 2407 2408 // FundOrder selects coins for use in an order. The coins will be locked, and 2409 // will not be returned in subsequent calls to FundOrder or calculated in calls 2410 // to Available, unless they are unlocked with ReturnCoins. 2411 // The returned []dex.Bytes contains the redeem scripts for the selected coins. 2412 // Equal number of coins and redeemed scripts must be returned. A nil or empty 2413 // dex.Bytes should be appended to the redeem scripts collection for coins with 2414 // no redeem script. 2415 func (btc *baseWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint64, error) { 2416 ordValStr := amount(ord.Value).String() 2417 btc.log.Debugf("Attempting to fund order for %s %s, maxFeeRate = %d, max swaps = %d", 2418 ordValStr, btc.symbol, ord.MaxFeeRate, ord.MaxSwapCount) 2419 2420 if ord.Value == 0 { 2421 return nil, nil, 0, fmt.Errorf("cannot fund value = 0") 2422 } 2423 if ord.MaxSwapCount == 0 { 2424 return nil, nil, 0, fmt.Errorf("cannot fund a zero-lot order") 2425 } 2426 if ord.FeeSuggestion > ord.MaxFeeRate { 2427 return nil, nil, 0, fmt.Errorf("fee suggestion %d > max fee rate %d", ord.FeeSuggestion, ord.MaxFeeRate) 2428 } 2429 // Check wallets fee rate limit against server's max fee rate 2430 if btc.feeRateLimit() < ord.MaxFeeRate { 2431 return nil, nil, 0, fmt.Errorf( 2432 "%v: server's max fee rate %v higher than configued fee rate limit %v", 2433 dex.BipIDSymbol(BipID), ord.MaxFeeRate, btc.feeRateLimit()) 2434 } 2435 2436 customCfg := new(swapOptions) 2437 err := config.Unmapify(ord.Options, customCfg) 2438 if err != nil { 2439 return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err) 2440 } 2441 2442 bumpedMaxRate, err := calcBumpedRate(ord.MaxFeeRate, customCfg.FeeBump) 2443 if err != nil { 2444 btc.log.Errorf("calcBumpRate error: %v", err) 2445 } 2446 2447 // If a split is not requested, but is forced, create an extra output from 2448 // the split tx to help avoid a forced split in subsequent orders. 2449 var extraSplitOutput uint64 2450 useSplit := btc.useSplitTx() 2451 if customCfg.Split != nil { 2452 useSplit = *customCfg.Split 2453 } 2454 2455 reserves := btc.bondReserves.Load() 2456 minConfs := uint32(0) 2457 coins, fundingCoins, spents, redeemScripts, inputsSize, sum, err := btc.cm.Fund(reserves, minConfs, true, 2458 orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit)) 2459 if err != nil { 2460 if !useSplit && reserves > 0 { 2461 // Force a split if funding failure may be due to reserves. 2462 btc.log.Infof("Retrying order funding with a forced split transaction to help respect reserves.") 2463 useSplit = true 2464 coins, fundingCoins, spents, redeemScripts, inputsSize, sum, err = btc.cm.Fund(reserves, minConfs, true, 2465 orderEnough(ord.Value, ord.MaxSwapCount, bumpedMaxRate, btc.initTxSizeBase, btc.initTxSize, btc.segwit, useSplit)) 2466 extraSplitOutput = reserves + btc.BondsFeeBuffer(ord.FeeSuggestion) 2467 } 2468 if err != nil { 2469 return nil, nil, 0, fmt.Errorf("error funding swap value of %s: %w", amount(ord.Value), err) 2470 } 2471 } 2472 2473 if useSplit { 2474 // We apply the bumped fee rate to the split transaction when the 2475 // PreSwap is created, so we use that bumped rate here too. 2476 // But first, check that it's within bounds. 2477 splitFeeRate := ord.FeeSuggestion 2478 if splitFeeRate == 0 { 2479 // TODO 2480 // 1.0: Error when no suggestion. 2481 // return nil, nil, fmt.Errorf("cannot do a split transaction without a fee rate suggestion from the server") 2482 splitFeeRate = btc.targetFeeRateWithFallback(btc.redeemConfTarget(), 0) 2483 // We PreOrder checked this as <= MaxFeeRate, so use that as an 2484 // upper limit. 2485 if splitFeeRate > ord.MaxFeeRate { 2486 splitFeeRate = ord.MaxFeeRate 2487 } 2488 } 2489 splitFeeRate, err = calcBumpedRate(splitFeeRate, customCfg.FeeBump) 2490 if err != nil { 2491 btc.log.Errorf("calcBumpRate error: %v", err) 2492 } 2493 2494 splitCoins, split, splitFees, err := btc.split(ord.Value, ord.MaxSwapCount, spents, 2495 inputsSize, fundingCoins, splitFeeRate, bumpedMaxRate, extraSplitOutput) 2496 if err != nil { 2497 if err := btc.ReturnCoins(coins); err != nil { 2498 btc.log.Errorf("Error returning coins: %v", err) 2499 } 2500 return nil, nil, 0, err 2501 } else if split { 2502 return splitCoins, []dex.Bytes{nil}, splitFees, nil // no redeem script required for split tx output 2503 } 2504 return coins, redeemScripts, 0, nil // splitCoins == coins 2505 } 2506 2507 btc.log.Infof("Funding %s %s order with coins %v worth %s", 2508 ordValStr, btc.symbol, coins, amount(sum)) 2509 2510 return coins, redeemScripts, 0, nil 2511 } 2512 2513 // fundsRequiredForMultiOrders returns an slice of the required funds for each 2514 // of a slice of orders and the total required funds. 2515 func (btc *baseWallet) fundsRequiredForMultiOrders(orders []*asset.MultiOrderValue, feeRate uint64, splitBuffer float64, swapInputSize uint64) ([]uint64, uint64) { 2516 requiredForOrders := make([]uint64, len(orders)) 2517 var totalRequired uint64 2518 2519 for i, value := range orders { 2520 req := calc.RequiredOrderFunds(value.Value, swapInputSize, value.MaxSwapCount, btc.initTxSizeBase, btc.initTxSize, feeRate) 2521 req = uint64(math.Round(float64(req) * (100 + splitBuffer) / 100)) 2522 requiredForOrders[i] = req 2523 totalRequired += req 2524 } 2525 2526 return requiredForOrders, totalRequired 2527 } 2528 2529 // fundMultiSplitTx uses the utxos provided and attempts to fund a multi-split 2530 // transaction to fund each of the orders. If successful, it returns the 2531 // funding coins and outputs. 2532 func (btc *baseWallet) fundMultiSplitTx( 2533 orders []*asset.MultiOrderValue, 2534 utxos []*CompositeUTXO, 2535 splitTxFeeRate, maxFeeRate uint64, 2536 splitBuffer float64, 2537 keep, maxLock uint64, 2538 ) (bool, asset.Coins, []*Output) { 2539 2540 var swapInputSize uint64 2541 if btc.segwit { 2542 swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize 2543 } else { 2544 swapInputSize = dexbtc.RedeemP2PKHInputSize 2545 } 2546 _, totalOutputRequired := btc.fundsRequiredForMultiOrders(orders, maxFeeRate, splitBuffer, swapInputSize) 2547 2548 var splitTxSizeWithoutInputs uint64 = dexbtc.MinimumTxOverhead 2549 numOutputs := len(orders) 2550 if keep > 0 { 2551 numOutputs++ 2552 } 2553 if btc.segwit { 2554 splitTxSizeWithoutInputs += uint64(dexbtc.P2WPKHOutputSize * numOutputs) 2555 } else { 2556 splitTxSizeWithoutInputs += uint64(dexbtc.P2PKHOutputSize * numOutputs) 2557 } 2558 enough := func(_, inputsSize, sum uint64) (bool, uint64) { 2559 splitTxFee := (splitTxSizeWithoutInputs + inputsSize) * splitTxFeeRate 2560 req := totalOutputRequired + splitTxFee 2561 return sum >= req, sum - req 2562 } 2563 2564 fundSplitCoins, _, spents, _, inputsSize, _, err := btc.cm.FundWithUTXOs(utxos, keep, false, enough) 2565 if err != nil { 2566 return false, nil, nil 2567 } 2568 2569 if maxLock > 0 { 2570 totalSize := inputsSize + splitTxSizeWithoutInputs 2571 if totalOutputRequired+(totalSize*splitTxFeeRate) > maxLock { 2572 return false, nil, nil 2573 } 2574 } 2575 2576 return true, fundSplitCoins, spents 2577 } 2578 2579 // submitMultiSplitTx creates a multi-split transaction using fundingCoins with 2580 // one output for each order, and submits it to the network. 2581 func (btc *baseWallet) submitMultiSplitTx(fundingCoins asset.Coins, spents []*Output, orders []*asset.MultiOrderValue, 2582 maxFeeRate, splitTxFeeRate uint64, splitBuffer float64) ([]asset.Coins, uint64, error) { 2583 baseTx, totalIn, _, err := btc.fundedTx(fundingCoins) 2584 if err != nil { 2585 return nil, 0, err 2586 } 2587 2588 btc.cm.lockUnspent(false, spents) 2589 var success bool 2590 defer func() { 2591 if !success { 2592 btc.node.lockUnspent(true, spents) 2593 } 2594 }() 2595 2596 var swapInputSize uint64 2597 if btc.segwit { 2598 swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize 2599 } else { 2600 swapInputSize = dexbtc.RedeemP2PKHInputSize 2601 } 2602 2603 requiredForOrders, totalRequired := btc.fundsRequiredForMultiOrders(orders, maxFeeRate, splitBuffer, swapInputSize) 2604 2605 outputAddresses := make([]btcutil.Address, len(orders)) 2606 for i, req := range requiredForOrders { 2607 outputAddr, err := btc.node.externalAddress() 2608 if err != nil { 2609 return nil, 0, err 2610 } 2611 outputAddresses[i] = outputAddr 2612 script, err := txscript.PayToAddrScript(outputAddr) 2613 if err != nil { 2614 return nil, 0, err 2615 } 2616 baseTx.AddTxOut(wire.NewTxOut(int64(req), script)) 2617 } 2618 2619 changeAddr, err := btc.node.changeAddress() 2620 if err != nil { 2621 return nil, 0, err 2622 } 2623 tx, err := btc.sendWithReturn(baseTx, changeAddr, totalIn, totalRequired, splitTxFeeRate) 2624 if err != nil { 2625 return nil, 0, err 2626 } 2627 2628 txHash := btc.hashTx(tx) 2629 coins := make([]asset.Coins, len(orders)) 2630 ops := make([]*Output, len(orders)) 2631 locks := make([]*UTxO, len(coins)) 2632 for i := range coins { 2633 coins[i] = asset.Coins{NewOutput(txHash, uint32(i), uint64(tx.TxOut[i].Value))} 2634 ops[i] = NewOutput(txHash, uint32(i), uint64(tx.TxOut[i].Value)) 2635 locks[i] = &UTxO{ 2636 TxHash: txHash, 2637 Vout: uint32(i), 2638 Amount: uint64(tx.TxOut[i].Value), 2639 Address: outputAddresses[i].String(), 2640 } 2641 } 2642 btc.cm.LockUTXOs(locks) 2643 btc.node.lockUnspent(false, ops) 2644 2645 var totalOut uint64 2646 for _, txOut := range tx.TxOut { 2647 totalOut += uint64(txOut.Value) 2648 } 2649 2650 btc.addTxToHistory(&asset.WalletTransaction{ 2651 Type: asset.Split, 2652 ID: txHash.String(), 2653 Fees: totalIn - totalOut, 2654 }, txHash, true) 2655 2656 success = true 2657 return coins, totalIn - totalOut, nil 2658 } 2659 2660 // fundMultiWithSplit creates a split transaction to fund multiple orders. It 2661 // attempts to fund as many of the orders as possible without a split transaction, 2662 // and only creates a split transaction for the remaining orders. This is only 2663 // called after it has been determined that all of the orders cannot be funded 2664 // without a split transaction. 2665 func (btc *baseWallet) fundMultiWithSplit(keep, maxLock uint64, values []*asset.MultiOrderValue, 2666 splitTxFeeRate, maxFeeRate uint64, splitBuffer float64) ([]asset.Coins, [][]dex.Bytes, uint64, error) { 2667 utxos, _, avail, err := btc.cm.SpendableUTXOs(0) 2668 if err != nil { 2669 return nil, nil, 0, fmt.Errorf("error getting spendable utxos: %w", err) 2670 } 2671 2672 canFund, splitCoins, splitSpents := btc.fundMultiSplitTx(values, utxos, splitTxFeeRate, maxFeeRate, splitBuffer, keep, maxLock) 2673 if !canFund { 2674 return nil, nil, 0, fmt.Errorf("cannot fund all with split") 2675 } 2676 2677 remainingUTXOs := utxos 2678 remainingOrders := values 2679 2680 // The return values must be in the same order as the values that were 2681 // passed in, so we keep track of the original indexes here. 2682 indexToFundingCoins := make(map[int][]*CompositeUTXO, len(values)) 2683 remainingIndexes := make([]int, len(values)) 2684 for i := range remainingIndexes { 2685 remainingIndexes[i] = i 2686 } 2687 2688 var totalFunded uint64 2689 2690 // Find each of the orders that can be funded without being included 2691 // in the split transaction. 2692 for range values { 2693 // First find the order the can be funded with the least overlock. 2694 // If there is no order that can be funded without going over the 2695 // maxLock limit, or not leaving enough for bond reserves, then all 2696 // of the remaining orders must be funded with the split transaction. 2697 orderIndex, fundingUTXOs := btc.cm.OrderWithLeastOverFund(maxLock-totalFunded, maxFeeRate, remainingOrders, remainingUTXOs) 2698 if orderIndex == -1 { 2699 break 2700 } 2701 totalFunded += SumUTXOs(fundingUTXOs) 2702 if totalFunded > avail-keep { 2703 break 2704 } 2705 2706 newRemainingOrders := make([]*asset.MultiOrderValue, 0, len(remainingOrders)-1) 2707 newRemainingIndexes := make([]int, 0, len(remainingOrders)-1) 2708 for j := range remainingOrders { 2709 if j != orderIndex { 2710 newRemainingOrders = append(newRemainingOrders, remainingOrders[j]) 2711 newRemainingIndexes = append(newRemainingIndexes, remainingIndexes[j]) 2712 } 2713 } 2714 remainingUTXOs = UTxOSetDiff(remainingUTXOs, fundingUTXOs) 2715 2716 // Then we make sure that a split transaction can be created for 2717 // any remaining orders without using the utxos returned by 2718 // orderWithLeastOverFund. 2719 if len(newRemainingOrders) > 0 { 2720 canFund, newSplitCoins, newSpents := btc.fundMultiSplitTx(newRemainingOrders, remainingUTXOs, 2721 splitTxFeeRate, maxFeeRate, splitBuffer, keep, maxLock-totalFunded) 2722 if !canFund { 2723 break 2724 } 2725 splitCoins = newSplitCoins 2726 splitSpents = newSpents 2727 } 2728 2729 indexToFundingCoins[remainingIndexes[orderIndex]] = fundingUTXOs 2730 remainingOrders = newRemainingOrders 2731 remainingIndexes = newRemainingIndexes 2732 } 2733 2734 var splitOutputCoins []asset.Coins 2735 var splitFees uint64 2736 2737 // This should always be true, otherwise this function would not have been 2738 // called. 2739 if len(remainingOrders) > 0 { 2740 splitOutputCoins, splitFees, err = btc.submitMultiSplitTx(splitCoins, 2741 splitSpents, remainingOrders, maxFeeRate, splitTxFeeRate, splitBuffer) 2742 if err != nil { 2743 return nil, nil, 0, fmt.Errorf("error creating split transaction: %w", err) 2744 } 2745 } 2746 2747 coins := make([]asset.Coins, len(values)) 2748 redeemScripts := make([][]dex.Bytes, len(values)) 2749 spents := make([]*Output, 0, len(values)) 2750 2751 var splitIndex int 2752 locks := make([]*UTxO, 0) 2753 for i := range values { 2754 if fundingUTXOs, ok := indexToFundingCoins[i]; ok { 2755 coins[i] = make(asset.Coins, len(fundingUTXOs)) 2756 redeemScripts[i] = make([]dex.Bytes, len(fundingUTXOs)) 2757 for j, unspent := range fundingUTXOs { 2758 output := NewOutput(unspent.TxHash, unspent.Vout, unspent.Amount) 2759 locks = append(locks, &UTxO{ 2760 TxHash: unspent.TxHash, 2761 Vout: unspent.Vout, 2762 Amount: unspent.Amount, 2763 Address: unspent.Address, 2764 }) 2765 coins[i][j] = output 2766 spents = append(spents, output) 2767 redeemScripts[i][j] = unspent.RedeemScript 2768 } 2769 } else { 2770 coins[i] = splitOutputCoins[splitIndex] 2771 redeemScripts[i] = []dex.Bytes{nil} 2772 splitIndex++ 2773 } 2774 } 2775 2776 btc.cm.LockUTXOs(locks) 2777 btc.node.lockUnspent(false, spents) 2778 2779 return coins, redeemScripts, splitFees, nil 2780 } 2781 2782 // fundMulti first attempts to fund each of the orders with with the available 2783 // UTXOs. If a split is not allowed, it will fund the orders that it was able 2784 // to fund. If splitting is allowed, a split transaction will be created to fund 2785 // all of the orders. 2786 func (btc *baseWallet) fundMulti(maxLock uint64, values []*asset.MultiOrderValue, splitTxFeeRate, maxFeeRate uint64, allowSplit bool, splitBuffer float64) ([]asset.Coins, [][]dex.Bytes, uint64, error) { 2787 reserves := btc.bondReserves.Load() 2788 2789 coins, redeemScripts, fundingCoins, spents, err := btc.cm.FundMultiBestEffort(reserves, maxLock, values, maxFeeRate, allowSplit) 2790 if err != nil { 2791 return nil, nil, 0, err 2792 } 2793 if len(coins) == len(values) || !allowSplit { 2794 btc.cm.LockOutputsMap(fundingCoins) 2795 btc.node.lockUnspent(false, spents) 2796 return coins, redeemScripts, 0, nil 2797 } 2798 2799 return btc.fundMultiWithSplit(reserves, maxLock, values, splitTxFeeRate, maxFeeRate, splitBuffer) 2800 } 2801 2802 // split will send a split transaction and return the sized output. If the 2803 // split transaction is determined to be un-economical, it will not be sent, 2804 // there is no error, and the input coins will be returned unmodified, but an 2805 // info message will be logged. The returned bool indicates if a split tx was 2806 // sent (true) or if the original coins were returned unmodified (false). 2807 // 2808 // A split transaction nets additional network bytes consisting of 2809 // - overhead from 1 transaction 2810 // - 1 extra signed p2wpkh-spending input. The split tx has the fundingCoins as 2811 // inputs now, but we'll add the input that spends the sized coin that will go 2812 // into the first swap if the split tx does not add excess baggage 2813 // - 2 additional p2wpkh outputs for the split tx sized output and change 2814 // 2815 // If the fees associated with this extra baggage are more than the excess 2816 // amount that would be locked if a split transaction were not used, then the 2817 // split transaction is pointless. This might be common, for instance, if an 2818 // order is canceled partially filled, and then the remainder resubmitted. We 2819 // would already have an output of just the right size, and that would be 2820 // recognized here. 2821 func (btc *baseWallet) split(value uint64, lots uint64, outputs []*Output, inputsSize uint64, 2822 fundingCoins map[OutPoint]*UTxO, suggestedFeeRate, bumpedMaxRate, extraOutput uint64) (asset.Coins, bool, uint64, error) { 2823 2824 var err error 2825 defer func() { 2826 if err != nil { 2827 return 2828 } 2829 btc.cm.LockOutputsMap(fundingCoins) 2830 err = btc.node.lockUnspent(false, outputs) 2831 if err != nil { 2832 btc.log.Errorf("error locking unspent outputs: %v", err) 2833 } 2834 }() 2835 2836 // Calculate the extra fees associated with the additional inputs, outputs, 2837 // and transaction overhead, and compare to the excess that would be locked. 2838 swapInputSize, baggage := btc.splitBaggageFees(bumpedMaxRate, extraOutput > 0) 2839 2840 var coinSum uint64 2841 coins := make(asset.Coins, 0, len(outputs)) 2842 for _, op := range outputs { 2843 coins = append(coins, op) 2844 coinSum += op.Val 2845 } 2846 2847 valueStr := amount(value).String() 2848 2849 excess := coinSum - calc.RequiredOrderFunds(value, inputsSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) 2850 if baggage > excess { 2851 btc.log.Debugf("Skipping split transaction because cost is greater than potential over-lock. "+ 2852 "%s > %s", amount(baggage), amount(excess)) 2853 btc.log.Infof("Funding %s %s order with coins %v worth %s", 2854 valueStr, btc.symbol, coins, amount(coinSum)) 2855 return coins, false, 0, nil // err==nil records and locks the provided fundingCoins in defer 2856 } 2857 2858 addr, err := btc.node.externalAddress() 2859 if err != nil { 2860 return nil, false, 0, fmt.Errorf("error creating split transaction address: %w", err) 2861 } 2862 addrStr, err := btc.stringAddr(addr, btc.chainParams) 2863 if err != nil { 2864 return nil, false, 0, fmt.Errorf("failed to stringify the change address: %w", err) 2865 } 2866 2867 reqFunds := calc.RequiredOrderFunds(value, swapInputSize, lots, btc.initTxSizeBase, btc.initTxSize, bumpedMaxRate) 2868 2869 baseTx, _, _, err := btc.fundedTx(coins) 2870 splitScript, err := txscript.PayToAddrScript(addr) 2871 if err != nil { 2872 return nil, false, 0, fmt.Errorf("error creating split tx script: %w", err) 2873 } 2874 baseTx.AddTxOut(wire.NewTxOut(int64(reqFunds), splitScript)) 2875 2876 if extraOutput > 0 { 2877 addr, err := btc.node.changeAddress() 2878 if err != nil { 2879 return nil, false, 0, fmt.Errorf("error creating split transaction address: %w", err) 2880 } 2881 splitScript, err := txscript.PayToAddrScript(addr) 2882 if err != nil { 2883 return nil, false, 0, fmt.Errorf("error creating split tx script: %w", err) 2884 } 2885 baseTx.AddTxOut(wire.NewTxOut(int64(extraOutput), splitScript)) 2886 } 2887 2888 // Grab a change address. 2889 changeAddr, err := btc.node.changeAddress() 2890 if err != nil { 2891 return nil, false, 0, fmt.Errorf("error creating change address: %w", err) 2892 } 2893 2894 // Sign, add change, and send the transaction. 2895 msgTx, err := btc.sendWithReturn(baseTx, changeAddr, coinSum, reqFunds+extraOutput, suggestedFeeRate) 2896 if err != nil { 2897 return nil, false, 0, fmt.Errorf("error sending tx: %w", err) 2898 } 2899 2900 txHash := btc.hashTx(msgTx) 2901 op := NewOutput(txHash, 0, reqFunds) 2902 2903 totalOut := reqFunds 2904 for i := 1; i < len(msgTx.TxOut); i++ { 2905 totalOut += uint64(msgTx.TxOut[i].Value) 2906 } 2907 2908 btc.addTxToHistory(&asset.WalletTransaction{ 2909 Type: asset.Split, 2910 ID: txHash.String(), 2911 Fees: coinSum - totalOut, 2912 }, txHash, true) 2913 2914 fundingCoins = map[OutPoint]*UTxO{op.Pt: { 2915 TxHash: op.txHash(), 2916 Vout: op.vout(), 2917 Address: addrStr, 2918 Amount: reqFunds, 2919 }} 2920 2921 // Unlock spent coins 2922 returnErr := btc.ReturnCoins(coins) 2923 if returnErr != nil { 2924 btc.log.Errorf("error unlocking spent coins: %v", err) 2925 } 2926 2927 btc.log.Infof("Funding %s %s order with split output coin %v from original coins %v", 2928 valueStr, btc.symbol, op, coins) 2929 btc.log.Infof("Sent split transaction %s to accommodate swap of size %s %s + fees = %s", 2930 op.txHash(), valueStr, btc.symbol, amount(reqFunds)) 2931 2932 // Assign to coins so the deferred function will lock the output. 2933 outputs = []*Output{op} 2934 return asset.Coins{op}, true, coinSum - totalOut, nil 2935 } 2936 2937 // splitBaggageFees is the fees associated with adding a split transaction. 2938 func (btc *baseWallet) splitBaggageFees(maxFeeRate uint64, extraOutput bool) (swapInputSize, baggage uint64) { 2939 if btc.segwit { 2940 baggage = maxFeeRate * splitTxBaggageSegwit 2941 if extraOutput { 2942 baggage += maxFeeRate * dexbtc.P2WPKHOutputSize 2943 } 2944 swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize 2945 return 2946 } 2947 baggage = maxFeeRate * splitTxBaggage 2948 if extraOutput { 2949 baggage += maxFeeRate * dexbtc.P2PKHOutputSize 2950 } 2951 swapInputSize = dexbtc.RedeemP2PKHInputSize 2952 return 2953 } 2954 2955 // ReturnCoins unlocks coins. This would be used in the case of a canceled or 2956 // partially filled order. Part of the asset.Wallet interface. 2957 func (btc *baseWallet) ReturnCoins(unspents asset.Coins) error { 2958 return btc.cm.ReturnCoins(unspents) 2959 } 2960 2961 // rawWalletTx gets the raw bytes of a transaction and the number of 2962 // confirmations. This is a wrapper for checkWalletTx (if node is a 2963 // walletTxChecker), with a fallback to getWalletTransaction. 2964 func (btc *baseWallet) rawWalletTx(hash *chainhash.Hash) ([]byte, uint32, error) { 2965 if fast, ok := btc.node.(walletTxChecker); ok { 2966 txRaw, confs, err := fast.checkWalletTx(hash.String()) 2967 if err == nil { 2968 return txRaw, confs, nil 2969 } 2970 btc.log.Warnf("checkWalletTx: %v", err) 2971 // fallback to getWalletTransaction 2972 } 2973 2974 tx, err := btc.node.getWalletTransaction(hash) 2975 if err != nil { 2976 return nil, 0, err 2977 } 2978 return tx.Bytes, uint32(tx.Confirmations), nil 2979 } 2980 2981 // FundingCoins gets funding coins for the coin IDs. The coins are locked. This 2982 // method might be called to reinitialize an order from data stored externally. 2983 // This method will only return funding coins, e.g. unspent transaction outputs. 2984 func (btc *baseWallet) FundingCoins(ids []dex.Bytes) (asset.Coins, error) { 2985 return btc.cm.FundingCoins(ids) 2986 } 2987 2988 // authAddOn implements the asset.Authenticator. 2989 type authAddOn struct { 2990 w Wallet 2991 } 2992 2993 // Unlock unlocks the underlying wallet. The pw supplied should be the same as 2994 // the password for the underlying bitcoind wallet which will also be unlocked. 2995 // It implements asset.authenticator. 2996 func (a *authAddOn) Unlock(pw []byte) error { 2997 return a.w.walletUnlock(pw) 2998 } 2999 3000 // Lock locks the underlying bitcoind wallet. It implements asset.authenticator. 3001 func (a *authAddOn) Lock() error { 3002 return a.w.walletLock() 3003 } 3004 3005 // Locked will be true if the wallet is currently locked. It implements 3006 // asset.authenticator. 3007 func (a *authAddOn) Locked() bool { 3008 return a.w.locked() 3009 } 3010 3011 func (btc *baseWallet) addInputsToTx(tx *wire.MsgTx, coins asset.Coins) (uint64, []OutPoint, error) { 3012 var totalIn uint64 3013 // Add the funding utxos. 3014 pts := make([]OutPoint, 0, len(coins)) 3015 for _, coin := range coins { 3016 op, err := ConvertCoin(coin) 3017 if err != nil { 3018 return 0, nil, fmt.Errorf("error converting coin: %w", err) 3019 } 3020 if op.Val == 0 { 3021 return 0, nil, fmt.Errorf("zero-valued output detected for %s:%d", op.txHash(), op.vout()) 3022 } 3023 totalIn += op.Val 3024 txIn := wire.NewTxIn(op.WireOutPoint(), []byte{}, nil) 3025 tx.AddTxIn(txIn) 3026 pts = append(pts, op.Pt) 3027 } 3028 return totalIn, pts, nil 3029 } 3030 3031 // fundedTx creates and returns a new MsgTx with the provided coins as inputs. 3032 func (btc *baseWallet) fundedTx(coins asset.Coins) (*wire.MsgTx, uint64, []OutPoint, error) { 3033 baseTx := wire.NewMsgTx(btc.txVersion()) 3034 totalIn, pts, err := btc.addInputsToTx(baseTx, coins) 3035 if err != nil { 3036 return nil, 0, nil, err 3037 } 3038 return baseTx, totalIn, pts, nil 3039 } 3040 3041 // lookupWalletTxOutput looks up the value of a transaction output that is 3042 // spandable by this wallet, and creates an output. 3043 func (btc *baseWallet) lookupWalletTxOutput(txHash *chainhash.Hash, vout uint32) (*Output, error) { 3044 getTxResult, err := btc.node.getWalletTransaction(txHash) 3045 if err != nil { 3046 return nil, err 3047 } 3048 3049 tx, err := btc.deserializeTx(getTxResult.Bytes) 3050 if err != nil { 3051 return nil, err 3052 } 3053 if len(tx.TxOut) <= int(vout) { 3054 return nil, fmt.Errorf("txId %s only has %d outputs. tried to access index %d", 3055 txHash, len(tx.TxOut), vout) 3056 } 3057 3058 value := tx.TxOut[vout].Value 3059 return NewOutput(txHash, vout, uint64(value)), nil 3060 } 3061 3062 // getTransactions retrieves the transactions that created coins. The 3063 // returned slice will be in the same order as the argument. 3064 func (btc *baseWallet) getTransactions(coins []dex.Bytes) ([]*GetTransactionResult, error) { 3065 txs := make([]*GetTransactionResult, 0, len(coins)) 3066 3067 for _, coinID := range coins { 3068 txHash, _, err := decodeCoinID(coinID) 3069 if err != nil { 3070 return nil, err 3071 } 3072 getTxRes, err := btc.node.getWalletTransaction(txHash) 3073 if err != nil { 3074 return nil, err 3075 } 3076 txs = append(txs, getTxRes) 3077 } 3078 3079 return txs, nil 3080 } 3081 3082 func (btc *baseWallet) getTxFee(tx *wire.MsgTx) (uint64, error) { 3083 var in, out uint64 3084 3085 for _, txOut := range tx.TxOut { 3086 out += uint64(txOut.Value) 3087 } 3088 3089 for _, txIn := range tx.TxIn { 3090 prevTx, err := btc.node.getWalletTransaction(&txIn.PreviousOutPoint.Hash) 3091 if err != nil { 3092 return 0, err 3093 } 3094 prevMsgTx, err := btc.deserializeTx(prevTx.Bytes) 3095 if err != nil { 3096 return 0, err 3097 } 3098 if len(prevMsgTx.TxOut) <= int(txIn.PreviousOutPoint.Index) { 3099 return 0, fmt.Errorf("tx %x references index %d output of %x, but it only has %d outputs", 3100 btc.hashTx(tx), txIn.PreviousOutPoint.Index, prevMsgTx.TxHash(), len(prevMsgTx.TxOut)) 3101 } 3102 in += uint64(prevMsgTx.TxOut[int(txIn.PreviousOutPoint.Index)].Value) 3103 } 3104 3105 if in < out { 3106 return 0, fmt.Errorf("tx %x has value of inputs %d < value of outputs %d", 3107 btc.hashTx(tx), in, out) 3108 } 3109 3110 return in - out, nil 3111 } 3112 3113 // sizeAndFeesOfUnconfirmedTxs returns the total size in vBytes and the total 3114 // fees spent by the unconfirmed transactions in txs. 3115 func (btc *baseWallet) sizeAndFeesOfUnconfirmedTxs(txs []*GetTransactionResult) (size uint64, fees uint64, err error) { 3116 for _, tx := range txs { 3117 if tx.Confirmations > 0 { 3118 continue 3119 } 3120 3121 msgTx, err := btc.deserializeTx(tx.Bytes) 3122 if err != nil { 3123 return 0, 0, err 3124 } 3125 3126 fee, err := btc.getTxFee(msgTx) 3127 if err != nil { 3128 return 0, 0, err 3129 } 3130 3131 fees += fee 3132 size += btc.calcTxSize(msgTx) 3133 } 3134 3135 return size, fees, nil 3136 } 3137 3138 // additionalFeesRequired calculates the additional satoshis that need to be 3139 // sent to miners in order to increase the average fee rate of unconfirmed 3140 // transactions to newFeeRate. An error is returned if no additional fees 3141 // are required. 3142 func (btc *baseWallet) additionalFeesRequired(txs []*GetTransactionResult, newFeeRate uint64) (uint64, error) { 3143 size, fees, err := btc.sizeAndFeesOfUnconfirmedTxs(txs) 3144 if err != nil { 3145 return 0, err 3146 } 3147 3148 if fees >= size*newFeeRate { 3149 return 0, fmt.Errorf("extra fees are not needed. %d would be needed "+ 3150 "for a fee rate of %d, but %d was already paid", 3151 size*newFeeRate, newFeeRate, fees) 3152 } 3153 3154 return size*newFeeRate - fees, nil 3155 } 3156 3157 // changeCanBeAccelerated returns nil if the change can be accelerated, 3158 // otherwise it returns an error containing the reason why it cannot. 3159 func (btc *baseWallet) changeCanBeAccelerated(change *Output, remainingSwaps bool) error { 3160 lockedUtxos, err := btc.node.listLockUnspent() 3161 if err != nil { 3162 return err 3163 } 3164 3165 changeTxHash := change.Pt.TxHash.String() 3166 for _, utxo := range lockedUtxos { 3167 if utxo.TxID == changeTxHash && utxo.Vout == change.Pt.Vout { 3168 if !remainingSwaps { 3169 return errors.New("change locked by another order") 3170 } 3171 // change is locked by this order 3172 return nil 3173 } 3174 } 3175 3176 utxos, err := btc.node.listUnspent() 3177 if err != nil { 3178 return err 3179 } 3180 for _, utxo := range utxos { 3181 if utxo.TxID == changeTxHash && utxo.Vout == change.Pt.Vout { 3182 return nil 3183 } 3184 } 3185 3186 return errors.New("change already spent") 3187 } 3188 3189 // signedAccelerationTx returns a signed transaction that sends funds to a 3190 // change address controlled by this wallet. This new transaction will have 3191 // a fee high enough to make the average fee of the unmined previousTxs to 3192 // the newFeeRate. orderChange latest change in the order, and must be spent 3193 // by this new transaction in order to accelerate the order. 3194 // requiredForRemainingSwaps is the amount of funds that are still required 3195 // to complete the order, so the change of the acceleration transaction must 3196 // contain at least that amount. 3197 func (btc *baseWallet) signedAccelerationTx(previousTxs []*GetTransactionResult, orderChange *Output, requiredForRemainingSwaps, newFeeRate uint64) (*wire.MsgTx, *Output, uint64, error) { 3198 makeError := func(err error) (*wire.MsgTx, *Output, uint64, error) { 3199 return nil, nil, 0, err 3200 } 3201 3202 err := btc.changeCanBeAccelerated(orderChange, requiredForRemainingSwaps > 0) 3203 if err != nil { 3204 return makeError(err) 3205 } 3206 3207 additionalFeesRequired, err := btc.additionalFeesRequired(previousTxs, newFeeRate) 3208 if err != nil { 3209 return makeError(err) 3210 } 3211 3212 // Figure out how much funds we need to increase the fee to the requested 3213 // amount. 3214 txSize := uint64(dexbtc.MinimumTxOverhead) 3215 // Add the size of using the order change as an input 3216 if btc.segwit { 3217 txSize += dexbtc.RedeemP2WPKHInputTotalSize 3218 } else { 3219 txSize += dexbtc.RedeemP2PKHInputSize 3220 } 3221 // We need an output if funds are still required for additional swaps in 3222 // the order. 3223 if requiredForRemainingSwaps > 0 { 3224 if btc.segwit { 3225 txSize += dexbtc.P2WPKHOutputSize 3226 } else { 3227 txSize += dexbtc.P2PKHOutputSize 3228 } 3229 } 3230 fundsRequired := additionalFeesRequired + requiredForRemainingSwaps + txSize*newFeeRate 3231 3232 var additionalInputs asset.Coins 3233 if fundsRequired > orderChange.Val { 3234 // If change not enough, need to use other UTXOs. 3235 enough := func(_, inputSize, inputsVal uint64) (bool, uint64) { 3236 txSize := dexbtc.MinimumTxOverhead + inputSize 3237 3238 // add the order change as an input 3239 if btc.segwit { 3240 txSize += dexbtc.RedeemP2WPKHInputTotalSize 3241 } else { 3242 txSize += dexbtc.RedeemP2PKHInputSize 3243 } 3244 3245 if requiredForRemainingSwaps > 0 { 3246 if btc.segwit { 3247 txSize += dexbtc.P2WPKHOutputSize 3248 } else { 3249 txSize += dexbtc.P2PKHOutputSize 3250 } 3251 } 3252 3253 totalFees := additionalFeesRequired + txSize*newFeeRate 3254 totalReq := requiredForRemainingSwaps + totalFees 3255 totalVal := inputsVal + orderChange.Val 3256 return totalReq <= totalVal, totalVal - totalReq 3257 } 3258 minConfs := uint32(1) 3259 additionalInputs, _, _, _, _, _, err = btc.cm.Fund(btc.bondReserves.Load(), minConfs, false, enough) 3260 if err != nil { 3261 return makeError(fmt.Errorf("failed to fund acceleration tx: %w", err)) 3262 } 3263 } 3264 3265 baseTx, totalIn, _, err := btc.fundedTx(append(additionalInputs, orderChange)) 3266 if err != nil { 3267 return makeError(err) 3268 } 3269 3270 addr, err := btc.node.externalAddress() 3271 if err != nil { 3272 return makeError(fmt.Errorf("error creating change address: %w", err)) 3273 } 3274 3275 tx, output, txFee, err := btc.signTxAndAddChange(baseTx, addr, totalIn, additionalFeesRequired, newFeeRate) 3276 if err != nil { 3277 return makeError(err) 3278 } 3279 3280 return tx, output, txFee + additionalFeesRequired, nil 3281 } 3282 3283 // FeesForRemainingSwaps returns the fees for a certain number of swaps at a given 3284 // feeRate. This is only accurate if each swap has a single input. Accurate 3285 // estimates should use PreSwap or FundOrder. 3286 func (btc *intermediaryWallet) FeesForRemainingSwaps(n, feeRate uint64) uint64 { 3287 return btc.initTxSize * n * feeRate 3288 } 3289 3290 // AccelerateOrder uses the Child-Pays-For-Parent technique to accelerate a 3291 // chain of swap transactions and previous accelerations. It broadcasts a new 3292 // transaction with a fee high enough so that the average fee of all the 3293 // unconfirmed transactions in the chain and the new transaction will have 3294 // an average fee rate of newFeeRate. The changeCoin argument is the latest 3295 // change in the order. It must be the input in the acceleration transaction 3296 // in order for the order to be accelerated. requiredForRemainingSwaps is the 3297 // amount of funds required to complete the rest of the swaps in the order. 3298 // The change output of the acceleration transaction will have at least 3299 // this amount. 3300 // 3301 // The returned change coin may be nil, and should be checked before use. 3302 func (btc *ExchangeWalletAccelerator) AccelerateOrder(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (asset.Coin, string, error) { 3303 return accelerateOrder(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate) 3304 } 3305 3306 // AccelerateOrder uses the Child-Pays-For-Parent technique to accelerate a 3307 // chain of swap transactions and previous accelerations. It broadcasts a new 3308 // transaction with a fee high enough so that the average fee of all the 3309 // unconfirmed transactions in the chain and the new transaction will have 3310 // an average fee rate of newFeeRate. The changeCoin argument is the latest 3311 // change in the order. It must be the input in the acceleration transaction 3312 // in order for the order to be accelerated. requiredForRemainingSwaps is the 3313 // amount of funds required to complete the rest of the swaps in the order. 3314 // The change output of the acceleration transaction will have at least 3315 // this amount. 3316 // 3317 // The returned change coin may be nil, and should be checked before use. 3318 func (btc *ExchangeWalletSPV) AccelerateOrder(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (asset.Coin, string, error) { 3319 return accelerateOrder(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate) 3320 } 3321 3322 func accelerateOrder(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (asset.Coin, string, error) { 3323 changeTxHash, changeVout, err := decodeCoinID(changeCoin) 3324 if err != nil { 3325 return nil, "", err 3326 } 3327 changeOutput, err := btc.lookupWalletTxOutput(changeTxHash, changeVout) 3328 if err != nil { 3329 return nil, "", err 3330 } 3331 previousTxs, err := btc.getTransactions(append(swapCoins, accelerationCoins...)) 3332 if err != nil { 3333 return nil, "", err 3334 } 3335 signedTx, newChange, fees, err := 3336 btc.signedAccelerationTx(previousTxs, changeOutput, requiredForRemainingSwaps, newFeeRate) 3337 if err != nil { 3338 return nil, "", err 3339 } 3340 3341 _, err = btc.broadcastTx(signedTx) 3342 if err != nil { 3343 return nil, "", err 3344 } 3345 3346 txHash := btc.hashTx(signedTx) 3347 btc.addTxToHistory(&asset.WalletTransaction{ 3348 Type: asset.Acceleration, 3349 ID: txHash.String(), 3350 Fees: fees, 3351 }, txHash, true) 3352 3353 // Delete the old change from the cache 3354 btc.cm.ReturnOutPoint(NewOutPoint(changeTxHash, changeVout)) 3355 3356 if newChange == nil { 3357 return nil, txHash.String(), nil 3358 } 3359 3360 // Add the new change to the cache if needed. We check if 3361 // required for remaining swaps > 0 because this ensures if the 3362 // previous change was locked, this one will also be locked. If 3363 // requiredForRemainingSwaps = 0, but the change was locked, 3364 // changeCanBeAccelerated would have returned an error since this means 3365 // that the change was locked by another order. 3366 if requiredForRemainingSwaps > 0 { 3367 err = btc.node.lockUnspent(false, []*Output{newChange}) 3368 if err != nil { 3369 // The transaction is already broadcasted, so don't fail now. 3370 btc.log.Errorf("failed to lock change output: %v", err) 3371 } 3372 3373 // Log it as a fundingCoin, since it is expected that this will be 3374 // chained into further matches. 3375 btc.cm.LockUTXOs([]*UTxO{{ 3376 TxHash: newChange.txHash(), 3377 Vout: newChange.vout(), 3378 Address: newChange.String(), 3379 Amount: newChange.Val, 3380 }}) 3381 } 3382 3383 // return nil error since tx is already broadcast, and core needs to update 3384 // the change coin 3385 return newChange, txHash.String(), nil 3386 } 3387 3388 // AccelerationEstimate takes the same parameters as AccelerateOrder, but 3389 // instead of broadcasting the acceleration transaction, it just returns 3390 // the amount of funds that will need to be spent in order to increase the 3391 // average fee rate to the desired amount. 3392 func (btc *ExchangeWalletAccelerator) AccelerationEstimate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (uint64, error) { 3393 return accelerationEstimate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate) 3394 } 3395 3396 // AccelerationEstimate takes the same parameters as AccelerateOrder, but 3397 // instead of broadcasting the acceleration transaction, it just returns 3398 // the amount of funds that will need to be spent in order to increase the 3399 // average fee rate to the desired amount. 3400 func (btc *ExchangeWalletSPV) AccelerationEstimate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (uint64, error) { 3401 return accelerationEstimate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, newFeeRate) 3402 } 3403 3404 func accelerationEstimate(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, newFeeRate uint64) (uint64, error) { 3405 previousTxs, err := btc.getTransactions(append(swapCoins, accelerationCoins...)) 3406 if err != nil { 3407 return 0, fmt.Errorf("failed to get transactions: %w", err) 3408 } 3409 3410 changeTxHash, changeVout, err := decodeCoinID(changeCoin) 3411 if err != nil { 3412 return 0, err 3413 } 3414 changeOutput, err := btc.lookupWalletTxOutput(changeTxHash, changeVout) 3415 if err != nil { 3416 return 0, err 3417 } 3418 3419 _, _, fee, err := btc.signedAccelerationTx(previousTxs, changeOutput, requiredForRemainingSwaps, newFeeRate) 3420 if err != nil { 3421 return 0, err 3422 } 3423 3424 return fee, nil 3425 } 3426 3427 // tooEarlyToAccelerate returns an asset.EarlyAcceleration if 3428 // minTimeBeforeAcceleration has not passed since either the earliest 3429 // unconfirmed swap transaction, or the latest acceleration transaction. 3430 func tooEarlyToAccelerate(swapTxs []*GetTransactionResult, accelerationTxs []*GetTransactionResult) (*asset.EarlyAcceleration, error) { 3431 accelerationTxLookup := make(map[string]bool, len(accelerationTxs)) 3432 for _, accelerationCoin := range accelerationTxs { 3433 accelerationTxLookup[accelerationCoin.TxID] = true 3434 } 3435 3436 var latestAcceleration, earliestUnconfirmed uint64 = 0, math.MaxUint64 3437 for _, tx := range swapTxs { 3438 if tx.Confirmations > 0 { 3439 continue 3440 } 3441 if tx.Time < earliestUnconfirmed { 3442 earliestUnconfirmed = tx.Time 3443 } 3444 } 3445 for _, tx := range accelerationTxs { 3446 if tx.Confirmations > 0 { 3447 continue 3448 } 3449 if tx.Time > latestAcceleration { 3450 latestAcceleration = tx.Time 3451 } 3452 } 3453 3454 var actionTime uint64 3455 var wasAccelerated bool 3456 if latestAcceleration == 0 && earliestUnconfirmed == math.MaxUint64 { 3457 return nil, fmt.Errorf("no need to accelerate because all tx are confirmed") 3458 } else if earliestUnconfirmed > latestAcceleration && earliestUnconfirmed < math.MaxUint64 { 3459 actionTime = earliestUnconfirmed 3460 } else { 3461 actionTime = latestAcceleration 3462 wasAccelerated = true 3463 } 3464 3465 currentTime := uint64(time.Now().Unix()) 3466 if actionTime+minTimeBeforeAcceleration > currentTime { 3467 return &asset.EarlyAcceleration{ 3468 TimePast: currentTime - actionTime, 3469 WasAccelerated: wasAccelerated, 3470 }, nil 3471 } 3472 3473 return nil, nil 3474 } 3475 3476 // PreAccelerate returns the current average fee rate of the unmined swap 3477 // initiation and acceleration transactions, and also returns a suggested 3478 // range that the fee rate should be increased to in order to expedite mining. 3479 // The feeSuggestion argument is the current prevailing network rate. It is 3480 // used to help determine the suggestedRange, which is a range meant to give 3481 // the user a good amount of flexibility in determining the post acceleration 3482 // effective fee rate, but still not allowing them to pick something 3483 // outrageously high. 3484 func (btc *ExchangeWalletAccelerator) PreAccelerate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, feeSuggestion uint64) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) { 3485 return preAccelerate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, feeSuggestion) 3486 } 3487 3488 // PreAccelerate returns the current average fee rate of the unmined swap 3489 // initiation and acceleration transactions, and also returns a suggested 3490 // range that the fee rate should be increased to in order to expedite mining. 3491 // The feeSuggestion argument is the current prevailing network rate. It is 3492 // used to help determine the suggestedRange, which is a range meant to give 3493 // the user a good amount of flexibility in determining the post acceleration 3494 // effective fee rate, but still not allowing them to pick something 3495 // outrageously high. 3496 func (btc *ExchangeWalletSPV) PreAccelerate(swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, feeSuggestion uint64) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) { 3497 return preAccelerate(btc.baseWallet, swapCoins, accelerationCoins, changeCoin, requiredForRemainingSwaps, feeSuggestion) 3498 } 3499 3500 // maxAccelerationRate returns the max rate to which an order can be 3501 // accelerated, if the max rate is less than rateNeeded. If the max rate is 3502 // greater than rateNeeded, rateNeeded is returned. 3503 func (btc *baseWallet) maxAccelerationRate(changeVal, feesAlreadyPaid, orderTxVBytes, requiredForRemainingSwaps, rateNeeded uint64) (uint64, error) { 3504 var txSize, witnessSize, additionalUtxosVal uint64 3505 3506 // First, add all the elements that will definitely be part of the 3507 // acceleration transaction, without any additional inputs. 3508 txSize += dexbtc.MinimumTxOverhead 3509 if btc.segwit { 3510 txSize += dexbtc.RedeemP2WPKHInputSize 3511 witnessSize += dexbtc.RedeemP2WPKHInputWitnessWeight 3512 } else { 3513 txSize += dexbtc.RedeemP2PKHInputSize 3514 } 3515 if requiredForRemainingSwaps > 0 { 3516 if btc.segwit { 3517 txSize += dexbtc.P2WPKHOutputSize 3518 } else { 3519 txSize += dexbtc.P2PKHOutputSize 3520 } 3521 } 3522 3523 calcFeeRate := func() uint64 { 3524 accelerationTxVBytes := txSize + (witnessSize+3)/4 3525 totalValue := changeVal + feesAlreadyPaid + additionalUtxosVal 3526 if totalValue < requiredForRemainingSwaps { 3527 return 0 3528 } 3529 totalValue -= requiredForRemainingSwaps 3530 totalSize := accelerationTxVBytes + orderTxVBytes 3531 return totalValue / totalSize 3532 } 3533 3534 if calcFeeRate() >= rateNeeded { 3535 return rateNeeded, nil 3536 } 3537 3538 // If necessary, use as many additional utxos as needed 3539 utxos, _, _, err := btc.cm.SpendableUTXOs(1) 3540 if err != nil { 3541 return 0, err 3542 } 3543 3544 for _, utxo := range utxos { 3545 if utxo.Input.NonStandardScript { 3546 continue 3547 } 3548 txSize += dexbtc.TxInOverhead + 3549 uint64(wire.VarIntSerializeSize(uint64(utxo.Input.SigScriptSize))) + 3550 uint64(utxo.Input.SigScriptSize) 3551 witnessSize += uint64(utxo.Input.WitnessSize) 3552 additionalUtxosVal += utxo.Amount 3553 if calcFeeRate() >= rateNeeded { 3554 return rateNeeded, nil 3555 } 3556 } 3557 3558 return calcFeeRate(), nil 3559 } 3560 3561 func preAccelerate(btc *baseWallet, swapCoins, accelerationCoins []dex.Bytes, changeCoin dex.Bytes, requiredForRemainingSwaps, feeSuggestion uint64) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) { 3562 makeError := func(err error) (uint64, *asset.XYRange, *asset.EarlyAcceleration, error) { 3563 return 0, &asset.XYRange{}, nil, err 3564 } 3565 3566 changeTxHash, changeVout, err := decodeCoinID(changeCoin) 3567 if err != nil { 3568 return makeError(err) 3569 } 3570 changeOutput, err := btc.lookupWalletTxOutput(changeTxHash, changeVout) 3571 if err != nil { 3572 return makeError(err) 3573 } 3574 3575 err = btc.changeCanBeAccelerated(changeOutput, requiredForRemainingSwaps > 0) 3576 if err != nil { 3577 return makeError(err) 3578 } 3579 3580 txs, err := btc.getTransactions(append(swapCoins, accelerationCoins...)) 3581 if err != nil { 3582 return makeError(fmt.Errorf("failed to get transactions: %w", err)) 3583 } 3584 3585 existingTxSize, feesAlreadyPaid, err := btc.sizeAndFeesOfUnconfirmedTxs(txs) 3586 if err != nil { 3587 return makeError(err) 3588 } 3589 // Is it safe to assume that transactions will all have some fee? 3590 if feesAlreadyPaid == 0 { 3591 return makeError(fmt.Errorf("all transactions are already confirmed, no need to accelerate")) 3592 } 3593 3594 earlyAcceleration, err := tooEarlyToAccelerate(txs[:len(swapCoins)], txs[len(swapCoins):]) 3595 if err != nil { 3596 return makeError(err) 3597 } 3598 3599 // The suggested range will be the min and max of the slider that is 3600 // displayed on the UI. The minimum of the range is 1 higher than the 3601 // current effective range of the swap transactions. The max of the range 3602 // will be the maximum of 5x the current effective rate, or 5x the current 3603 // prevailing network rate. This is a completely arbitrary choice, but in 3604 // this way the user will definitely be able to accelerate at least 5x the 3605 // original rate, and even if the prevailing network rate is much higher 3606 // than the current effective rate, they will still have a comformtable 3607 // buffer above the prevailing network rate. 3608 const scalingFactor = 5 3609 currentEffectiveRate := feesAlreadyPaid / existingTxSize 3610 maxSuggestion := currentEffectiveRate * scalingFactor 3611 if feeSuggestion > currentEffectiveRate { 3612 maxSuggestion = feeSuggestion * scalingFactor 3613 } 3614 3615 // We must make sure that the wallet can fund an acceleration at least 3616 // the max suggestion, and if not, lower the max suggestion to the max 3617 // rate that the wallet can fund. 3618 maxRate, err := btc.maxAccelerationRate(changeOutput.Val, feesAlreadyPaid, existingTxSize, requiredForRemainingSwaps, maxSuggestion) 3619 if err != nil { 3620 return makeError(err) 3621 } 3622 if maxRate <= currentEffectiveRate { 3623 return makeError(fmt.Errorf("cannot accelerate, max rate %v <= current rate %v", maxRate, currentEffectiveRate)) 3624 } 3625 if maxRate < maxSuggestion { 3626 maxSuggestion = maxRate 3627 } 3628 3629 suggestedRange := asset.XYRange{ 3630 Start: asset.XYRangePoint{ 3631 Label: "Min", 3632 X: float64(currentEffectiveRate+1) / float64(currentEffectiveRate), 3633 Y: float64(currentEffectiveRate + 1), 3634 }, 3635 End: asset.XYRangePoint{ 3636 Label: "Max", 3637 X: float64(maxSuggestion) / float64(currentEffectiveRate), 3638 Y: float64(maxSuggestion), 3639 }, 3640 XUnit: "X", 3641 YUnit: btc.walletInfo.UnitInfo.AtomicUnit + "/" + btc.sizeUnit(), 3642 } 3643 3644 return currentEffectiveRate, &suggestedRange, earlyAcceleration, nil 3645 } 3646 3647 func (btc *baseWallet) txDB() *BadgerTxDB { 3648 dbi := btc.txHistoryDB.Load() 3649 if dbi == nil { 3650 return nil 3651 } 3652 return dbi.(*BadgerTxDB) 3653 } 3654 3655 func (btc *baseWallet) markTxAsSubmitted(txHash *chainhash.Hash) { 3656 txHistoryDB := btc.txDB() 3657 if txHistoryDB == nil { 3658 return 3659 } 3660 3661 err := txHistoryDB.MarkTxAsSubmitted(txHash.String()) 3662 if err != nil { 3663 btc.log.Errorf("failed to mark tx as submitted in tx history db: %v", err) 3664 } 3665 3666 btc.pendingTxsMtx.RLock() 3667 wt, found := btc.pendingTxs[*txHash] 3668 btc.pendingTxsMtx.RUnlock() 3669 3670 if !found { 3671 btc.log.Errorf("tx %s not found in pending txs", txHash) 3672 return 3673 } 3674 3675 wt.Submitted = true 3676 3677 btc.pendingTxsMtx.Lock() 3678 btc.pendingTxs[*txHash] = wt 3679 btc.pendingTxsMtx.Unlock() 3680 3681 btc.emit.TransactionNote(wt.WalletTransaction, true) 3682 } 3683 3684 func (btc *baseWallet) removeTxFromHistory(txHash *chainhash.Hash) { 3685 txHistoryDB := btc.txDB() 3686 if txHistoryDB == nil { 3687 return 3688 } 3689 3690 err := txHistoryDB.RemoveTx(txHash.String()) 3691 if err != nil { 3692 btc.log.Errorf("failed to remove tx from tx history db: %v", err) 3693 } 3694 3695 btc.pendingTxsMtx.Lock() 3696 delete(btc.pendingTxs, *txHash) 3697 btc.pendingTxsMtx.Unlock() 3698 } 3699 3700 func (btc *baseWallet) addTxToHistory(wt *asset.WalletTransaction, txHash *chainhash.Hash, submitted bool, skipNotes ...bool) { 3701 txHistoryDB := btc.txDB() 3702 if txHistoryDB == nil { 3703 return 3704 } 3705 3706 ewt := &ExtendedWalletTx{ 3707 WalletTransaction: wt, 3708 Submitted: submitted, 3709 } 3710 3711 if wt.BlockNumber == 0 { 3712 btc.pendingTxsMtx.Lock() 3713 btc.pendingTxs[*txHash] = *ewt 3714 btc.pendingTxsMtx.Unlock() 3715 } 3716 3717 err := txHistoryDB.StoreTx(ewt) 3718 if err != nil { 3719 btc.log.Errorf("failed to store tx in tx history db: %v", err) 3720 } 3721 3722 skipNote := len(skipNotes) > 0 && skipNotes[0] 3723 if submitted && !skipNote { 3724 btc.emit.TransactionNote(wt, true) 3725 } 3726 } 3727 3728 // Swap sends the swaps in a single transaction and prepares the receipts. The 3729 // Receipts returned can be used to refund a failed transaction. The Input coins 3730 // are NOT manually unlocked because they're auto-unlocked when the transaction 3731 // is broadcasted. 3732 func (btc *baseWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint64, error) { 3733 if swaps.FeeRate == 0 { 3734 return nil, nil, 0, fmt.Errorf("cannot send swap with with zero fee rate") 3735 } 3736 3737 contracts := make([][]byte, 0, len(swaps.Contracts)) 3738 var totalOut uint64 3739 // Start with an empty MsgTx. 3740 baseTx, totalIn, pts, err := btc.fundedTx(swaps.Inputs) 3741 if err != nil { 3742 return nil, nil, 0, err 3743 } 3744 3745 customCfg := new(swapOptions) 3746 err = config.Unmapify(swaps.Options, customCfg) 3747 if err != nil { 3748 return nil, nil, 0, fmt.Errorf("error parsing swap options: %w", err) 3749 } 3750 3751 refundAddrs := make([]btcutil.Address, 0, len(swaps.Contracts)) 3752 3753 // Add the contract outputs. 3754 // TODO: Make P2WSH contract and P2WPKH change outputs instead of 3755 // legacy/non-segwit swap contracts pkScripts. 3756 for _, contract := range swaps.Contracts { 3757 totalOut += contract.Value 3758 // revokeAddr is the address belonging to the key that may be used to 3759 // sign and refund a swap past its encoded refund locktime. 3760 revokeAddrStr, err := btc.recyclableAddress() 3761 if err != nil { 3762 return nil, nil, 0, fmt.Errorf("error creating revocation address: %w", err) 3763 } 3764 revokeAddr, err := btc.decodeAddr(revokeAddrStr, btc.chainParams) 3765 if err != nil { 3766 return nil, nil, 0, fmt.Errorf("refund address decode error: %v", err) 3767 } 3768 refundAddrs = append(refundAddrs, revokeAddr) 3769 3770 contractAddr, err := btc.decodeAddr(contract.Address, btc.chainParams) 3771 if err != nil { 3772 return nil, nil, 0, fmt.Errorf("contract address decode error: %v", err) 3773 } 3774 3775 // Create the contract, a P2SH redeem script. 3776 contractScript, err := dexbtc.MakeContract(contractAddr, revokeAddr, 3777 contract.SecretHash, int64(contract.LockTime), btc.segwit, btc.chainParams) 3778 if err != nil { 3779 return nil, nil, 0, fmt.Errorf("unable to create pubkey script for address %s: %w", contract.Address, err) 3780 } 3781 contracts = append(contracts, contractScript) 3782 3783 // Make the P2SH address and pubkey script. 3784 scriptAddr, err := btc.scriptHashAddress(contractScript) 3785 if err != nil { 3786 return nil, nil, 0, fmt.Errorf("error encoding script address: %w", err) 3787 } 3788 3789 pkScript, err := txscript.PayToAddrScript(scriptAddr) 3790 if err != nil { 3791 return nil, nil, 0, fmt.Errorf("error creating pubkey script: %w", err) 3792 } 3793 3794 // Add the transaction output. 3795 txOut := wire.NewTxOut(int64(contract.Value), pkScript) 3796 baseTx.AddTxOut(txOut) 3797 } 3798 if totalIn < totalOut { 3799 return nil, nil, 0, fmt.Errorf("unfunded contract. %d < %d", totalIn, totalOut) 3800 } 3801 3802 // Ensure we have enough outputs before broadcasting. 3803 swapCount := len(swaps.Contracts) 3804 if len(baseTx.TxOut) < swapCount { 3805 return nil, nil, 0, fmt.Errorf("fewer outputs than swaps. %d < %d", len(baseTx.TxOut), swapCount) 3806 } 3807 3808 // Grab a change address. 3809 changeAddr, err := btc.node.changeAddress() 3810 if err != nil { 3811 return nil, nil, 0, fmt.Errorf("error creating change address: %w", err) 3812 } 3813 3814 feeRate, err := calcBumpedRate(swaps.FeeRate, customCfg.FeeBump) 3815 if err != nil { 3816 btc.log.Errorf("ignoring invalid fee bump factor, %s: %v", float64PtrStr(customCfg.FeeBump), err) 3817 } 3818 3819 // Sign, add change, but don't send the transaction yet until 3820 // the individual swap refund txs are prepared and signed. 3821 msgTx, change, fees, err := btc.signTxAndAddChange(baseTx, changeAddr, totalIn, totalOut, feeRate) 3822 if err != nil { 3823 return nil, nil, 0, err 3824 } 3825 txHash := btc.hashTx(msgTx) 3826 3827 // Prepare the receipts. 3828 receipts := make([]asset.Receipt, 0, swapCount) 3829 for i, contract := range swaps.Contracts { 3830 output := NewOutput(txHash, uint32(i), contract.Value) 3831 refundAddr := refundAddrs[i] 3832 signedRefundTx, err := btc.refundTx(output.txHash(), output.vout(), contracts[i], 3833 contract.Value, refundAddr, swaps.FeeRate) 3834 if err != nil { 3835 return nil, nil, 0, fmt.Errorf("error creating refund tx: %w", err) 3836 } 3837 refundBuff := new(bytes.Buffer) 3838 err = signedRefundTx.Serialize(refundBuff) 3839 if err != nil { 3840 return nil, nil, 0, fmt.Errorf("error serializing refund tx: %w", err) 3841 } 3842 receipts = append(receipts, &SwapReceipt{ 3843 Output: output, 3844 SwapContract: contracts[i], 3845 ExpirationTime: time.Unix(int64(contract.LockTime), 0).UTC(), 3846 SignedRefundBytes: refundBuff.Bytes(), 3847 }) 3848 } 3849 3850 // Refund txs prepared and signed. Can now broadcast the swap(s). 3851 _, err = btc.broadcastTx(msgTx) 3852 if err != nil { 3853 return nil, nil, 0, err 3854 } 3855 3856 btc.addTxToHistory(&asset.WalletTransaction{ 3857 Type: asset.Swap, 3858 ID: txHash.String(), 3859 Amount: totalOut, 3860 Fees: fees, 3861 }, txHash, true) 3862 3863 // If change is nil, return a nil asset.Coin. 3864 var changeCoin asset.Coin 3865 if change != nil { 3866 changeCoin = change 3867 } 3868 3869 var locks []*UTxO 3870 if change != nil && swaps.LockChange { 3871 // Lock the change output 3872 btc.log.Debugf("locking change coin %s", change) 3873 err = btc.node.lockUnspent(false, []*Output{change}) 3874 if err != nil { 3875 // The swap transaction is already broadcasted, so don't fail now. 3876 btc.log.Errorf("failed to lock change output: %v", err) 3877 } 3878 3879 addrStr, err := btc.stringAddr(changeAddr, btc.chainParams) 3880 if err != nil { 3881 btc.log.Errorf("Failed to stringify address %v (default encoding): %v", changeAddr, err) 3882 addrStr = changeAddr.String() // may or may not be able to retrieve the private keys for the next swap! 3883 } 3884 3885 // Log it as a fundingCoin, since it is expected that this will be 3886 // chained into further matches. 3887 locks = append(locks, &UTxO{ 3888 TxHash: change.txHash(), 3889 Vout: change.vout(), 3890 Address: addrStr, 3891 Amount: change.Val, 3892 }) 3893 } 3894 3895 btc.cm.LockUTXOs(locks) 3896 btc.cm.UnlockOutPoints(pts) 3897 3898 return receipts, changeCoin, fees, nil 3899 } 3900 3901 // Redeem sends the redemption transaction, completing the atomic swap. 3902 func (btc *baseWallet) Redeem(form *asset.RedeemForm) ([]dex.Bytes, asset.Coin, uint64, error) { 3903 // Create a transaction that spends the referenced contract. 3904 msgTx := wire.NewMsgTx(btc.txVersion()) 3905 var totalIn uint64 3906 contracts := make([][]byte, 0, len(form.Redemptions)) 3907 prevScripts := make([][]byte, 0, len(form.Redemptions)) 3908 addresses := make([]btcutil.Address, 0, len(form.Redemptions)) 3909 values := make([]int64, 0, len(form.Redemptions)) 3910 for _, r := range form.Redemptions { 3911 if r.Spends == nil { 3912 return nil, nil, 0, fmt.Errorf("no audit info") 3913 } 3914 3915 cinfo, err := ConvertAuditInfo(r.Spends, btc.decodeAddr, btc.chainParams) 3916 if err != nil { 3917 return nil, nil, 0, err 3918 } 3919 3920 // Extract the swap contract recipient and secret hash and check the secret 3921 // hash against the hash of the provided secret. 3922 contract := cinfo.contract 3923 _, receiver, _, secretHash, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams) 3924 if err != nil { 3925 return nil, nil, 0, fmt.Errorf("error extracting swap addresses: %w", err) 3926 } 3927 checkSecretHash := sha256.Sum256(r.Secret) 3928 if !bytes.Equal(checkSecretHash[:], secretHash) { 3929 return nil, nil, 0, fmt.Errorf("secret hash mismatch") 3930 } 3931 pkScript, err := btc.scriptHashScript(contract) 3932 if err != nil { 3933 return nil, nil, 0, fmt.Errorf("error constructs p2sh script: %v", err) 3934 } 3935 prevScripts = append(prevScripts, pkScript) 3936 addresses = append(addresses, receiver) 3937 contracts = append(contracts, contract) 3938 txIn := wire.NewTxIn(cinfo.Output.WireOutPoint(), nil, nil) 3939 msgTx.AddTxIn(txIn) 3940 values = append(values, int64(cinfo.Output.Val)) 3941 totalIn += cinfo.Output.Val 3942 } 3943 3944 // Calculate the size and the fees. 3945 size := btc.calcTxSize(msgTx) 3946 if btc.segwit { 3947 // Add the marker and flag weight here. 3948 witnessVBytes := (dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + 2 + 3) / 4 3949 size += witnessVBytes + dexbtc.P2WPKHOutputSize 3950 } else { 3951 size += dexbtc.RedeemSwapSigScriptSize*uint64(len(form.Redemptions)) + dexbtc.P2PKHOutputSize 3952 } 3953 3954 customCfg := new(redeemOptions) 3955 err := config.Unmapify(form.Options, customCfg) 3956 if err != nil { 3957 return nil, nil, 0, fmt.Errorf("error parsing selected swap options: %w", err) 3958 } 3959 3960 rawFeeRate := btc.targetFeeRateWithFallback(btc.redeemConfTarget(), form.FeeSuggestion) 3961 feeRate, err := calcBumpedRate(rawFeeRate, customCfg.FeeBump) 3962 if err != nil { 3963 btc.log.Errorf("calcBumpRate error: %v", err) 3964 } 3965 fee := feeRate * size 3966 if fee > totalIn { 3967 // Double check that the fee bump isn't the issue. 3968 feeRate = rawFeeRate 3969 fee = feeRate * size 3970 if fee > totalIn { 3971 return nil, nil, 0, fmt.Errorf("redeem tx not worth the fees") 3972 } 3973 btc.log.Warnf("Ignoring fee bump (%s) resulting in fees > redemption", float64PtrStr(customCfg.FeeBump)) 3974 } 3975 3976 // Send the funds back to the exchange wallet. 3977 redeemAddr, err := btc.node.externalAddress() 3978 if err != nil { 3979 return nil, nil, 0, fmt.Errorf("error getting new address from the wallet: %w", err) 3980 } 3981 pkScript, err := txscript.PayToAddrScript(redeemAddr) 3982 if err != nil { 3983 return nil, nil, 0, fmt.Errorf("error creating change script: %w", err) 3984 } 3985 txOut := wire.NewTxOut(int64(totalIn-fee), pkScript) 3986 // One last check for dust. 3987 if btc.IsDust(txOut, feeRate) { 3988 return nil, nil, 0, fmt.Errorf("swap redeem output is dust") 3989 } 3990 msgTx.AddTxOut(txOut) 3991 3992 if btc.segwit { 3993 // NewTxSigHashes uses the PrevOutFetcher only for detecting a taproot 3994 // output, so we can provide a dummy that always returns a wire.TxOut 3995 // with a nil pkScript that so IsPayToTaproot returns false. 3996 sigHashes := txscript.NewTxSigHashes(msgTx, new(txscript.CannedPrevOutputFetcher)) 3997 for i, r := range form.Redemptions { 3998 contract := contracts[i] 3999 redeemSig, redeemPubKey, err := btc.createWitnessSig(msgTx, i, contract, addresses[i], values[i], sigHashes) 4000 if err != nil { 4001 return nil, nil, 0, err 4002 } 4003 msgTx.TxIn[i].Witness = dexbtc.RedeemP2WSHContract(contract, redeemSig, redeemPubKey, r.Secret) 4004 } 4005 } else { 4006 for i, r := range form.Redemptions { 4007 contract := contracts[i] 4008 redeemSig, redeemPubKey, err := btc.createSig(msgTx, i, contract, addresses[i], values, prevScripts) 4009 if err != nil { 4010 return nil, nil, 0, err 4011 } 4012 msgTx.TxIn[i].SignatureScript, err = dexbtc.RedeemP2SHContract(contract, redeemSig, redeemPubKey, r.Secret) 4013 if err != nil { 4014 return nil, nil, 0, err 4015 } 4016 } 4017 } 4018 4019 // Send the transaction. 4020 txHash, err := btc.broadcastTx(msgTx) 4021 if err != nil { 4022 return nil, nil, 0, err 4023 } 4024 4025 btc.addTxToHistory(&asset.WalletTransaction{ 4026 Type: asset.Redeem, 4027 ID: txHash.String(), 4028 Amount: totalIn, 4029 Fees: fee, 4030 }, txHash, true) 4031 4032 // Log the change output. 4033 coinIDs := make([]dex.Bytes, 0, len(form.Redemptions)) 4034 for i := range form.Redemptions { 4035 coinIDs = append(coinIDs, ToCoinID(txHash, uint32(i))) 4036 } 4037 return coinIDs, NewOutput(txHash, 0, uint64(txOut.Value)), fee, nil 4038 } 4039 4040 // ConvertAuditInfo converts from the common *asset.AuditInfo type to our 4041 // internal *auditInfo type. 4042 func ConvertAuditInfo(ai *asset.AuditInfo, decodeAddr dexbtc.AddressDecoder, chainParams *chaincfg.Params) (*AuditInfo, error) { 4043 if ai.Coin == nil { 4044 return nil, fmt.Errorf("no coin") 4045 } 4046 4047 txHash, vout, err := decodeCoinID(ai.Coin.ID()) 4048 if err != nil { 4049 return nil, err 4050 } 4051 4052 recip, err := decodeAddr(ai.Recipient, chainParams) 4053 if err != nil { 4054 return nil, err 4055 } 4056 4057 return &AuditInfo{ 4058 Output: NewOutput(txHash, vout, ai.Coin.Value()), // *Output 4059 Recipient: recip, // btcutil.Address 4060 contract: ai.Contract, // []byte 4061 secretHash: ai.SecretHash, // []byte 4062 expiration: ai.Expiration, // time.Time 4063 }, nil 4064 } 4065 4066 // SignMessage signs the message with the private key associated with the 4067 // specified unspent coin. A slice of pubkeys required to spend the coin and a 4068 // signature for each pubkey are returned. 4069 func (btc *baseWallet) SignMessage(coin asset.Coin, msg dex.Bytes) (pubkeys, sigs []dex.Bytes, err error) { 4070 op, err := ConvertCoin(coin) 4071 if err != nil { 4072 return nil, nil, fmt.Errorf("error converting coin: %w", err) 4073 } 4074 utxo := btc.cm.LockedOutput(op.Pt) 4075 4076 if utxo == nil { 4077 return nil, nil, fmt.Errorf("no utxo found for %s", op) 4078 } 4079 privKey, err := btc.node.privKeyForAddress(utxo.Address) 4080 if err != nil { 4081 return nil, nil, err 4082 } 4083 defer privKey.Zero() 4084 pk := privKey.PubKey() 4085 hash := chainhash.HashB(msg) // legacy servers will not accept this signature! 4086 sig := ecdsa.Sign(privKey, hash) 4087 pubkeys = append(pubkeys, pk.SerializeCompressed()) 4088 sigs = append(sigs, sig.Serialize()) // DER format serialization 4089 return 4090 } 4091 4092 // AuditContract retrieves information about a swap contract from the provided 4093 // txData. The extracted information would be used to audit the counter-party's 4094 // contract during a swap. The txData may be empty to attempt retrieval of the 4095 // transaction output from the network, but it is only ensured to succeed for a 4096 // full node or, if the tx is confirmed, an SPV wallet. Normally the server 4097 // should communicate this txData, and the caller can decide to require it. The 4098 // ability to work with an empty txData is a convenience for recovery tools and 4099 // testing, and it may change in the future if a GetTxData method is added for 4100 // this purpose. 4101 func (btc *baseWallet) AuditContract(coinID, contract, txData dex.Bytes, rebroadcast bool) (*asset.AuditInfo, error) { 4102 txHash, vout, err := decodeCoinID(coinID) 4103 if err != nil { 4104 return nil, err 4105 } 4106 // Get the receiving address. 4107 _, receiver, stamp, secretHash, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams) 4108 if err != nil { 4109 return nil, fmt.Errorf("error extracting swap addresses: %w", err) 4110 } 4111 4112 // If no tx data is provided, attempt to get the required data (the txOut) 4113 // from the wallet. If this is a full node wallet, a simple gettxout RPC is 4114 // sufficient with no pkScript or "since" time. If this is an SPV wallet, 4115 // only a confirmed counterparty contract can be located, and only one 4116 // within ContractSearchLimit. As such, this mode of operation is not 4117 // intended for normal server-coordinated operation. 4118 var tx *wire.MsgTx 4119 var txOut *wire.TxOut 4120 if len(txData) == 0 { 4121 // Fall back to gettxout, but we won't have the tx to rebroadcast. 4122 pkScript, _ := btc.scriptHashScript(contract) // pkScript and since time are unused if full node 4123 txOut, _, err = btc.node.getTxOut(txHash, vout, pkScript, time.Now().Add(-ContractSearchLimit)) 4124 if err != nil || txOut == nil { 4125 return nil, fmt.Errorf("error finding unspent contract: %s:%d : %w", txHash, vout, err) 4126 } 4127 } else { 4128 tx, err = btc.deserializeTx(txData) 4129 if err != nil { 4130 return nil, fmt.Errorf("coin not found, and error encountered decoding tx data: %v", err) 4131 } 4132 if len(tx.TxOut) <= int(vout) { 4133 return nil, fmt.Errorf("specified output %d not found in decoded tx %s", vout, txHash) 4134 } 4135 txOut = tx.TxOut[vout] 4136 } 4137 4138 // Check for standard P2SH. NOTE: btc.scriptHashScript(contract) should 4139 // equal txOut.PkScript. All we really get from the TxOut is the *value*. 4140 scriptClass, addrs, numReq, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams) 4141 if err != nil { 4142 return nil, fmt.Errorf("error extracting script addresses from '%x': %w", txOut.PkScript, err) 4143 } 4144 var contractHash []byte 4145 if btc.segwit { 4146 if scriptClass != txscript.WitnessV0ScriptHashTy { 4147 return nil, fmt.Errorf("unexpected script class. expected %s, got %s", 4148 txscript.WitnessV0ScriptHashTy, scriptClass) 4149 } 4150 h := sha256.Sum256(contract) 4151 contractHash = h[:] 4152 } else { 4153 if scriptClass != txscript.ScriptHashTy { 4154 return nil, fmt.Errorf("unexpected script class. expected %s, got %s", 4155 txscript.ScriptHashTy, scriptClass) 4156 } 4157 // Compare the contract hash to the P2SH address. 4158 contractHash = btcutil.Hash160(contract) 4159 } 4160 // These last two checks are probably overkill. 4161 if numReq != 1 { 4162 return nil, fmt.Errorf("unexpected number of signatures expected for P2SH script: %d", numReq) 4163 } 4164 if len(addrs) != 1 { 4165 return nil, fmt.Errorf("unexpected number of addresses for P2SH script: %d", len(addrs)) 4166 } 4167 4168 addr := addrs[0] 4169 if !bytes.Equal(contractHash, addr.ScriptAddress()) { 4170 return nil, fmt.Errorf("contract hash doesn't match script address. %x != %x", 4171 contractHash, addr.ScriptAddress()) 4172 } 4173 4174 // Broadcast the transaction, but do not block because this is not required 4175 // and does not affect the audit result. 4176 if rebroadcast && tx != nil { 4177 go func() { 4178 if hashSent, err := btc.node.sendRawTransaction(tx); err != nil { 4179 btc.log.Debugf("Rebroadcasting counterparty contract %v (THIS MAY BE NORMAL): %v", txHash, err) 4180 } else if !hashSent.IsEqual(txHash) { 4181 btc.log.Errorf("Counterparty contract %v was rebroadcast as %v!", txHash, hashSent) 4182 } 4183 }() 4184 } 4185 4186 addrStr, err := btc.stringAddr(receiver, btc.chainParams) 4187 if err != nil { 4188 btc.log.Errorf("Failed to stringify receiver address %v (default): %v", receiver, err) 4189 addrStr = receiver.String() // potentially misleading AuditInfo.Recipient 4190 } 4191 4192 return &asset.AuditInfo{ 4193 Coin: NewOutput(txHash, vout, uint64(txOut.Value)), 4194 Recipient: addrStr, 4195 Contract: contract, 4196 SecretHash: secretHash, 4197 Expiration: time.Unix(int64(stamp), 0).UTC(), 4198 }, nil 4199 } 4200 4201 // LockTimeExpired returns true if the specified locktime has expired, making it 4202 // possible to refund the locked coins. 4203 func (btc *baseWallet) LockTimeExpired(_ context.Context, lockTime time.Time) (bool, error) { 4204 medianTime, err := btc.node.medianTime() // TODO: pass ctx 4205 if err != nil { 4206 return false, fmt.Errorf("error getting median time: %w", err) 4207 } 4208 return medianTime.After(lockTime), nil 4209 } 4210 4211 // ContractLockTimeExpired returns true if the specified contract's locktime has 4212 // expired, making it possible to issue a Refund. 4213 func (btc *baseWallet) ContractLockTimeExpired(ctx context.Context, contract dex.Bytes) (bool, time.Time, error) { 4214 _, _, locktime, _, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams) 4215 if err != nil { 4216 return false, time.Time{}, fmt.Errorf("error extracting contract locktime: %w", err) 4217 } 4218 contractExpiry := time.Unix(int64(locktime), 0).UTC() 4219 expired, err := btc.LockTimeExpired(ctx, contractExpiry) 4220 if err != nil { 4221 return false, time.Time{}, err 4222 } 4223 return expired, contractExpiry, nil 4224 } 4225 4226 // FindRedemption watches for the input that spends the specified contract 4227 // coin, and returns the spending input and the contract's secret key when it 4228 // finds a spender. 4229 // 4230 // This method blocks until the redemption is found, an error occurs or the 4231 // provided context is canceled. 4232 func (btc *intermediaryWallet) FindRedemption(ctx context.Context, coinID, _ dex.Bytes) (redemptionCoin, secret dex.Bytes, err error) { 4233 return btc.rf.FindRedemption(ctx, coinID) 4234 } 4235 4236 // Refund revokes a contract. This can only be used after the time lock has 4237 // expired. This MUST return an asset.CoinNotFoundError error if the coin is 4238 // spent. 4239 // NOTE: The contract cannot be retrieved from the unspent coin info as the 4240 // wallet does not store it, even though it was known when the init transaction 4241 // was created. The client should store this information for persistence across 4242 // sessions. 4243 func (btc *baseWallet) Refund(coinID, contract dex.Bytes, feeRate uint64) (dex.Bytes, error) { 4244 txHash, vout, err := decodeCoinID(coinID) 4245 if err != nil { 4246 return nil, err 4247 } 4248 4249 pkScript, err := btc.scriptHashScript(contract) 4250 if err != nil { 4251 return nil, fmt.Errorf("error parsing pubkey script: %w", err) 4252 } 4253 4254 if feeRate == 0 { 4255 feeRate = btc.targetFeeRateWithFallback(2, 0) 4256 } 4257 4258 // TODO: I'd recommend not passing a pkScript without a limited startTime 4259 // to prevent potentially long searches. In this case though, the output 4260 // will be found in the wallet and won't need to be searched for, only 4261 // the spender search will be conducted using the pkScript starting from 4262 // the block containing the original tx. The script can be gotten from 4263 // the wallet tx though and used for the spender search, while not passing 4264 // a script here to ensure no attempt is made to find the output without 4265 // a limited startTime. 4266 utxo, _, err := btc.node.getTxOut(txHash, vout, pkScript, time.Time{}) 4267 if err != nil { 4268 return nil, fmt.Errorf("error finding unspent contract: %w", err) 4269 } 4270 if utxo == nil { 4271 return nil, asset.CoinNotFoundError // spent 4272 } 4273 msgTx, err := btc.refundTx(txHash, vout, contract, uint64(utxo.Value), nil, feeRate) 4274 if err != nil { 4275 return nil, fmt.Errorf("error creating refund tx: %w", err) 4276 } 4277 4278 refundHash, err := btc.broadcastTx(msgTx) 4279 if err != nil { 4280 return nil, fmt.Errorf("broadcastTx: %w", err) 4281 } 4282 4283 var fee uint64 4284 if len(msgTx.TxOut) > 0 { // something went very wrong if not true 4285 fee = uint64(utxo.Value - msgTx.TxOut[0].Value) 4286 } 4287 btc.addTxToHistory(&asset.WalletTransaction{ 4288 Type: asset.Refund, 4289 ID: refundHash.String(), 4290 Amount: uint64(utxo.Value), 4291 Fees: fee, 4292 }, refundHash, true) 4293 4294 return ToCoinID(refundHash, 0), nil 4295 } 4296 4297 // refundTx creates and signs a contract`s refund transaction. If refundAddr is 4298 // not supplied, one will be requested from the wallet. 4299 func (btc *baseWallet) refundTx(txHash *chainhash.Hash, vout uint32, contract dex.Bytes, val uint64, refundAddr btcutil.Address, feeRate uint64) (*wire.MsgTx, error) { 4300 sender, _, lockTime, _, err := dexbtc.ExtractSwapDetails(contract, btc.segwit, btc.chainParams) 4301 if err != nil { 4302 return nil, fmt.Errorf("error extracting swap addresses: %w", err) 4303 } 4304 4305 // Create the transaction that spends the contract. 4306 msgTx := wire.NewMsgTx(btc.txVersion()) 4307 msgTx.LockTime = uint32(lockTime) 4308 prevOut := wire.NewOutPoint(txHash, vout) 4309 txIn := wire.NewTxIn(prevOut, []byte{}, nil) 4310 // Enable the OP_CHECKLOCKTIMEVERIFY opcode to be used. 4311 // 4312 // https://github.com/bitcoin/bips/blob/master/bip-0125.mediawiki#Spending_wallet_policy 4313 txIn.Sequence = wire.MaxTxInSequenceNum - 1 4314 msgTx.AddTxIn(txIn) 4315 // Calculate fees and add the change output. 4316 4317 size := btc.calcTxSize(msgTx) 4318 4319 if btc.segwit { 4320 // Add the marker and flag weight too. 4321 witnessVBytes := uint64((dexbtc.RefundSigScriptSize + 2 + 3) / 4) 4322 size += witnessVBytes + dexbtc.P2WPKHOutputSize 4323 } else { 4324 size += dexbtc.RefundSigScriptSize + dexbtc.P2PKHOutputSize 4325 } 4326 4327 fee := feeRate * size // TODO: use btc.FeeRate in caller and fallback to nfo.MaxFeeRate 4328 if fee > val { 4329 return nil, fmt.Errorf("refund tx not worth the fees") 4330 } 4331 if refundAddr == nil { 4332 refundAddr, err = btc.node.externalAddress() 4333 if err != nil { 4334 return nil, fmt.Errorf("error getting new address from the wallet: %w", err) 4335 } 4336 } 4337 pkScript, err := txscript.PayToAddrScript(refundAddr) 4338 if err != nil { 4339 return nil, fmt.Errorf("error creating change script: %w", err) 4340 } 4341 txOut := wire.NewTxOut(int64(val-fee), pkScript) 4342 // One last check for dust. 4343 if btc.IsDust(txOut, feeRate) { 4344 return nil, fmt.Errorf("refund output is dust") 4345 } 4346 msgTx.AddTxOut(txOut) 4347 4348 if btc.segwit { 4349 sigHashes := txscript.NewTxSigHashes(msgTx, new(txscript.CannedPrevOutputFetcher)) 4350 refundSig, refundPubKey, err := btc.createWitnessSig(msgTx, 0, contract, sender, int64(val), sigHashes) 4351 if err != nil { 4352 return nil, fmt.Errorf("createWitnessSig: %w", err) 4353 } 4354 txIn.Witness = dexbtc.RefundP2WSHContract(contract, refundSig, refundPubKey) 4355 4356 } else { 4357 prevScript, err := btc.scriptHashScript(contract) 4358 if err != nil { 4359 return nil, fmt.Errorf("error constructing p2sh script: %w", err) 4360 } 4361 4362 refundSig, refundPubKey, err := btc.createSig(msgTx, 0, contract, sender, []int64{int64(val)}, [][]byte{prevScript}) 4363 if err != nil { 4364 return nil, fmt.Errorf("createSig: %w", err) 4365 } 4366 txIn.SignatureScript, err = dexbtc.RefundP2SHContract(contract, refundSig, refundPubKey) 4367 if err != nil { 4368 return nil, fmt.Errorf("RefundP2SHContract: %w", err) 4369 } 4370 } 4371 return msgTx, nil 4372 } 4373 4374 // DepositAddress returns an address for depositing funds into the 4375 // exchange wallet. 4376 func (btc *baseWallet) DepositAddress() (string, error) { 4377 addr, err := btc.node.externalAddress() 4378 if err != nil { 4379 return "", err 4380 } 4381 addrStr, err := btc.stringAddr(addr, btc.chainParams) 4382 if err != nil { 4383 return "", err 4384 } 4385 if btc.node.locked() { 4386 return addrStr, nil 4387 } 4388 4389 // If the wallet is unlocked, be extra cautious and ensure the wallet gave 4390 // us an address for which we can retrieve the private keys, regardless of 4391 // what ownsAddress would say. 4392 priv, err := btc.node.privKeyForAddress(addrStr) 4393 if err != nil { 4394 return "", fmt.Errorf("private key unavailable for address %v: %w", addrStr, err) 4395 } 4396 priv.Zero() 4397 return addrStr, nil 4398 } 4399 4400 // RedemptionAddress gets an address for use in redeeming the counterparty's 4401 // swap. This would be included in their swap initialization. 4402 func (btc *baseWallet) RedemptionAddress() (string, error) { 4403 return btc.recyclableAddress() 4404 } 4405 4406 // A recyclable address is a redemption or refund address that may be recycled 4407 // if unused. If already recycled addresses are available, one will be returned. 4408 func (btc *baseWallet) recyclableAddress() (string, error) { 4409 var returns []string 4410 defer btc.ar.ReturnAddresses(returns) 4411 for { 4412 addr := btc.ar.Address() 4413 if addr == "" { 4414 break 4415 } 4416 if owns, err := btc.OwnsDepositAddress(addr); owns { 4417 return addr, nil 4418 } else if err != nil { 4419 btc.log.Errorf("Error checking ownership of recycled address %q: %v", addr, err) 4420 returns = append(returns, addr) 4421 } 4422 } 4423 return btc.DepositAddress() 4424 } 4425 4426 // ReturnRefundContracts should be called with the Receipt.Contract() data for 4427 // any swaps that will not be refunded. 4428 func (btc *baseWallet) ReturnRefundContracts(contracts [][]byte) { 4429 addrs := make([]string, 0, len(contracts)) 4430 for _, c := range contracts { 4431 sender, _, _, _, err := dexbtc.ExtractSwapDetails(c, btc.segwit, btc.chainParams) 4432 if err != nil { 4433 btc.log.Errorf("Error extracting refund address from contract '%x': %v", c, err) 4434 continue 4435 } 4436 addr, err := btc.stringAddr(sender, btc.chainParams) 4437 if err != nil { 4438 btc.log.Errorf("Error stringifying address %q: %v", addr, err) 4439 continue 4440 } 4441 addrs = append(addrs, addr) 4442 } 4443 if len(addrs) > 0 { 4444 btc.ar.ReturnAddresses(addrs) 4445 } 4446 } 4447 4448 // ReturnRedemptionAddress accepts a Wallet.RedemptionAddress() if the address 4449 // will not be used. 4450 func (btc *baseWallet) ReturnRedemptionAddress(addr string) { 4451 btc.ar.ReturnAddresses([]string{addr}) 4452 } 4453 4454 // NewAddress returns a new address from the wallet. This satisfies the 4455 // NewAddresser interface. 4456 func (btc *baseWallet) NewAddress() (string, error) { 4457 return btc.DepositAddress() 4458 } 4459 4460 // Withdraw withdraws funds to the specified address. Fees are subtracted from 4461 // the value. feeRate is in units of sats/byte. 4462 // Withdraw satisfies asset.Withdrawer. 4463 func (btc *baseWallet) Withdraw(address string, value, feeRate uint64) (asset.Coin, error) { 4464 txHash, vout, sent, err := btc.send(address, value, btc.feeRateWithFallback(feeRate), true) 4465 if err != nil { 4466 return nil, err 4467 } 4468 return NewOutput(txHash, vout, sent), nil 4469 } 4470 4471 // Send sends the exact value to the specified address. This is different from 4472 // Withdraw, which subtracts the tx fees from the amount sent. feeRate is in 4473 // units of sats/byte. 4474 func (btc *baseWallet) Send(address string, value, feeRate uint64) (asset.Coin, error) { 4475 txHash, vout, sent, err := btc.send(address, value, btc.feeRateWithFallback(feeRate), false) 4476 if err != nil { 4477 return nil, err 4478 } 4479 return NewOutput(txHash, vout, sent), nil 4480 } 4481 4482 // SendTransaction broadcasts a valid fully-signed transaction. 4483 func (btc *baseWallet) SendTransaction(rawTx []byte) ([]byte, error) { 4484 msgTx, err := btc.deserializeTx(rawTx) 4485 if err != nil { 4486 return nil, err 4487 } 4488 4489 txHash, err := btc.node.sendRawTransaction(msgTx) 4490 if err != nil { 4491 return nil, err 4492 } 4493 4494 btc.markTxAsSubmitted(txHash) 4495 4496 return ToCoinID(txHash, 0), nil 4497 } 4498 4499 // ValidateSecret checks that the secret satisfies the contract. 4500 func (btc *baseWallet) ValidateSecret(secret, secretHash []byte) bool { 4501 h := sha256.Sum256(secret) 4502 return bytes.Equal(h[:], secretHash) 4503 } 4504 4505 // send the value to the address, with the given fee rate. If subtract is true, 4506 // the fees will be subtracted from the value. If false, the fees are in 4507 // addition to the value. feeRate is in units of sats/byte. 4508 func (btc *baseWallet) send(address string, val uint64, feeRate uint64, subtract bool) (*chainhash.Hash, uint32, uint64, error) { 4509 addr, err := btc.decodeAddr(address, btc.chainParams) 4510 if err != nil { 4511 return nil, 0, 0, fmt.Errorf("invalid address: %s", address) 4512 } 4513 var pay2script []byte 4514 if scripter, is := addr.(PaymentScripter); is { 4515 pay2script, err = scripter.PaymentScript() 4516 } else { 4517 pay2script, err = txscript.PayToAddrScript(addr) 4518 } 4519 if err != nil { 4520 return nil, 0, 0, fmt.Errorf("PayToAddrScript error: %w", err) 4521 } 4522 4523 baseSize := dexbtc.MinimumTxOverhead 4524 if btc.segwit { 4525 baseSize += dexbtc.P2WPKHOutputSize * 2 4526 } else { 4527 baseSize += dexbtc.P2PKHOutputSize * 2 4528 } 4529 4530 enough := sendEnough(val, feeRate, subtract, uint64(baseSize), btc.segwit, true) 4531 minConfs := uint32(0) 4532 coins, _, _, _, inputsSize, _, err := btc.cm.Fund(btc.bondReserves.Load(), minConfs, false, enough) 4533 if err != nil { 4534 return nil, 0, 0, fmt.Errorf("error funding transaction: %w", err) 4535 } 4536 4537 fundedTx, totalIn, _, err := btc.fundedTx(coins) 4538 if err != nil { 4539 return nil, 0, 0, fmt.Errorf("error adding inputs to transaction: %w", err) 4540 } 4541 4542 fees := feeRate * (inputsSize + uint64(baseSize)) 4543 var toSend uint64 4544 if subtract { 4545 toSend = val - fees 4546 } else { 4547 toSend = val 4548 } 4549 fundedTx.AddTxOut(wire.NewTxOut(int64(toSend), pay2script)) 4550 4551 changeAddr, err := btc.node.changeAddress() 4552 if err != nil { 4553 return nil, 0, 0, fmt.Errorf("error creating change address: %w", err) 4554 } 4555 4556 msgTx, err := btc.sendWithReturn(fundedTx, changeAddr, totalIn, toSend, feeRate) 4557 if err != nil { 4558 return nil, 0, 0, err 4559 } 4560 4561 txHash := btc.hashTx(msgTx) 4562 4563 var totalOut uint64 4564 for _, txOut := range msgTx.TxOut { 4565 totalOut += uint64(txOut.Value) 4566 } 4567 4568 selfSend, err := btc.OwnsDepositAddress(address) 4569 if err != nil { 4570 return nil, 0, 0, fmt.Errorf("error checking address ownership: %w", err) 4571 } 4572 txType := asset.Send 4573 if selfSend { 4574 txType = asset.SelfSend 4575 } 4576 4577 btc.addTxToHistory(&asset.WalletTransaction{ 4578 Type: txType, 4579 ID: txHash.String(), 4580 Amount: toSend, 4581 Fees: totalIn - totalOut, 4582 Recipient: &address, 4583 }, txHash, true) 4584 4585 return txHash, 0, toSend, nil 4586 } 4587 4588 // SwapConfirmations gets the number of confirmations for the specified swap 4589 // by first checking for a unspent output, and if not found, searching indexed 4590 // wallet transactions. 4591 func (btc *baseWallet) SwapConfirmations(_ context.Context, id dex.Bytes, contract dex.Bytes, startTime time.Time) (uint32, bool, error) { 4592 txHash, vout, err := decodeCoinID(id) 4593 if err != nil { 4594 return 0, false, err 4595 } 4596 pkScript, err := btc.scriptHashScript(contract) 4597 if err != nil { 4598 return 0, false, err 4599 } 4600 return btc.node.swapConfirmations(txHash, vout, pkScript, startTime) 4601 } 4602 4603 // RegFeeConfirmations gets the number of confirmations for the specified output 4604 // by first checking for a unspent output, and if not found, searching indexed 4605 // wallet transactions. 4606 func (btc *baseWallet) RegFeeConfirmations(_ context.Context, id dex.Bytes) (confs uint32, err error) { 4607 txHash, _, err := decodeCoinID(id) 4608 if err != nil { 4609 return 0, err 4610 } 4611 _, confs, err = btc.rawWalletTx(txHash) 4612 return 4613 } 4614 4615 func (btc *baseWallet) checkPeers() { 4616 numPeers, err := btc.node.peerCount() 4617 if err != nil { 4618 prevPeer := atomic.SwapUint32(&btc.lastPeerCount, 0) 4619 if prevPeer != 0 { 4620 btc.log.Errorf("Failed to get peer count: %v", err) 4621 btc.peersChange(0, err) 4622 } 4623 return 4624 } 4625 prevPeer := atomic.SwapUint32(&btc.lastPeerCount, numPeers) 4626 if prevPeer != numPeers { 4627 btc.peersChange(numPeers, nil) 4628 } 4629 } 4630 4631 func (btc *baseWallet) monitorPeers(ctx context.Context) { 4632 ticker := time.NewTicker(peerCountTicker) 4633 defer ticker.Stop() 4634 for { 4635 btc.checkPeers() 4636 4637 select { 4638 case <-ticker.C: 4639 case <-ctx.Done(): 4640 return 4641 } 4642 } 4643 } 4644 4645 // watchBlocks pings for new blocks and runs the tipChange callback function 4646 // when the block changes. 4647 func (btc *intermediaryWallet) watchBlocks(ctx context.Context) { 4648 ticker := time.NewTicker(blockTicker) 4649 defer ticker.Stop() 4650 4651 var walletBlock <-chan *BlockVector 4652 if notifier, isNotifier := btc.node.(tipNotifier); isNotifier { 4653 walletBlock = notifier.tipFeed() 4654 } 4655 4656 // A polledBlock is a block found during polling, but whose broadcast has 4657 // been queued in anticipation of a wallet notification. 4658 type polledBlock struct { 4659 *BlockVector 4660 queue *time.Timer 4661 } 4662 4663 // queuedBlock is the currently queued, polling-discovered block that will 4664 // be broadcast after a timeout if the wallet doesn't send the matching 4665 // notification. 4666 var queuedBlock *polledBlock 4667 4668 for { 4669 select { 4670 4671 // Poll for the block. If the wallet offers tip reports, delay reporting 4672 // the tip to give the wallet a moment to request and scan block data. 4673 case <-ticker.C: 4674 newTipHdr, err := btc.node.getBestBlockHeader() 4675 if err != nil { 4676 btc.log.Errorf("failed to get best block header from %s node: %v", btc.symbol, err) 4677 continue 4678 } 4679 newTipHash, err := chainhash.NewHashFromStr(newTipHdr.Hash) 4680 if err != nil { 4681 btc.log.Errorf("invalid best block hash from %s node: %v", btc.symbol, err) 4682 continue 4683 } 4684 4685 if queuedBlock != nil && *newTipHash == queuedBlock.BlockVector.Hash { 4686 continue 4687 } 4688 4689 btc.tipMtx.RLock() 4690 sameTip := btc.currentTip.Hash == *newTipHash 4691 btc.tipMtx.RUnlock() 4692 if sameTip { 4693 continue 4694 } 4695 4696 newTip := &BlockVector{newTipHdr.Height, *newTipHash} 4697 4698 // If the wallet is not offering tip reports, send this one right 4699 // away. 4700 if walletBlock == nil { 4701 btc.reportNewTip(ctx, newTip) 4702 } else { 4703 // Queue it for reporting, but don't send it right away. Give the 4704 // wallet a chance to provide their block update. SPV wallet may 4705 // need more time after storing the block header to fetch and 4706 // scan filters and issue the FilteredBlockConnected report. 4707 if queuedBlock != nil { 4708 queuedBlock.queue.Stop() 4709 } 4710 blockAllowance := walletBlockAllowance 4711 syncStatus, err := btc.node.syncStatus() 4712 if err != nil { 4713 btc.log.Errorf("Error retrieving sync status before queuing polled block: %v", err) 4714 } else if !syncStatus.Synced { 4715 blockAllowance *= 10 4716 } 4717 queuedBlock = &polledBlock{ 4718 BlockVector: newTip, 4719 queue: time.AfterFunc(blockAllowance, func() { 4720 if ss, _ := btc.SyncStatus(); ss != nil && ss.Synced { 4721 btc.log.Warnf("Reporting a block found in polling that the wallet apparently "+ 4722 "never reported: %d %s. If you see this message repeatedly, it may indicate "+ 4723 "an issue with the wallet.", newTip.Height, newTip.Hash) 4724 } 4725 btc.reportNewTip(ctx, newTip) 4726 }), 4727 } 4728 } 4729 4730 // Tip reports from the wallet are always sent, and we'll clear any 4731 // queued polled block that would appear to be superceded by this one. 4732 case walletTip := <-walletBlock: 4733 if queuedBlock != nil && walletTip.Height >= queuedBlock.Height { 4734 if !queuedBlock.queue.Stop() && walletTip.Hash == queuedBlock.Hash { 4735 continue 4736 } 4737 queuedBlock = nil 4738 } 4739 btc.reportNewTip(ctx, walletTip) 4740 4741 case <-ctx.Done(): 4742 return 4743 } 4744 4745 // Ensure context cancellation takes priority before the next iteration. 4746 if ctx.Err() != nil { 4747 return 4748 } 4749 } 4750 } 4751 4752 // reportNewTip sets the currentTip. The tipChange callback function is invoked 4753 // and RedemptionFinder is informed of the new block. 4754 func (btc *intermediaryWallet) reportNewTip(ctx context.Context, newTip *BlockVector) { 4755 btc.tipMtx.Lock() 4756 defer btc.tipMtx.Unlock() 4757 4758 prevTip := btc.currentTip 4759 btc.currentTip = newTip 4760 btc.log.Tracef("tip change: %d (%s) => %d (%s)", prevTip.Height, prevTip.Hash, newTip.Height, newTip.Hash) 4761 btc.emit.TipChange(uint64(newTip.Height)) 4762 4763 go btc.syncTxHistory(uint64(newTip.Height)) 4764 4765 btc.rf.ReportNewTip(ctx, prevTip, newTip) 4766 } 4767 4768 // sendWithReturn sends the unsigned transaction with an added output (unless 4769 // dust) for the change. 4770 func (btc *baseWallet) sendWithReturn(baseTx *wire.MsgTx, addr btcutil.Address, 4771 totalIn, totalOut, feeRate uint64) (*wire.MsgTx, error) { 4772 4773 signedTx, _, _, err := btc.signTxAndAddChange(baseTx, addr, totalIn, totalOut, feeRate) 4774 if err != nil { 4775 return nil, err 4776 } 4777 4778 _, err = btc.broadcastTx(signedTx) 4779 return signedTx, err 4780 } 4781 4782 // signTxAndAddChange signs the passed tx and adds a change output if the change 4783 // wouldn't be dust. Returns but does NOT broadcast the signed tx. 4784 func (btc *baseWallet) signTxAndAddChange(baseTx *wire.MsgTx, addr btcutil.Address, 4785 totalIn, totalOut, feeRate uint64) (*wire.MsgTx, *Output, uint64, error) { 4786 4787 makeErr := func(s string, a ...any) (*wire.MsgTx, *Output, uint64, error) { 4788 return nil, nil, 0, fmt.Errorf(s, a...) 4789 } 4790 4791 // Sign the transaction to get an initial size estimate and calculate whether 4792 // a change output would be dust. 4793 sigCycles := 1 4794 msgTx, err := btc.node.signTx(baseTx) 4795 if err != nil { 4796 return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) 4797 } 4798 vSize := btc.calcTxSize(msgTx) 4799 minFee := feeRate * vSize 4800 remaining := totalIn - totalOut 4801 if minFee > remaining { 4802 return makeErr("not enough funds to cover minimum fee rate. %.8f < %.8f, raw tx: %x", 4803 toBTC(totalIn), toBTC(minFee+totalOut), btc.wireBytes(baseTx)) 4804 } 4805 4806 // Create a change output. 4807 changeScript, err := txscript.PayToAddrScript(addr) 4808 if err != nil { 4809 return makeErr("error creating change script: %v", err) 4810 } 4811 changeFees := dexbtc.P2PKHOutputSize * feeRate 4812 if btc.segwit { 4813 changeFees = dexbtc.P2WPKHOutputSize * feeRate 4814 } 4815 changeIdx := len(baseTx.TxOut) 4816 changeOutput := wire.NewTxOut(int64(remaining-minFee-changeFees), changeScript) 4817 if changeFees+minFee > remaining { // Prevent underflow 4818 changeOutput.Value = 0 4819 } 4820 // If the change is not dust, recompute the signed txn size and iterate on 4821 // the fees vs. change amount. 4822 changeAdded := !btc.IsDust(changeOutput, feeRate) 4823 if changeAdded { 4824 // Add the change output. 4825 vSize0 := btc.calcTxSize(baseTx) 4826 baseTx.AddTxOut(changeOutput) 4827 changeSize := btc.calcTxSize(baseTx) - vSize0 // may be dexbtc.P2WPKHOutputSize 4828 addrStr, _ := btc.stringAddr(addr, btc.chainParams) // just for logging 4829 btc.log.Tracef("Change output size = %d, addr = %s", changeSize, addrStr) 4830 4831 vSize += changeSize 4832 fee := feeRate * vSize 4833 changeOutput.Value = int64(remaining - fee) 4834 // Find the best fee rate by closing in on it in a loop. 4835 tried := map[uint64]bool{} 4836 for { 4837 // Sign the transaction with the change output and compute new size. 4838 sigCycles++ 4839 msgTx, err = btc.node.signTx(baseTx) 4840 if err != nil { 4841 return makeErr("signing error: %v, raw tx: %x", err, btc.wireBytes(baseTx)) 4842 } 4843 vSize = btc.calcTxSize(msgTx) // recompute the size with new tx signature 4844 reqFee := feeRate * vSize 4845 if reqFee > remaining { 4846 // I can't imagine a scenario where this condition would be true, but 4847 // I'd hate to be wrong. 4848 btc.log.Errorf("reached the impossible place. in = %.8f, out = %.8f, reqFee = %.8f, lastFee = %.8f, raw tx = %x, vSize = %d, feeRate = %d", 4849 toBTC(totalIn), toBTC(totalOut), toBTC(reqFee), toBTC(fee), btc.wireBytes(msgTx), vSize, feeRate) 4850 return makeErr("change error") 4851 } 4852 if fee == reqFee || (fee > reqFee && tried[reqFee]) { 4853 // If a lower fee appears available, but it's already been attempted and 4854 // had a longer serialized size, the current fee is likely as good as 4855 // it gets. 4856 break 4857 } 4858 4859 // We must have some room for improvement. 4860 tried[fee] = true 4861 fee = reqFee 4862 changeOutput.Value = int64(remaining - fee) 4863 if btc.IsDust(changeOutput, feeRate) { 4864 // Another condition that should be impossible, but check anyway in case 4865 // the maximum fee was underestimated causing the first check to be 4866 // missed. 4867 btc.log.Errorf("reached the impossible place. in = %.8f, out = %.8f, reqFee = %.8f, lastFee = %.8f, raw tx = %x", 4868 toBTC(totalIn), toBTC(totalOut), toBTC(reqFee), toBTC(fee), btc.wireBytes(msgTx)) 4869 return makeErr("dust error") 4870 } 4871 continue 4872 } 4873 4874 totalOut += uint64(changeOutput.Value) 4875 } else { 4876 btc.log.Debugf("Foregoing change worth up to %v in tx %v because it is dust", 4877 changeOutput.Value, btc.hashTx(msgTx)) 4878 } 4879 4880 txHash := btc.hashTx(msgTx) 4881 4882 fee := totalIn - totalOut 4883 actualFeeRate := fee / vSize 4884 btc.log.Debugf("%d signature cycles to converge on fees for tx %s: "+ 4885 "min rate = %d, actual fee rate = %d (%v for %v bytes), change = %v", 4886 sigCycles, txHash, feeRate, actualFeeRate, fee, vSize, changeAdded) 4887 4888 var change *Output 4889 if changeAdded { 4890 change = NewOutput(txHash, uint32(changeIdx), uint64(changeOutput.Value)) 4891 } 4892 4893 return msgTx, change, fee, nil 4894 } 4895 4896 func (btc *baseWallet) broadcastTx(signedTx *wire.MsgTx) (*chainhash.Hash, error) { 4897 txHash, err := btc.node.sendRawTransaction(signedTx) 4898 if err != nil { 4899 return nil, fmt.Errorf("sendrawtx error: %v, raw tx: %x", err, btc.wireBytes(signedTx)) 4900 } 4901 checkHash := btc.hashTx(signedTx) 4902 if *txHash != *checkHash { 4903 return nil, fmt.Errorf("transaction sent, but received unexpected transaction ID back from RPC server. "+ 4904 "expected %s, got %s. raw tx: %x", checkHash, *txHash, btc.wireBytes(signedTx)) 4905 } 4906 return txHash, nil 4907 } 4908 4909 // createSig creates and returns the serialized raw signature and compressed 4910 // pubkey for a transaction input signature. 4911 func (btc *baseWallet) createSig(tx *wire.MsgTx, idx int, pkScript []byte, addr btcutil.Address, vals []int64, pkScripts [][]byte) (sig, pubkey []byte, err error) { 4912 addrStr, err := btc.stringAddr(addr, btc.chainParams) 4913 if err != nil { 4914 return nil, nil, err 4915 } 4916 4917 privKey, err := btc.node.privKeyForAddress(addrStr) 4918 if err != nil { 4919 return nil, nil, err 4920 } 4921 defer privKey.Zero() 4922 4923 sig, err = btc.signNonSegwit(tx, idx, pkScript, txscript.SigHashAll, privKey, vals, pkScripts) 4924 if err != nil { 4925 return nil, nil, err 4926 } 4927 4928 return sig, privKey.PubKey().SerializeCompressed(), nil 4929 } 4930 4931 // createWitnessSig creates and returns a signature for the witness of a segwit 4932 // input and the pubkey associated with the address. 4933 func (btc *baseWallet) createWitnessSig(tx *wire.MsgTx, idx int, pkScript []byte, 4934 addr btcutil.Address, val int64, sigHashes *txscript.TxSigHashes) (sig, pubkey []byte, err error) { 4935 addrStr, err := btc.stringAddr(addr, btc.chainParams) 4936 if err != nil { 4937 return nil, nil, err 4938 } 4939 privKey, err := btc.node.privKeyForAddress(addrStr) 4940 if err != nil { 4941 return nil, nil, err 4942 } 4943 defer privKey.Zero() 4944 sig, err = txscript.RawTxInWitnessSignature(tx, sigHashes, idx, val, 4945 pkScript, txscript.SigHashAll, privKey) 4946 4947 if err != nil { 4948 return nil, nil, err 4949 } 4950 return sig, privKey.PubKey().SerializeCompressed(), nil 4951 } 4952 4953 // ValidateAddress checks that the provided address is valid. 4954 func (btc *baseWallet) ValidateAddress(address string) bool { 4955 _, err := btc.decodeAddr(address, btc.chainParams) 4956 return err == nil 4957 } 4958 4959 // dummyP2PKHScript only has to be a valid 25-byte pay-to-pubkey-hash pkScript 4960 // for EstimateSendTxFee when an empty or invalid address is provided. 4961 var dummyP2PKHScript = []byte{0x76, 0xa9, 0x14, 0xe4, 0x28, 0x61, 0xa, 4962 0xfc, 0xd0, 0x4e, 0x21, 0x94, 0xf7, 0xe2, 0xcc, 0xf8, 4963 0x58, 0x7a, 0xc9, 0xe7, 0x2c, 0x79, 0x7b, 0x88, 0xac, 4964 } 4965 4966 // EstimateSendTxFee returns a tx fee estimate for sending or withdrawing the 4967 // provided amount using the provided feeRate. 4968 func (btc *intermediaryWallet) EstimateSendTxFee(address string, sendAmount, feeRate uint64, subtract, _ bool) (fee uint64, isValidAddress bool, err error) { 4969 if sendAmount == 0 { 4970 return 0, false, fmt.Errorf("cannot check fee: send amount = 0") 4971 } 4972 4973 var pkScript []byte 4974 if addr, err := btc.decodeAddr(address, btc.chainParams); err == nil { 4975 pkScript, err = txscript.PayToAddrScript(addr) 4976 if err != nil { 4977 return 0, false, fmt.Errorf("error generating pubkey script: %w", err) 4978 } 4979 isValidAddress = true 4980 } else { 4981 // use a dummy 25-byte p2pkh script 4982 pkScript = dummyP2PKHScript 4983 } 4984 4985 wireOP := wire.NewTxOut(int64(sendAmount), pkScript) 4986 if dexbtc.IsDust(wireOP, feeRate) { 4987 return 0, false, errors.New("output value is dust") 4988 } 4989 4990 tx := wire.NewMsgTx(btc.txVersion()) 4991 tx.AddTxOut(wireOP) 4992 fee, err = btc.txFeeEstimator.estimateSendTxFee(tx, btc.feeRateWithFallback(feeRate), subtract) 4993 if err != nil { 4994 return 0, false, err 4995 } 4996 return fee, isValidAddress, nil 4997 } 4998 4999 // StandardSendFees returns the fees for a simple send tx with one input and two 5000 // outputs. 5001 func (btc *baseWallet) StandardSendFee(feeRate uint64) uint64 { 5002 var sz uint64 = dexbtc.MinimumTxOverhead 5003 if btc.segwit { 5004 inputSize := dexbtc.RedeemP2WPKHInputSize + uint64((dexbtc.RedeemP2PKSigScriptSize+2+3)/4) 5005 sz += inputSize + dexbtc.P2WPKHOutputSize*2 5006 } else { 5007 sz += dexbtc.RedeemP2PKHInputSize + dexbtc.P2PKHOutputSize*2 5008 } 5009 return feeRate * sz 5010 } 5011 5012 func (btc *baseWallet) SetBondReserves(reserves uint64) { 5013 btc.bondReserves.Store(reserves) 5014 } 5015 5016 func bondPushData(ver uint16, acctID []byte, lockTimeSec int64, pkh []byte) []byte { 5017 pushData := make([]byte, 2+len(acctID)+4+20) 5018 var offset int 5019 binary.BigEndian.PutUint16(pushData[offset:], ver) 5020 offset += 2 5021 copy(pushData[offset:], acctID[:]) 5022 offset += len(acctID) 5023 binary.BigEndian.PutUint32(pushData[offset:], uint32(lockTimeSec)) 5024 offset += 4 5025 copy(pushData[offset:], pkh) 5026 return pushData 5027 } 5028 5029 func bondPushDataScript(ver uint16, acctID []byte, lockTimeSec int64, pkh []byte) ([]byte, error) { 5030 return txscript.NewScriptBuilder(). 5031 AddOp(txscript.OP_RETURN). 5032 AddData(bondPushData(ver, acctID, lockTimeSec, pkh)). 5033 Script() 5034 } 5035 5036 // MakeBondTx creates a time-locked fidelity bond transaction. The V0 5037 // transaction has two required outputs: 5038 // 5039 // Output 0 is a the time-locked bond output of type P2SH with the provided 5040 // value. The redeem script looks similar to the refund path of an atomic swap 5041 // script, but with a pubkey hash: 5042 // 5043 // <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash[20]> OP_EQUALVERIFY OP_CHECKSIG 5044 // 5045 // The pubkey referenced by the script is provided by the caller. 5046 // 5047 // Output 1 is a DEX Account commitment. This is an OP_RETURN output that 5048 // references the provided account ID. 5049 // 5050 // OP_RETURN <2-byte version> <32-byte account ID> <4-byte locktime> <20-byte pubkey hash> 5051 // 5052 // Having the account ID in the raw allows the txn alone to identify the account 5053 // without the bond output's redeem script. 5054 // 5055 // Output 2 is change, if any. 5056 // 5057 // The bond output's redeem script, which is needed to spend the bond output, is 5058 // returned as the Data field of the Bond. The bond output pays to a pubkeyhash 5059 // script for a wallet address. Bond.RedeemTx is a backup transaction that 5060 // spends the bond output after lockTime passes, paying to an address for the 5061 // current underlying wallet; the bond private key should normally be used to 5062 // author a new transaction paying to a new address instead. 5063 func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time.Time, bondKey *secp256k1.PrivateKey, acctID []byte) (*asset.Bond, func(), error) { 5064 if ver != 0 { 5065 return nil, nil, errors.New("only version 0 bonds supported") 5066 } 5067 if until := time.Until(lockTime); until >= 365*12*time.Hour /* ~6 months */ { 5068 return nil, nil, fmt.Errorf("that lock time is nuts: %v", lockTime) 5069 } else if until < 0 { 5070 return nil, nil, fmt.Errorf("that lock time is already passed: %v", lockTime) 5071 } 5072 5073 pk := bondKey.PubKey().SerializeCompressed() 5074 pkh := btcutil.Hash160(pk) 5075 5076 feeRate = btc.feeRateWithFallback(feeRate) 5077 baseTx := wire.NewMsgTx(btc.txVersion()) 5078 5079 // TL output. 5080 lockTimeSec := lockTime.Unix() 5081 if lockTimeSec >= dexbtc.MaxCLTVScriptNum || lockTimeSec <= 0 { 5082 return nil, nil, fmt.Errorf("invalid lock time %v", lockTime) 5083 } 5084 bondScript, err := dexbtc.MakeBondScript(ver, uint32(lockTimeSec), pkh) 5085 if err != nil { 5086 return nil, nil, fmt.Errorf("failed to build bond output redeem script: %w", err) 5087 } 5088 pkScript, err := btc.scriptHashScript(bondScript) 5089 if err != nil { 5090 return nil, nil, fmt.Errorf("error constructing p2sh script: %v", err) 5091 } 5092 txOut := wire.NewTxOut(int64(amt), pkScript) 5093 if btc.IsDust(txOut, feeRate) { 5094 return nil, nil, fmt.Errorf("bond output value of %d (fee rate %d) is dust", amt, feeRate) 5095 } 5096 baseTx.AddTxOut(txOut) 5097 5098 // Acct ID commitment and bond details output, v0. The integers are encoded 5099 // with big-endian byte order and a fixed number of bytes, unlike in Script, 5100 // for natural visual inspection of the version and lock time. 5101 commitPkScript, err := bondPushDataScript(ver, acctID, lockTimeSec, pkh) 5102 if err != nil { 5103 return nil, nil, fmt.Errorf("failed to build acct commit output script: %w", err) 5104 } 5105 acctOut := wire.NewTxOut(0, commitPkScript) // value zero 5106 baseTx.AddTxOut(acctOut) 5107 5108 baseSize := uint32(baseTx.SerializeSize()) 5109 if btc.segwit { 5110 baseSize += dexbtc.P2WPKHOutputSize 5111 } else { 5112 baseSize += dexbtc.P2PKHOutputSize 5113 } 5114 5115 const subtract = false 5116 coins, _, _, _, _, _, err := btc.cm.Fund(0, 0, true, sendEnough(amt, feeRate, subtract, uint64(baseSize), btc.segwit, true)) 5117 if err != nil { 5118 return nil, nil, fmt.Errorf("failed to fund bond tx: %w", err) 5119 } 5120 5121 var txIDToRemoveFromHistory *chainhash.Hash // will be non-nil if tx was added to history 5122 5123 abandon := func() { // if caller does not broadcast, or we fail in this method 5124 err := btc.ReturnCoins(coins) 5125 if err != nil { 5126 btc.log.Errorf("error returning coins for unused bond tx: %v", coins) 5127 } 5128 if txIDToRemoveFromHistory != nil { 5129 btc.removeTxFromHistory(txIDToRemoveFromHistory) 5130 } 5131 } 5132 5133 var success bool 5134 defer func() { 5135 if !success { 5136 abandon() 5137 } 5138 }() 5139 5140 totalIn, _, err := btc.addInputsToTx(baseTx, coins) 5141 if err != nil { 5142 return nil, nil, fmt.Errorf("failed to add inputs to bond tx: %w", err) 5143 } 5144 5145 changeAddr, err := btc.node.changeAddress() 5146 if err != nil { 5147 return nil, nil, fmt.Errorf("error creating change address: %w", err) 5148 } 5149 signedTx, _, fee, err := btc.signTxAndAddChange(baseTx, changeAddr, totalIn, amt, feeRate) 5150 if err != nil { 5151 return nil, nil, fmt.Errorf("failed to sign bond tx: %w", err) 5152 } 5153 5154 txid := btc.hashTx(signedTx) 5155 5156 signedTxBytes, err := btc.serializeTx(signedTx) 5157 if err != nil { 5158 return nil, nil, err 5159 } 5160 unsignedTxBytes, err := btc.serializeTx(baseTx) 5161 if err != nil { 5162 return nil, nil, err 5163 } 5164 5165 // Prep the redeem / refund tx. 5166 redeemMsgTx, err := btc.makeBondRefundTxV0(txid, 0, amt, bondScript, bondKey, feeRate) 5167 if err != nil { 5168 return nil, nil, fmt.Errorf("unable to create bond redemption tx: %w", err) 5169 } 5170 redeemTx, err := btc.serializeTx(redeemMsgTx) 5171 if err != nil { 5172 return nil, nil, fmt.Errorf("failed to serialize bond redemption tx: %w", err) 5173 } 5174 5175 bond := &asset.Bond{ 5176 Version: ver, 5177 AssetID: btc.cloneParams.AssetID, 5178 Amount: amt, 5179 CoinID: ToCoinID(txid, 0), 5180 Data: bondScript, 5181 SignedTx: signedTxBytes, 5182 UnsignedTx: unsignedTxBytes, 5183 RedeemTx: redeemTx, 5184 } 5185 success = true 5186 5187 btc.addTxToHistory(&asset.WalletTransaction{ 5188 Type: asset.CreateBond, 5189 ID: txid.String(), 5190 Amount: amt, 5191 Fees: fee, 5192 BondInfo: &asset.BondTxInfo{ 5193 AccountID: acctID, 5194 LockTime: uint64(lockTimeSec), 5195 BondID: pkh, 5196 }, 5197 }, txid, false) 5198 5199 txIDToRemoveFromHistory = txid 5200 5201 return bond, abandon, nil 5202 } 5203 5204 func (btc *baseWallet) makeBondRefundTxV0(txid *chainhash.Hash, vout uint32, amt uint64, 5205 script []byte, priv *secp256k1.PrivateKey, feeRate uint64) (*wire.MsgTx, error) { 5206 lockTime, pkhPush, err := dexbtc.ExtractBondDetailsV0(0, script) 5207 if err != nil { 5208 return nil, err 5209 } 5210 5211 pk := priv.PubKey().SerializeCompressed() 5212 pkh := btcutil.Hash160(pk) 5213 if !bytes.Equal(pkh, pkhPush) { 5214 return nil, fmt.Errorf("incorrect private key to spend the bond output") 5215 } 5216 5217 msgTx := wire.NewMsgTx(btc.txVersion()) 5218 // Transaction LockTime must be <= spend time, and >= the CLTV lockTime, so 5219 // we use exactly the CLTV's value. This limits the CLTV value to 32-bits. 5220 msgTx.LockTime = lockTime 5221 bondPrevOut := wire.NewOutPoint(txid, vout) 5222 txIn := wire.NewTxIn(bondPrevOut, []byte{}, nil) 5223 txIn.Sequence = wire.MaxTxInSequenceNum - 1 // not finalized, do not disable cltv 5224 msgTx.AddTxIn(txIn) 5225 5226 // Calculate fees and add the refund output. 5227 size := btc.calcTxSize(msgTx) 5228 if btc.segwit { 5229 witnessVBytes := (dexbtc.RedeemBondSigScriptSize + 2 + 3) / 4 5230 size += uint64(witnessVBytes) + dexbtc.P2WPKHOutputSize 5231 } else { 5232 size += dexbtc.RedeemBondSigScriptSize + dexbtc.P2PKHOutputSize 5233 } 5234 fee := feeRate * size 5235 if fee > amt { 5236 return nil, fmt.Errorf("irredeemable bond at fee rate %d atoms/byte", feeRate) 5237 } 5238 5239 // Add the refund output. 5240 redeemAddr, err := btc.node.externalAddress() 5241 if err != nil { 5242 return nil, fmt.Errorf("error creating change address: %w", err) 5243 } 5244 redeemPkScript, err := txscript.PayToAddrScript(redeemAddr) 5245 if err != nil { 5246 return nil, fmt.Errorf("error creating pubkey script: %w", err) 5247 } 5248 redeemTxOut := wire.NewTxOut(int64(amt-fee), redeemPkScript) 5249 if btc.IsDust(redeemTxOut, feeRate) { // hard to imagine 5250 return nil, fmt.Errorf("bond redeem output (amt = %d, feeRate = %d, outputSize = %d) is dust", amt, feeRate, redeemTxOut.SerializeSize()) 5251 } 5252 msgTx.AddTxOut(redeemTxOut) 5253 5254 if btc.segwit { 5255 sigHashes := txscript.NewTxSigHashes(msgTx, new(txscript.CannedPrevOutputFetcher)) 5256 sig, err := txscript.RawTxInWitnessSignature(msgTx, sigHashes, 0, int64(amt), 5257 script, txscript.SigHashAll, priv) 5258 if err != nil { 5259 return nil, err 5260 } 5261 txIn.Witness = dexbtc.RefundBondScriptSegwit(script, sig, pk) 5262 } else { 5263 prevPkScript, err := btc.scriptHashScript(script) // P2SH: OP_HASH160 <script hash> OP_EQUAL 5264 if err != nil { 5265 return nil, fmt.Errorf("error constructing p2sh script: %w", err) 5266 } 5267 sig, err := btc.signNonSegwit(msgTx, 0, script, txscript.SigHashAll, priv, []int64{int64(amt)}, [][]byte{prevPkScript}) 5268 if err != nil { 5269 return nil, err 5270 } 5271 txIn.SignatureScript, err = dexbtc.RefundBondScript(script, sig, pk) 5272 if err != nil { 5273 return nil, fmt.Errorf("RefundBondScript: %w", err) 5274 } 5275 } 5276 5277 return msgTx, nil 5278 } 5279 5280 // RefundBond refunds a bond output to a new wallet address given the redeem 5281 // script and private key. After broadcasting, the output paying to the wallet 5282 // is returned. 5283 func (btc *baseWallet) RefundBond(ctx context.Context, ver uint16, coinID, script []byte, amt uint64, privKey *secp256k1.PrivateKey) (asset.Coin, error) { 5284 if ver != 0 { 5285 return nil, errors.New("only version 0 bonds supported") 5286 } 5287 lockTime, pkhPush, err := dexbtc.ExtractBondDetailsV0(0, script) 5288 if err != nil { 5289 return nil, err 5290 } 5291 txHash, vout, err := decodeCoinID(coinID) 5292 if err != nil { 5293 return nil, err 5294 } 5295 feeRate := btc.targetFeeRateWithFallback(2, 0) 5296 5297 msgTx, err := btc.makeBondRefundTxV0(txHash, vout, amt, script, privKey, feeRate) 5298 if err != nil { 5299 return nil, err 5300 } 5301 5302 _, err = btc.node.sendRawTransaction(msgTx) 5303 if err != nil { 5304 return nil, fmt.Errorf("error sending refund bond transaction: %w", err) 5305 } 5306 5307 txID := btc.hashTx(msgTx) 5308 var fees uint64 5309 if len(msgTx.TxOut) == 1 { 5310 fees = amt - uint64(msgTx.TxOut[0].Value) 5311 } 5312 btc.addTxToHistory(&asset.WalletTransaction{ 5313 Type: asset.RedeemBond, 5314 ID: txID.String(), 5315 Amount: amt, 5316 Fees: fees, 5317 BondInfo: &asset.BondTxInfo{ 5318 LockTime: uint64(lockTime), 5319 BondID: pkhPush, 5320 }, 5321 }, txID, true) 5322 5323 return NewOutput(txHash, 0, uint64(msgTx.TxOut[0].Value)), nil 5324 } 5325 5326 func (btc *baseWallet) decodeV0BondTx(msgTx *wire.MsgTx, txHash *chainhash.Hash, coinID []byte) (*asset.BondDetails, error) { 5327 if len(msgTx.TxOut) < 2 { 5328 return nil, fmt.Errorf("tx %s is not a v0 bond transaction: too few outputs", txHash) 5329 } 5330 _, lockTime, pkh, err := dexbtc.ExtractBondCommitDataV0(0, msgTx.TxOut[1].PkScript) 5331 if err != nil { 5332 return nil, fmt.Errorf("unable to extract bond commitment details from output 1 of %s: %v", txHash, err) 5333 } 5334 // Sanity check. 5335 bondScript, err := dexbtc.MakeBondScript(0, lockTime, pkh[:]) 5336 if err != nil { 5337 return nil, fmt.Errorf("failed to build bond output redeem script: %w", err) 5338 } 5339 pkScript, err := btc.scriptHashScript(bondScript) 5340 if err != nil { 5341 return nil, fmt.Errorf("error constructing p2sh script: %v", err) 5342 } 5343 if !bytes.Equal(pkScript, msgTx.TxOut[0].PkScript) { 5344 return nil, fmt.Errorf("bond script does not match commit data for %s: %x != %x", 5345 txHash, bondScript, msgTx.TxOut[0].PkScript) 5346 } 5347 return &asset.BondDetails{ 5348 Bond: &asset.Bond{ 5349 Version: 0, 5350 AssetID: btc.cloneParams.AssetID, 5351 Amount: uint64(msgTx.TxOut[0].Value), 5352 CoinID: coinID, 5353 Data: bondScript, 5354 // 5355 // SignedTx and UnsignedTx not populated because this is 5356 // an already posted bond and these fields are no longer used. 5357 // SignedTx, UnsignedTx []byte 5358 // 5359 // RedeemTx cannot be populated because we do not have 5360 // the private key that only core knows. Core will need 5361 // the BondPKH to determine what the private key was. 5362 // RedeemTx []byte 5363 }, 5364 LockTime: time.Unix(int64(lockTime), 0), 5365 CheckPrivKey: func(bondKey *secp256k1.PrivateKey) bool { 5366 pk := bondKey.PubKey().SerializeCompressed() 5367 pkhB := btcutil.Hash160(pk) 5368 return bytes.Equal(pkh[:], pkhB) 5369 }, 5370 }, nil 5371 } 5372 5373 // FindBond finds the bond with coinID and returns the values used to create it. 5374 func (btc *baseWallet) FindBond(_ context.Context, coinID []byte, _ time.Time) (bond *asset.BondDetails, err error) { 5375 txHash, vout, err := decodeCoinID(coinID) 5376 if err != nil { 5377 return nil, err 5378 } 5379 5380 // If the bond was funded by this wallet or had a change output paying 5381 // to this wallet, it should be found here. 5382 tx, err := btc.node.getWalletTransaction(txHash) 5383 if err != nil { 5384 return nil, fmt.Errorf("did not find the bond output %v:%d", txHash, vout) 5385 } 5386 msgTx, err := btc.deserializeTx(tx.Bytes) 5387 if err != nil { 5388 return nil, fmt.Errorf("invalid hex for tx %s: %v", txHash, err) 5389 } 5390 return btc.decodeV0BondTx(msgTx, txHash, coinID) 5391 } 5392 5393 // FindBond finds the bond with coinID and returns the values used to create it. 5394 // The intermediate wallet is able to brute force finding blocks. 5395 func (btc *intermediaryWallet) FindBond( 5396 ctx context.Context, 5397 coinID []byte, 5398 searchUntil time.Time, 5399 ) (bond *asset.BondDetails, err error) { 5400 5401 txHash, vout, err := decodeCoinID(coinID) 5402 if err != nil { 5403 return nil, err 5404 } 5405 5406 // If the bond was funded by this wallet or had a change output paying 5407 // to this wallet, it should be found here. 5408 tx, err := btc.node.getWalletTransaction(txHash) 5409 if err == nil { 5410 msgTx, err := btc.deserializeTx(tx.Bytes) 5411 if err != nil { 5412 return nil, fmt.Errorf("invalid hex for tx %s: %v", txHash, err) 5413 } 5414 return btc.decodeV0BondTx(msgTx, txHash, coinID) 5415 } 5416 if !errors.Is(err, asset.CoinNotFoundError) { 5417 btc.log.Warnf("Unexpected error looking up bond output %v:%d", txHash, vout) 5418 } 5419 5420 // The bond was not funded by this wallet or had no change output when 5421 // restored from seed. This is not a problem. However, we are unable to 5422 // use filters because we don't know any output scripts. Brute force 5423 // finding the transaction. 5424 bestBlockHdr, err := btc.node.getBestBlockHeader() 5425 if err != nil { 5426 return nil, fmt.Errorf("unable to get best hash: %v", err) 5427 } 5428 blockHash, err := chainhash.NewHashFromStr(bestBlockHdr.Hash) 5429 if err != nil { 5430 return nil, fmt.Errorf("invalid best block hash from %s node: %v", btc.symbol, err) 5431 } 5432 var ( 5433 blk *wire.MsgBlock 5434 msgTx *wire.MsgTx 5435 ) 5436 out: 5437 for { 5438 if err := ctx.Err(); err != nil { 5439 return nil, fmt.Errorf("bond search stopped: %w", err) 5440 } 5441 blk, err = btc.tipRedeemer.getBlock(*blockHash) 5442 if err != nil { 5443 return nil, fmt.Errorf("error retrieving block %s: %w", blockHash, err) 5444 } 5445 if blk.Header.Timestamp.Before(searchUntil) { 5446 return nil, fmt.Errorf("searched blocks until %v but did not find the bond tx %s", searchUntil, txHash) 5447 } 5448 for _, tx := range blk.Transactions { 5449 if tx.TxHash() == *txHash { 5450 btc.log.Debugf("Found mined tx %s in block %s.", txHash, blk.BlockHash()) 5451 msgTx = tx 5452 break out 5453 } 5454 } 5455 blockHash = &blk.Header.PrevBlock 5456 if blockHash == nil { 5457 return nil, fmt.Errorf("did not find the bond output %v:%d", txHash, vout) 5458 } 5459 } 5460 return btc.decodeV0BondTx(msgTx, txHash, coinID) 5461 } 5462 5463 // BondsFeeBuffer suggests how much extra may be required for the transaction 5464 // fees part of required bond reserves when bond rotation is enabled. The 5465 // provided fee rate may be zero, in which case the wallet will use it's own 5466 // estimate or fallback value. 5467 func (btc *baseWallet) BondsFeeBuffer(feeRate uint64) uint64 { 5468 if feeRate == 0 { 5469 feeRate = btc.targetFeeRateWithFallback(1, 0) 5470 } 5471 feeRate *= 2 // double the current fee rate estimate so this fee buffer does not get stale too quickly 5472 return bondsFeeBuffer(btc.segwit, feeRate) 5473 } 5474 5475 // FundMultiOrder funds multiple orders in one shot. MaxLock is the maximum 5476 // amount that the wallet can lock for these orders. If maxLock == 0, then 5477 // there is no limit. An error is returned if the wallet does not have enough 5478 // available balance to fund each of the orders, however, if splitting is 5479 // not enabled and all of the orders cannot be funded due to mismatches in 5480 // UTXO sizes, the orders that can be funded are funded. It will fail on the 5481 // first order that cannot be funded. The returned values will always be in 5482 // the same order as the Values in the parameter. If the length of the returned 5483 // orders is shorter than what was passed in, it means that the orders at the 5484 // end of the list were unable to be funded. 5485 func (btc *baseWallet) FundMultiOrder(mo *asset.MultiOrder, maxLock uint64) ([]asset.Coins, [][]dex.Bytes, uint64, error) { 5486 btc.log.Debugf("Attempting to fund a multi-order for %s, maxFeeRate = %d", btc.symbol, mo.MaxFeeRate) 5487 5488 var totalRequiredForOrders uint64 5489 var swapInputSize uint64 5490 if btc.segwit { 5491 swapInputSize = dexbtc.RedeemP2WPKHInputTotalSize 5492 } else { 5493 swapInputSize = dexbtc.RedeemP2PKHInputSize 5494 } 5495 for _, value := range mo.Values { 5496 if value.Value == 0 { 5497 return nil, nil, 0, fmt.Errorf("cannot fund value = 0") 5498 } 5499 if value.MaxSwapCount == 0 { 5500 return nil, nil, 0, fmt.Errorf("cannot fund zero-lot order") 5501 } 5502 req := calc.RequiredOrderFunds(value.Value, swapInputSize, value.MaxSwapCount, 5503 btc.initTxSizeBase, btc.initTxSize, mo.MaxFeeRate) 5504 totalRequiredForOrders += req 5505 } 5506 5507 if maxLock < totalRequiredForOrders && maxLock != 0 { 5508 return nil, nil, 0, fmt.Errorf("maxLock < totalRequiredForOrders (%d < %d)", maxLock, totalRequiredForOrders) 5509 } 5510 5511 if mo.FeeSuggestion > mo.MaxFeeRate { 5512 return nil, nil, 0, fmt.Errorf("fee suggestion %d > max fee rate %d", mo.FeeSuggestion, mo.MaxFeeRate) 5513 } 5514 // Check wallets fee rate limit against server's max fee rate 5515 if btc.feeRateLimit() < mo.MaxFeeRate { 5516 return nil, nil, 0, fmt.Errorf( 5517 "%v: server's max fee rate %v higher than configued fee rate limit %v", 5518 dex.BipIDSymbol(BipID), mo.MaxFeeRate, btc.feeRateLimit()) 5519 } 5520 5521 bal, err := btc.Balance() 5522 if err != nil { 5523 return nil, nil, 0, fmt.Errorf("error getting wallet balance: %w", err) 5524 } 5525 if bal.Available < totalRequiredForOrders { 5526 return nil, nil, 0, fmt.Errorf("insufficient funds. %d < %d", 5527 bal.Available, totalRequiredForOrders) 5528 } 5529 5530 customCfg, err := decodeFundMultiOptions(mo.Options) 5531 if err != nil { 5532 return nil, nil, 0, fmt.Errorf("error decoding options: %w", err) 5533 } 5534 5535 return btc.fundMulti(maxLock, mo.Values, mo.FeeSuggestion, mo.MaxFeeRate, customCfg.Split, customCfg.SplitBuffer) 5536 } 5537 5538 // MaxFundingFees returns the maximum funding fees for an order/multi-order. 5539 func (btc *baseWallet) MaxFundingFees(numTrades uint32, feeRate uint64, options map[string]string) uint64 { 5540 customCfg, err := decodeFundMultiOptions(options) 5541 if err != nil { 5542 btc.log.Errorf("Error decoding multi-fund settings: %v", err) 5543 return 0 5544 } 5545 5546 if !customCfg.Split { 5547 return 0 5548 } 5549 5550 var inputSize, outputSize uint64 5551 if btc.segwit { 5552 inputSize = dexbtc.RedeemP2WPKHInputTotalSize 5553 outputSize = dexbtc.P2WPKHOutputSize 5554 } else { 5555 inputSize = dexbtc.RedeemP2PKHInputSize 5556 outputSize = dexbtc.P2PKHOutputSize 5557 } 5558 5559 const numInputs = 12 // plan for lots of inputs to get a safe estimate 5560 5561 txSize := dexbtc.MinimumTxOverhead + numInputs*inputSize + uint64(numTrades+1)*outputSize 5562 return feeRate * txSize 5563 } 5564 5565 func rpcTxFee(tx *ListTransactionsResult) uint64 { 5566 if tx.Fee != nil { 5567 // Fee always seems to be negative in btcwallet, but just 5568 // in case. 5569 if *tx.Fee < 0 { 5570 return toSatoshi(-*tx.Fee) 5571 } 5572 return toSatoshi(*tx.Fee) 5573 } 5574 return 0 5575 } 5576 5577 // idUnknownTx identifies the type and details of a transaction either made 5578 // or recieved by the wallet. 5579 func (btc *baseWallet) idUnknownTx(tx *ListTransactionsResult) (*asset.WalletTransaction, error) { 5580 txHash, err := chainhash.NewHashFromStr(tx.TxID) 5581 if err != nil { 5582 return nil, fmt.Errorf("error decoding tx hash %s: %v", tx.TxID, err) 5583 } 5584 txRaw, _, err := btc.rawWalletTx(txHash) 5585 if err != nil { 5586 return nil, err 5587 } 5588 msgTx, err := btc.deserializeTx(txRaw) 5589 if err != nil { 5590 return nil, fmt.Errorf("error deserializing tx: %v", err) 5591 } 5592 5593 fee := rpcTxFee(tx) 5594 5595 var totalOut uint64 5596 for _, txOut := range msgTx.TxOut { 5597 totalOut += uint64(txOut.Value) 5598 } 5599 5600 txIsBond := func(msgTx *wire.MsgTx) (bool, *asset.BondTxInfo) { 5601 if len(msgTx.TxOut) < 2 { 5602 return false, nil 5603 } 5604 const scriptVer = 0 5605 acctID, lockTime, pkHash, err := dexbtc.ExtractBondCommitDataV0(scriptVer, msgTx.TxOut[1].PkScript) 5606 if err != nil { 5607 return false, nil 5608 } 5609 return true, &asset.BondTxInfo{ 5610 AccountID: acctID[:], 5611 LockTime: uint64(lockTime), 5612 BondID: pkHash[:], 5613 } 5614 } 5615 if isBond, bondInfo := txIsBond(msgTx); isBond { 5616 return &asset.WalletTransaction{ 5617 ID: tx.TxID, 5618 Type: asset.CreateBond, 5619 Amount: uint64(msgTx.TxOut[0].Value), 5620 Fees: fee, 5621 BondInfo: bondInfo, 5622 }, nil 5623 } 5624 5625 // Any other P2SH may be a swap or a send. We cannot determine unless we 5626 // look up the transaction that spends this UTXO. 5627 txPaysToScriptHash := func(msgTx *wire.MsgTx) (v uint64) { 5628 for _, txOut := range msgTx.TxOut { 5629 scriptClass := txscript.GetScriptClass(txOut.PkScript) 5630 if scriptClass == txscript.WitnessV0ScriptHashTy || scriptClass == txscript.ScriptHashTy { 5631 v += uint64(txOut.Value) 5632 } 5633 } 5634 return 5635 } 5636 if v := txPaysToScriptHash(msgTx); tx.Send && v > 0 { 5637 return &asset.WalletTransaction{ 5638 ID: tx.TxID, 5639 Type: asset.SwapOrSend, 5640 Amount: v, 5641 Fees: fee, 5642 }, nil 5643 } 5644 5645 // Helper function will help us identify inputs that spend P2SH contracts. 5646 containsContractAtPushIndex := func(msgTx *wire.MsgTx, idx int, isContract func(segwit bool, contract []byte) bool) bool { 5647 txinloop: 5648 for _, txIn := range msgTx.TxIn { 5649 if len(txIn.Witness) > 0 { 5650 // segwit 5651 if len(txIn.Witness) < idx+1 { 5652 continue 5653 } 5654 contract := txIn.Witness[idx] 5655 if isContract(true, contract) { 5656 return true 5657 } 5658 } else { 5659 // not segwit 5660 const scriptVer = 0 5661 tokenizer := txscript.MakeScriptTokenizer(scriptVer, txIn.SignatureScript) 5662 for i := 0; i <= idx; i++ { // contract is 5th item item in redemption and 4th in refund 5663 if !tokenizer.Next() { 5664 continue txinloop 5665 } 5666 } 5667 if isContract(false, tokenizer.Data()) { 5668 return true 5669 } 5670 } 5671 } 5672 return false 5673 } 5674 5675 // Swap redemptions and refunds 5676 contractIsSwap := func(segwit bool, contract []byte) bool { 5677 _, _, _, _, err := dexbtc.ExtractSwapDetails(contract, segwit, btc.chainParams) 5678 return err == nil 5679 } 5680 redeemsSwap := func(msgTx *wire.MsgTx) bool { 5681 return containsContractAtPushIndex(msgTx, 4, contractIsSwap) 5682 } 5683 if redeemsSwap(msgTx) { 5684 return &asset.WalletTransaction{ 5685 ID: tx.TxID, 5686 Type: asset.Redeem, 5687 Amount: totalOut + fee, 5688 Fees: fee, 5689 }, nil 5690 } 5691 refundsSwap := func(msgTx *wire.MsgTx) bool { 5692 return containsContractAtPushIndex(msgTx, 3, contractIsSwap) 5693 } 5694 if refundsSwap(msgTx) { 5695 return &asset.WalletTransaction{ 5696 ID: tx.TxID, 5697 Type: asset.Refund, 5698 Amount: totalOut + fee, 5699 Fees: fee, 5700 }, nil 5701 } 5702 5703 // Bond refunds 5704 redeemsBond := func(msgTx *wire.MsgTx) (bool, *asset.BondTxInfo) { 5705 var bondInfo *asset.BondTxInfo 5706 isBond := func(segwit bool, contract []byte) bool { 5707 const scriptVer = 0 5708 lockTime, pkHash, err := dexbtc.ExtractBondDetailsV0(scriptVer, contract) 5709 if err != nil { 5710 return false 5711 } 5712 bondInfo = &asset.BondTxInfo{ 5713 AccountID: []byte{}, // Could look for the bond tx to get this, I guess. 5714 LockTime: uint64(lockTime), 5715 BondID: pkHash[:], 5716 } 5717 return true 5718 } 5719 return containsContractAtPushIndex(msgTx, 2, isBond), bondInfo 5720 } 5721 if isBondRedemption, bondInfo := redeemsBond(msgTx); isBondRedemption { 5722 return &asset.WalletTransaction{ 5723 ID: tx.TxID, 5724 Type: asset.RedeemBond, 5725 Amount: totalOut, 5726 Fees: fee, 5727 BondInfo: bondInfo, 5728 }, nil 5729 } 5730 5731 allOutputsPayUs := func(msgTx *wire.MsgTx) bool { 5732 for _, txOut := range msgTx.TxOut { 5733 scriptClass, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams) 5734 if err != nil { 5735 btc.log.Errorf("ExtractPkScriptAddrs error: %w", err) 5736 return false 5737 } 5738 switch scriptClass { 5739 case txscript.PubKeyHashTy, txscript.WitnessV0PubKeyHashTy: 5740 default: 5741 return false 5742 } 5743 if len(addrs) != 1 { // sanity check 5744 return false 5745 } 5746 5747 addr := addrs[0] 5748 owns, err := btc.node.ownsAddress(addr) 5749 if err != nil { 5750 btc.log.Errorf("ownsAddress error: %w", err) 5751 return false 5752 } 5753 if !owns { 5754 return false 5755 } 5756 } 5757 5758 return true 5759 } 5760 5761 if tx.Send && allOutputsPayUs(msgTx) { 5762 if len(msgTx.TxOut) == 1 { 5763 return &asset.WalletTransaction{ 5764 ID: tx.TxID, 5765 Type: asset.Acceleration, 5766 Amount: 0, 5767 Fees: fee, 5768 }, nil 5769 5770 } 5771 return &asset.WalletTransaction{ 5772 ID: tx.TxID, 5773 Type: asset.Split, 5774 Amount: 0, 5775 Fees: fee, 5776 }, nil 5777 } 5778 5779 txOutDirection := func(msgTx *wire.MsgTx) (in, out uint64) { 5780 for _, txOut := range msgTx.TxOut { 5781 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams) 5782 if err != nil { 5783 btc.log.Errorf("ExtractPkScriptAddrs error: %w", err) 5784 continue 5785 } 5786 5787 if len(addrs) == 0 { // sanity check 5788 continue 5789 } 5790 5791 addr := addrs[0] 5792 owns, err := btc.node.ownsAddress(addr) 5793 if err != nil { 5794 btc.log.Errorf("ownsAddress error: %w", err) 5795 continue 5796 } 5797 if owns { 5798 in += uint64(txOut.Value) 5799 } else { 5800 out += uint64(txOut.Value) 5801 } 5802 } 5803 return 5804 } 5805 in, out := txOutDirection(msgTx) 5806 5807 getRecipient := func(msgTx *wire.MsgTx, receive bool) *string { 5808 for _, txOut := range msgTx.TxOut { 5809 _, addrs, _, err := txscript.ExtractPkScriptAddrs(txOut.PkScript, btc.chainParams) 5810 if err != nil { 5811 btc.log.Errorf("ExtractPkScriptAddrs error: %w", err) 5812 continue 5813 } 5814 5815 if len(addrs) == 0 { // sanity check 5816 continue 5817 } 5818 5819 addr := addrs[0] 5820 owns, err := btc.node.ownsAddress(addr) 5821 if err != nil { 5822 btc.log.Errorf("ownsAddress error: %w", err) 5823 continue 5824 } 5825 5826 if receive == owns { 5827 str := addr.String() 5828 return &str 5829 } 5830 } 5831 return nil 5832 } 5833 5834 if tx.Send { 5835 return &asset.WalletTransaction{ 5836 ID: tx.TxID, 5837 Type: asset.Send, 5838 Amount: out, 5839 Fees: fee, 5840 Recipient: getRecipient(msgTx, false), 5841 }, nil 5842 } 5843 5844 return &asset.WalletTransaction{ 5845 ID: tx.TxID, 5846 Type: asset.Receive, 5847 Amount: in, 5848 Fees: fee, 5849 Recipient: getRecipient(msgTx, true), 5850 }, nil 5851 } 5852 5853 // addUnknownTransactionsToHistory checks for any transactions the wallet has 5854 // made or recieved that are not part of the transaction history. It scans 5855 // from the last point to which it had previously scanned to the current tip. 5856 func (btc *baseWallet) addUnknownTransactionsToHistory(tip uint64) { 5857 txHistoryDB := btc.txDB() 5858 if txHistoryDB == nil { 5859 return 5860 } 5861 5862 const blockQueryBuffer = 3 5863 var blockToQuery uint64 5864 lastQuery := btc.receiveTxLastQuery.Load() 5865 if lastQuery == 0 { 5866 // TODO: use wallet birthday instead of block 0. 5867 // blockToQuery = 0 5868 } else if lastQuery < tip-blockQueryBuffer { 5869 blockToQuery = lastQuery - blockQueryBuffer 5870 } else { 5871 blockToQuery = tip - blockQueryBuffer 5872 } 5873 5874 txs, err := btc.node.listTransactionsSinceBlock(int32(blockToQuery)) 5875 if err != nil { 5876 btc.log.Errorf("Error listing transactions since block %d: %v", blockToQuery, err) 5877 return 5878 } 5879 5880 for _, tx := range txs { 5881 if btc.ctx.Err() != nil { 5882 return 5883 } 5884 txHash, err := chainhash.NewHashFromStr(tx.TxID) 5885 if err != nil { 5886 btc.log.Errorf("Error decoding tx hash %s: %v", tx.TxID, err) 5887 continue 5888 } 5889 _, err = txHistoryDB.GetTx(txHash.String()) 5890 if err == nil { 5891 continue 5892 } 5893 if !errors.Is(err, asset.CoinNotFoundError) { 5894 btc.log.Errorf("Error getting tx %s: %v", txHash.String(), err) 5895 continue 5896 } 5897 wt, err := btc.idUnknownTx(tx) 5898 if err != nil { 5899 btc.log.Errorf("error identifying transaction: %v", err) 5900 continue 5901 } 5902 if tx.BlockHeight > 0 && tx.BlockHeight < uint32(tip-blockQueryBuffer) { 5903 wt.BlockNumber = uint64(tx.BlockHeight) 5904 wt.Timestamp = tx.BlockTime 5905 } 5906 5907 // Don't send notifications for the initial sync to avoid spamming the 5908 // front end. A notification is sent at the end of the initial sync. 5909 btc.addTxToHistory(wt, txHash, true, blockToQuery == 0) 5910 } 5911 5912 btc.receiveTxLastQuery.Store(tip) 5913 err = txHistoryDB.SetLastReceiveTxQuery(tip) 5914 if err != nil { 5915 btc.log.Errorf("Error setting last query to %d: %v", tip, err) 5916 } 5917 5918 if blockToQuery == 0 { 5919 btc.emit.TransactionHistorySyncedNote() 5920 } 5921 } 5922 5923 // syncTxHistory checks to see if there are any transactions which the wallet 5924 // has made or recieved that are not part of the transaction history, then 5925 // identifies and adds them. It also checks all the pending transactions to see 5926 // if they have been mined into a block, and if so, updates the transaction 5927 // history to reflect the block height. 5928 func (btc *intermediaryWallet) syncTxHistory(tip uint64) { 5929 if !btc.syncingTxHistory.CompareAndSwap(false, true) { 5930 return 5931 } 5932 defer btc.syncingTxHistory.Store(false) 5933 5934 txHistoryDB := btc.txDB() 5935 if txHistoryDB == nil { 5936 return 5937 } 5938 5939 ss, err := btc.SyncStatus() 5940 if err != nil { 5941 btc.log.Errorf("Error getting sync status: %v", err) 5942 return 5943 } 5944 if !ss.Synced { 5945 return 5946 } 5947 5948 btc.addUnknownTransactionsToHistory(tip) 5949 5950 pendingTxsCopy := make(map[chainhash.Hash]ExtendedWalletTx, len(btc.pendingTxs)) 5951 btc.pendingTxsMtx.RLock() 5952 for hash, tx := range btc.pendingTxs { 5953 pendingTxsCopy[hash] = tx 5954 } 5955 btc.pendingTxsMtx.RUnlock() 5956 5957 handlePendingTx := func(txHash chainhash.Hash, tx *ExtendedWalletTx) { 5958 if !tx.Submitted { 5959 return 5960 } 5961 5962 gtr, err := btc.node.getWalletTransaction(&txHash) 5963 if errors.Is(err, asset.CoinNotFoundError) { 5964 err = txHistoryDB.RemoveTx(txHash.String()) 5965 if err == nil || errors.Is(err, asset.CoinNotFoundError) { 5966 btc.pendingTxsMtx.Lock() 5967 delete(btc.pendingTxs, txHash) 5968 btc.pendingTxsMtx.Unlock() 5969 } else { 5970 // Leave it in the pendingPendingTxs and attempt to remove it 5971 // again next time. 5972 btc.log.Errorf("Error removing tx %s from the history store: %v", txHash.String(), err) 5973 } 5974 return 5975 } 5976 if err != nil { 5977 btc.log.Errorf("Error getting transaction %s: %v", txHash.String(), err) 5978 return 5979 } 5980 5981 var updated bool 5982 if gtr.BlockHash != "" { 5983 blockHash, err := chainhash.NewHashFromStr(gtr.BlockHash) 5984 if err != nil { 5985 btc.log.Errorf("Error decoding block hash %s: %v", gtr.BlockHash, err) 5986 return 5987 } 5988 blockHeight, err := btc.tipRedeemer.getBlockHeight(blockHash) 5989 if err != nil { 5990 btc.log.Errorf("Error getting block height for %s: %v", blockHash, err) 5991 return 5992 } 5993 if tx.BlockNumber != uint64(blockHeight) || tx.Timestamp != gtr.BlockTime { 5994 tx.BlockNumber = uint64(blockHeight) 5995 tx.Timestamp = gtr.BlockTime 5996 updated = true 5997 } 5998 } else if gtr.BlockHash == "" && tx.BlockNumber != 0 { 5999 tx.BlockNumber = 0 6000 tx.Timestamp = 0 6001 updated = true 6002 } 6003 6004 var confs uint64 6005 if tx.BlockNumber > 0 && tip >= tx.BlockNumber { 6006 confs = tip - tx.BlockNumber + 1 6007 } 6008 if confs >= requiredRedeemConfirms { 6009 tx.Confirmed = true 6010 updated = true 6011 } 6012 6013 if updated { 6014 err = txHistoryDB.StoreTx(tx) 6015 if err != nil { 6016 btc.log.Errorf("Error updating tx %s: %v", txHash, err) 6017 return 6018 } 6019 6020 btc.pendingTxsMtx.Lock() 6021 if tx.Confirmed { 6022 delete(btc.pendingTxs, txHash) 6023 } else { 6024 btc.pendingTxs[txHash] = *tx 6025 } 6026 btc.pendingTxsMtx.Unlock() 6027 6028 btc.emit.TransactionNote(tx.WalletTransaction, false) 6029 } 6030 } 6031 6032 for hash, tx := range pendingTxsCopy { 6033 if btc.ctx.Err() != nil { 6034 return 6035 } 6036 handlePendingTx(hash, &tx) 6037 } 6038 } 6039 6040 // WalletTransaction returns a transaction that either the wallet has made or 6041 // one in which the wallet has received funds. The txID can be either a byte 6042 // reversed tx hash or a hex encoded coin ID. 6043 func (btc *intermediaryWallet) WalletTransaction(ctx context.Context, txID string) (*asset.WalletTransaction, error) { 6044 coinID, err := hex.DecodeString(txID) 6045 if err == nil { 6046 txHash, _, err := decodeCoinID(coinID) 6047 if err == nil { 6048 txID = txHash.String() 6049 } 6050 } 6051 6052 txHistoryDB := btc.txDB() 6053 if txHistoryDB == nil { 6054 return nil, fmt.Errorf("tx database not initialized") 6055 } 6056 6057 tx, err := txHistoryDB.GetTx(txID) 6058 if err != nil && !errors.Is(err, asset.CoinNotFoundError) { 6059 return nil, err 6060 } 6061 if tx != nil && tx.Confirmed { 6062 return tx, nil 6063 } 6064 6065 txHash, err := chainhash.NewHashFromStr(txID) 6066 if err != nil { 6067 return nil, fmt.Errorf("error decoding txid %s: %w", txID, err) 6068 } 6069 gtr, err := btc.node.getWalletTransaction(txHash) 6070 if err != nil { 6071 return nil, fmt.Errorf("error getting transaction %s: %w", txID, err) 6072 } 6073 6074 var blockHeight uint32 6075 if gtr.BlockHash != "" { 6076 blockHash, err := chainhash.NewHashFromStr(gtr.BlockHash) 6077 if err != nil { 6078 return nil, fmt.Errorf("error decoding block hash %s: %w", gtr.BlockHash, err) 6079 } 6080 height, err := btc.tipRedeemer.getBlockHeight(blockHash) 6081 if err != nil { 6082 return nil, fmt.Errorf("error getting block height for %s: %w", blockHash, err) 6083 } 6084 blockHeight = uint32(height) 6085 } 6086 6087 updated := tx == nil 6088 if tx == nil { 6089 tx, err = btc.idUnknownTx(&ListTransactionsResult{ 6090 BlockHeight: blockHeight, 6091 BlockTime: gtr.BlockTime, 6092 TxID: txID, 6093 }) 6094 if err != nil { 6095 return nil, fmt.Errorf("error identifying transaction: %v", err) 6096 } 6097 } 6098 6099 if tx.BlockNumber != uint64(blockHeight) || tx.Timestamp != gtr.BlockTime { 6100 tx.BlockNumber = uint64(blockHeight) 6101 tx.Timestamp = gtr.BlockTime 6102 tx.Confirmed = blockHeight > 0 6103 updated = true 6104 } 6105 6106 if updated { 6107 btc.addTxToHistory(tx, txHash, true, false) 6108 } 6109 6110 return tx, nil 6111 } 6112 6113 // TxHistory returns all the transactions the wallet has made. If refID is nil, 6114 // then transactions starting from the most recent are returned (past is ignored). 6115 // If past is true, the transactions prior to the refID are returned, otherwise 6116 // the transactions after the refID are returned. n is the number of 6117 // transactions to return. If n is <= 0, all the transactions will be returned. 6118 func (btc *intermediaryWallet) TxHistory(n int, refID *string, past bool) ([]*asset.WalletTransaction, error) { 6119 txHistoryDB := btc.txDB() 6120 if txHistoryDB == nil { 6121 return nil, fmt.Errorf("tx database not initialized") 6122 } 6123 return txHistoryDB.GetTxs(n, refID, past) 6124 } 6125 6126 // lockedSats is the total value of locked outputs, as locked with LockUnspent. 6127 func (btc *baseWallet) lockedSats() (uint64, error) { 6128 lockedOutpoints, err := btc.node.listLockUnspent() 6129 if err != nil { 6130 return 0, err 6131 } 6132 var sum uint64 6133 for _, rpcOP := range lockedOutpoints { 6134 txHash, err := chainhash.NewHashFromStr(rpcOP.TxID) 6135 if err != nil { 6136 return 0, err 6137 } 6138 pt := NewOutPoint(txHash, rpcOP.Vout) 6139 utxo := btc.cm.LockedOutput(pt) 6140 if utxo != nil { 6141 sum += utxo.Amount 6142 continue 6143 } 6144 tx, err := btc.node.getWalletTransaction(txHash) 6145 if err != nil { 6146 return 0, err 6147 } 6148 txOut, err := TxOutFromTxBytes(tx.Bytes, rpcOP.Vout, btc.deserializeTx, btc.hashTx) 6149 if err != nil { 6150 return 0, err 6151 } 6152 sum += uint64(txOut.Value) 6153 } 6154 return sum, nil 6155 } 6156 6157 // wireBytes dumps the serialized transaction bytes. 6158 func (btc *baseWallet) wireBytes(tx *wire.MsgTx) []byte { 6159 b, err := btc.serializeTx(tx) 6160 // wireBytes is just used for logging, and a serialization error is 6161 // extremely unlikely, so just log the error and return the nil bytes. 6162 if err != nil { 6163 btc.log.Errorf("error serializing %s transaction: %v", btc.symbol, err) 6164 return nil 6165 } 6166 return b 6167 } 6168 6169 // GetBestBlockHeight is exported for use by clone wallets. Not part of the 6170 // asset.Wallet interface. 6171 func (btc *baseWallet) GetBestBlockHeight() (int32, error) { 6172 return btc.node.getBestBlockHeight() 6173 } 6174 6175 // Convert the BTC value to satoshi. 6176 func toSatoshi(v float64) uint64 { 6177 return uint64(math.Round(v * conventionalConversionFactor)) 6178 } 6179 6180 // BlockHeader is a partial btcjson.GetBlockHeaderVerboseResult with mediantime 6181 // included. 6182 type BlockHeader struct { 6183 Hash string `json:"hash"` 6184 Confirmations int64 `json:"confirmations"` 6185 Height int64 `json:"height"` 6186 Time int64 `json:"time"` 6187 PreviousBlockHash string `json:"previousblockhash"` 6188 MedianTime int64 `json:"mediantime"` 6189 } 6190 6191 // hashContract hashes the contract for use in a p2sh or p2wsh pubkey script. 6192 // The hash function used depends on whether the wallet is configured for 6193 // segwit. Non-segwit uses Hash160, segwit uses SHA256. 6194 func (btc *baseWallet) hashContract(contract []byte) []byte { 6195 return hashContract(btc.segwit, contract) 6196 } 6197 6198 func hashContract(segwit bool, contract []byte) []byte { 6199 if segwit { 6200 h := sha256.Sum256(contract) // BIP141 6201 return h[:] 6202 } 6203 return btcutil.Hash160(contract) // BIP16 6204 } 6205 6206 // scriptHashAddress returns a new p2sh or p2wsh address, depending on whether 6207 // the wallet is configured for segwit. 6208 func (btc *baseWallet) scriptHashAddress(contract []byte) (btcutil.Address, error) { 6209 return scriptHashAddress(btc.segwit, contract, btc.chainParams) 6210 } 6211 6212 func (btc *baseWallet) scriptHashScript(contract []byte) ([]byte, error) { 6213 addr, err := btc.scriptHashAddress(contract) 6214 if err != nil { 6215 return nil, err 6216 } 6217 return txscript.PayToAddrScript(addr) 6218 } 6219 6220 // CallRPC is a method for making RPC calls directly on an underlying RPC 6221 // client. CallRPC is not part of the wallet interface. Its intended use is for 6222 // clone wallets to implement custom functionality. 6223 func (btc *baseWallet) CallRPC(method string, args []any, thing any) error { 6224 rpcCl, is := btc.node.(*rpcClient) 6225 if !is { 6226 return errors.New("wallet is not RPC") 6227 } 6228 return rpcCl.call(method, args, thing) 6229 } 6230 6231 func scriptHashAddress(segwit bool, contract []byte, chainParams *chaincfg.Params) (btcutil.Address, error) { 6232 if segwit { 6233 return btcutil.NewAddressWitnessScriptHash(hashContract(segwit, contract), chainParams) 6234 } 6235 return btcutil.NewAddressScriptHash(contract, chainParams) 6236 } 6237 6238 // ToCoinID converts the tx hash and vout to a coin ID, as a []byte. 6239 func ToCoinID(txHash *chainhash.Hash, vout uint32) []byte { 6240 coinID := make([]byte, chainhash.HashSize+4) 6241 copy(coinID[:chainhash.HashSize], txHash[:]) 6242 binary.BigEndian.PutUint32(coinID[chainhash.HashSize:], vout) 6243 return coinID 6244 } 6245 6246 // decodeCoinID decodes the coin ID into a tx hash and a vout. 6247 func decodeCoinID(coinID dex.Bytes) (*chainhash.Hash, uint32, error) { 6248 if len(coinID) != 36 { 6249 return nil, 0, fmt.Errorf("coin ID wrong length. expected 36, got %d", len(coinID)) 6250 } 6251 var txHash chainhash.Hash 6252 copy(txHash[:], coinID[:32]) 6253 return &txHash, binary.BigEndian.Uint32(coinID[32:]), nil 6254 } 6255 6256 // toBTC returns a float representation in conventional units for the sats. 6257 func toBTC[V uint64 | int64](v V) float64 { 6258 return btcutil.Amount(v).ToBTC() 6259 } 6260 6261 // rawTxInSig signs the transaction in input using the standard bitcoin 6262 // signature hash and ECDSA algorithm. 6263 func rawTxInSig(tx *wire.MsgTx, idx int, pkScript []byte, hashType txscript.SigHashType, 6264 key *btcec.PrivateKey, _ []int64, _ [][]byte) ([]byte, error) { 6265 6266 return txscript.RawTxInSignature(tx, idx, pkScript, txscript.SigHashAll, key) 6267 } 6268 6269 // prettyBTC prints a value as a float with up to 8 digits of precision, but 6270 // with trailing zeros and decimal points removed. 6271 func prettyBTC(v uint64) string { 6272 return strings.TrimRight(strings.TrimRight(strconv.FormatFloat(float64(v)/1e8, 'f', 8, 64), "0"), ".") 6273 } 6274 6275 // calcBumpedRate calculated a bump on the baseRate. If bump is nil, the 6276 // baseRate is returned directly. In the case of an error (nil or out-of-range), 6277 // the baseRate is returned unchanged. 6278 func calcBumpedRate(baseRate uint64, bump *float64) (uint64, error) { 6279 if bump == nil { 6280 return baseRate, nil 6281 } 6282 userBump := *bump 6283 if userBump > 2.0 { 6284 return baseRate, fmt.Errorf("fee bump %f is higher than the 2.0 limit", userBump) 6285 } 6286 if userBump < 1.0 { 6287 return baseRate, fmt.Errorf("fee bump %f is lower than 1", userBump) 6288 } 6289 return uint64(math.Round(float64(baseRate) * userBump)), nil 6290 } 6291 6292 func float64PtrStr(v *float64) string { 6293 if v == nil { 6294 return "nil" 6295 } 6296 return strconv.FormatFloat(*v, 'f', 8, 64) 6297 } 6298 6299 func hashTx(tx *wire.MsgTx) *chainhash.Hash { 6300 h := tx.TxHash() 6301 return &h 6302 } 6303 6304 func stringifyAddress(addr btcutil.Address, _ *chaincfg.Params) (string, error) { 6305 return addr.String(), nil 6306 } 6307 6308 func deserializeBlock(b []byte) (*wire.MsgBlock, error) { 6309 msgBlock := &wire.MsgBlock{} 6310 return msgBlock, msgBlock.Deserialize(bytes.NewReader(b)) 6311 } 6312 6313 // serializeMsgTx serializes the wire.MsgTx. 6314 func serializeMsgTx(msgTx *wire.MsgTx) ([]byte, error) { 6315 buf := bytes.NewBuffer(make([]byte, 0, msgTx.SerializeSize())) 6316 err := msgTx.Serialize(buf) 6317 if err != nil { 6318 return nil, err 6319 } 6320 return buf.Bytes(), nil 6321 } 6322 6323 // deserializeMsgTx creates a wire.MsgTx by deserializing data from the Reader. 6324 func deserializeMsgTx(r io.Reader) (*wire.MsgTx, error) { 6325 msgTx := new(wire.MsgTx) 6326 err := msgTx.Deserialize(r) 6327 if err != nil { 6328 return nil, err 6329 } 6330 return msgTx, nil 6331 } 6332 6333 // msgTxFromBytes creates a wire.MsgTx by deserializing the transaction. 6334 func msgTxFromBytes(txB []byte) (*wire.MsgTx, error) { 6335 return deserializeMsgTx(bytes.NewReader(txB)) 6336 } 6337 6338 // ConfirmRedemption returns how many confirmations a redemption has. Normally 6339 // this is very straightforward. However, with fluxuating fees, there's the 6340 // possibility that the tx is never mined and eventually purged from the 6341 // mempool. In that case we use the provided fee suggestion to create and send 6342 // a new redeem transaction, returning the new transactions hash. 6343 func (btc *baseWallet) ConfirmRedemption(coinID dex.Bytes, redemption *asset.Redemption, feeSuggestion uint64) (*asset.ConfirmRedemptionStatus, error) { 6344 txHash, _, err := decodeCoinID(coinID) 6345 if err != nil { 6346 return nil, err 6347 } 6348 6349 _, confs, err := btc.rawWalletTx(txHash) 6350 // redemption transaction found, return its confirms. 6351 // 6352 // TODO: Investigate the case where this redeem has been sitting in the 6353 // mempool for a long amount of time, possibly requiring some action by 6354 // us to get it unstuck. 6355 if err == nil { 6356 return &asset.ConfirmRedemptionStatus{ 6357 Confs: uint64(confs), 6358 Req: requiredRedeemConfirms, 6359 CoinID: coinID, 6360 }, nil 6361 } 6362 6363 if !errors.Is(err, WalletTransactionNotFound) { 6364 return nil, fmt.Errorf("problem searching for redemption transaction %s: %w", txHash, err) 6365 } 6366 6367 // Redemption transaction is missing from the point of view of our node! 6368 // Unlikely, but possible it was redeemed by another transaction. Check 6369 // if the contract is still an unspent output. 6370 6371 pkScript, err := btc.scriptHashScript(redemption.Spends.Contract) 6372 if err != nil { 6373 return nil, fmt.Errorf("error creating contract script: %w", err) 6374 } 6375 6376 swapHash, vout, err := decodeCoinID(redemption.Spends.Coin.ID()) 6377 if err != nil { 6378 return nil, err 6379 } 6380 6381 utxo, _, err := btc.node.getTxOut(swapHash, vout, pkScript, time.Now().Add(-ContractSearchLimit)) 6382 if err != nil { 6383 return nil, fmt.Errorf("error finding unspent contract %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err) 6384 } 6385 if utxo == nil { 6386 // TODO: Spent, but by who. Find the spending tx. 6387 btc.log.Warnf("Contract coin %v with swap hash %v vout %d spent by someone but not sure who.", redemption.Spends.Coin.ID(), swapHash, vout) 6388 // Incorrect, but we will be in a loop of erroring if we don't 6389 // return something. 6390 return &asset.ConfirmRedemptionStatus{ 6391 Confs: requiredRedeemConfirms, 6392 Req: requiredRedeemConfirms, 6393 CoinID: coinID, 6394 }, nil 6395 } 6396 6397 // The contract has not yet been redeemed, but it seems the redeeming 6398 // tx has disappeared. Assume the fee was too low at the time and it 6399 // was eventually purged from the mempool. Attempt to redeem again with 6400 // a currently reasonable fee. 6401 6402 form := &asset.RedeemForm{ 6403 Redemptions: []*asset.Redemption{redemption}, 6404 FeeSuggestion: feeSuggestion, 6405 } 6406 _, coin, _, err := btc.Redeem(form) 6407 if err != nil { 6408 return nil, fmt.Errorf("unable to re-redeem %s with swap hash %v vout %d: %w", redemption.Spends.Coin.ID(), swapHash, vout, err) 6409 } 6410 return &asset.ConfirmRedemptionStatus{ 6411 Confs: 0, 6412 Req: requiredRedeemConfirms, 6413 CoinID: coin.ID(), 6414 }, nil 6415 } 6416 6417 type AddressRecycler struct { 6418 recyclePath string 6419 log dex.Logger 6420 6421 mtx sync.Mutex 6422 addrs map[string]struct{} 6423 } 6424 6425 func NewAddressRecycler(recyclePath string, log dex.Logger) (*AddressRecycler, error) { 6426 // Try to load any cached unused redemption addresses. 6427 b, err := os.ReadFile(recyclePath) 6428 if err != nil && !errors.Is(err, os.ErrNotExist) { 6429 return nil, fmt.Errorf("error looking for recycled address file: %w", err) 6430 } 6431 addrs := strings.Split(string(b), "\n") 6432 recycledAddrs := make(map[string]struct{}, len(addrs)) 6433 for _, addr := range addrs { 6434 if addr == "" { 6435 continue 6436 } 6437 recycledAddrs[addr] = struct{}{} 6438 } 6439 return &AddressRecycler{ 6440 recyclePath: recyclePath, 6441 log: log, 6442 addrs: recycledAddrs, 6443 }, nil 6444 } 6445 6446 // WriteRecycledAddrsToFile writes the recycled address cache to file. 6447 func (a *AddressRecycler) WriteRecycledAddrsToFile() { 6448 a.mtx.Lock() 6449 addrs := make([]string, 0, len(a.addrs)) 6450 for addr := range a.addrs { 6451 addrs = append(addrs, addr) 6452 } 6453 a.mtx.Unlock() 6454 contents := []byte(strings.Join(addrs, "\n")) 6455 if err := os.WriteFile(a.recyclePath, contents, 0600); err != nil { 6456 a.log.Errorf("Error writing recycled address file: %v", err) 6457 } 6458 } 6459 6460 func (a *AddressRecycler) Address() string { 6461 a.mtx.Lock() 6462 defer a.mtx.Unlock() 6463 for addr := range a.addrs { 6464 delete(a.addrs, addr) 6465 return addr 6466 } 6467 return "" 6468 } 6469 6470 func (a *AddressRecycler) ReturnAddresses(addrs []string) { 6471 a.mtx.Lock() 6472 defer a.mtx.Unlock() 6473 for _, addr := range addrs { 6474 if _, exists := a.addrs[addr]; exists { 6475 a.log.Errorf("Returned address %q was already indexed", addr) 6476 continue 6477 } 6478 a.addrs[addr] = struct{}{} 6479 } 6480 } 6481 6482 // BitcoreRateFetcher generates a rate fetching function for the bitcore.io API. 6483 func BitcoreRateFetcher(ticker string) func(ctx context.Context, net dex.Network) (uint64, error) { 6484 const uriTemplate = "https://api.bitcore.io/api/%s/%s/fee/1" 6485 mainnetURI, testnetURI := fmt.Sprintf(uriTemplate, ticker, "mainnet"), fmt.Sprintf(uriTemplate, ticker, "testnet") 6486 6487 return func(ctx context.Context, net dex.Network) (uint64, error) { 6488 var uri string 6489 if net == dex.Testnet { 6490 uri = testnetURI 6491 } else { 6492 uri = mainnetURI 6493 } 6494 ctx, cancel := context.WithTimeout(ctx, 4*time.Second) 6495 defer cancel() 6496 var resp struct { 6497 RatePerKB float64 `json:"feerate"` 6498 } 6499 if err := dexnet.Get(ctx, uri, &resp); err != nil { 6500 return 0, err 6501 } 6502 if resp.RatePerKB <= 0 { 6503 return 0, fmt.Errorf("zero or negative fee rate") 6504 } 6505 return uint64(math.Round(resp.RatePerKB * 1e5)), nil // 1/kB => 1/B 6506 } 6507 }