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