decred.org/dcrdex@v1.0.5/server/asset/dcr/dcr.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 dcr 5 6 import ( 7 "bytes" 8 "context" 9 "crypto/sha256" 10 "encoding/binary" 11 "encoding/hex" 12 "errors" 13 "fmt" 14 "math" 15 "os" 16 "strings" 17 "sync" 18 "time" 19 20 "decred.org/dcrdex/dex" 21 "decred.org/dcrdex/dex/calc" 22 dexdcr "decred.org/dcrdex/dex/networks/dcr" 23 "decred.org/dcrdex/server/account" 24 "decred.org/dcrdex/server/asset" 25 "github.com/decred/dcrd/blockchain/stake/v5" 26 "github.com/decred/dcrd/blockchain/standalone/v2" 27 "github.com/decred/dcrd/chaincfg/chainhash" 28 "github.com/decred/dcrd/dcrjson/v4" 29 "github.com/decred/dcrd/dcrutil/v4" 30 "github.com/decred/dcrd/hdkeychain/v3" 31 chainjson "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 32 "github.com/decred/dcrd/rpcclient/v8" 33 "github.com/decred/dcrd/txscript/v4" 34 "github.com/decred/dcrd/txscript/v4/stdaddr" 35 "github.com/decred/dcrd/txscript/v4/stdscript" 36 "github.com/decred/dcrd/wire" 37 ) 38 39 // Driver implements asset.Driver. 40 type Driver struct{} 41 42 var _ asset.Driver = (*Driver)(nil) 43 44 // Setup creates the DCR backend. Start the backend with its Run method. 45 func (d *Driver) Setup(cfg *asset.BackendConfig) (asset.Backend, error) { 46 // With a websocket RPC client with auto-reconnect, setup a logging 47 // subsystem for the rpcclient. 48 rpcLogger := cfg.Logger.SubLogger("RPC") 49 if rpcLogger.Level() == dex.LevelTrace { 50 rpcLogger.SetLevel(dex.LevelDebug) 51 } 52 rpcclient.UseLogger(rpcLogger) 53 return NewBackend(cfg) 54 } 55 56 // DecodeCoinID creates a human-readable representation of a coin ID for Decred. 57 func (d *Driver) DecodeCoinID(coinID []byte) (string, error) { 58 txid, vout, err := decodeCoinID(coinID) 59 if err != nil { 60 return "", err 61 } 62 return fmt.Sprintf("%v:%d", txid, vout), err 63 } 64 65 // UnitInfo returns the dex.UnitInfo for the asset. 66 func (d *Driver) UnitInfo() dex.UnitInfo { 67 return dexdcr.UnitInfo 68 } 69 70 // Version returns the Backend implementation's version number. 71 func (d *Driver) Version() uint32 { 72 return version 73 } 74 75 // MinBondSize calculates the minimum bond size for a given fee rate that avoids 76 // dust outputs on the bond and refund txs, assuming the maxFeeRate doesn't 77 // change. 78 func (d *Driver) MinBondSize(maxFeeRate uint64) uint64 { 79 return dexdcr.MinBondSize(maxFeeRate) 80 } 81 82 // MinLotSize calculates the minimum bond size for a given fee rate that avoids 83 // dust outputs on the swap and refund txs, assuming the maxFeeRate doesn't 84 // change. 85 func (d *Driver) MinLotSize(maxFeeRate uint64) uint64 { 86 return dexdcr.MinLotSize(maxFeeRate) 87 } 88 89 // Name is the asset's name. 90 func (d *Driver) Name() string { 91 return "Decred" 92 } 93 94 func init() { 95 asset.Register(BipID, &Driver{}) 96 } 97 98 var ( 99 zeroHash chainhash.Hash 100 // The blockPollInterval is the delay between calls to GetBestBlockHash to 101 // check for new blocks. 102 blockPollInterval = time.Second 103 104 compatibleNodeRPCVersions = []dex.Semver{ 105 {Major: 8, Minor: 0, Patch: 0}, // 1.8-pre, just dropped unused ticket RPCs 106 {Major: 7, Minor: 0, Patch: 0}, // 1.7 release, new gettxout args 107 } 108 109 conventionalConversionFactor = float64(dexdcr.UnitInfo.Conventional.ConversionFactor) 110 ) 111 112 const ( 113 version = 0 114 BipID = 42 115 assetName = "dcr" 116 immatureTransactionError = dex.ErrorKind("immature output") 117 BondVersion = 0 118 ) 119 120 // dcrNode represents a blockchain information fetcher. In practice, it is 121 // satisfied by rpcclient.Client, and all methods are matches for Client 122 // methods. For testing, it can be satisfied by a stub. 123 type dcrNode interface { 124 EstimateSmartFee(ctx context.Context, confirmations int64, mode chainjson.EstimateSmartFeeMode) (*chainjson.EstimateSmartFeeResult, error) 125 GetTxOut(ctx context.Context, txHash *chainhash.Hash, index uint32, tree int8, mempool bool) (*chainjson.GetTxOutResult, error) 126 GetRawTransactionVerbose(ctx context.Context, txHash *chainhash.Hash) (*chainjson.TxRawResult, error) 127 GetBlockVerbose(ctx context.Context, blockHash *chainhash.Hash, verboseTx bool) (*chainjson.GetBlockVerboseResult, error) 128 GetBlockHash(ctx context.Context, blockHeight int64) (*chainhash.Hash, error) 129 GetBestBlockHash(ctx context.Context) (*chainhash.Hash, error) 130 GetBlockChainInfo(ctx context.Context) (*chainjson.GetBlockChainInfoResult, error) 131 GetRawTransaction(ctx context.Context, txHash *chainhash.Hash) (*dcrutil.Tx, error) 132 SendRawTransaction(ctx context.Context, tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error) 133 } 134 135 // The rpcclient package functions will return a rpcclient.ErrRequestCanceled 136 // error if the context is canceled. Translate these to asset.ErrRequestTimeout. 137 func translateRPCCancelErr(err error) error { 138 if errors.Is(err, rpcclient.ErrRequestCanceled) { 139 err = asset.ErrRequestTimeout 140 } 141 return err 142 } 143 144 // ParseBondTx performs basic validation of a serialized time-locked fidelity 145 // bond transaction given the bond's P2SH redeem script. 146 // 147 // The transaction must have at least two outputs: out 0 pays to a P2SH address 148 // (the bond), and out 1 is a nulldata output that commits to an account ID. 149 // There may also be a change output. 150 // 151 // Returned: The bond's coin ID (i.e. encoded UTXO) of the bond output. The bond 152 // output's amount and P2SH address. The lockTime and pubkey hash data pushes 153 // from the script. The account ID from the second output is also returned. 154 // 155 // Properly formed transactions: 156 // 157 // 1. The bond output (vout 0) must be a P2SH output. 158 // 2. The bond's redeem script must be of the form: 159 // <lockTime[4]> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash[20]> OP_EQUALVERIFY OP_CHECKSIG 160 // 3. The null data output (vout 1) must have a 58-byte data push (ver | account ID | lockTime | pubkeyHash). 161 // 4. The transaction must have a zero locktime and expiry. 162 // 5. All inputs must have the max sequence num set (finalized). 163 // 6. The transaction must pass the checks in the 164 // blockchain.CheckTransactionSanity function. 165 // 166 // For DCR, and possibly all assets, the bond script is reconstructed from the 167 // null data output, and it is verified that the bond output pays to this 168 // script. 169 func ParseBondTx(ver uint16, rawTx []byte) (bondCoinID []byte, amt int64, bondAddr string, 170 bondPubKeyHash []byte, lockTime int64, acct account.AccountID, err error) { 171 if ver != BondVersion { 172 err = errors.New("only version 0 bonds supported") 173 return 174 } 175 // While the dcr package uses a package-level chainParams variable, ensure 176 // that a backend has been instantiated first. Alternatively, we can add a 177 // dex.Network argument to this function, or make it a Backend method. 178 if chainParams == nil { 179 err = errors.New("dcr asset package config not yet loaded") 180 return 181 } 182 msgTx := wire.NewMsgTx() 183 if err = msgTx.Deserialize(bytes.NewReader(rawTx)); err != nil { 184 return 185 } 186 187 if msgTx.LockTime != 0 { 188 err = errors.New("transaction locktime not zero") 189 return 190 } 191 if msgTx.Expiry != wire.NoExpiryValue { 192 err = errors.New("transaction has an expiration") 193 return 194 } 195 196 if err = standalone.CheckTransactionSanity(msgTx, uint64(chainParams.MaxTxSize)); err != nil { 197 return 198 } 199 200 if len(msgTx.TxOut) < 2 { 201 err = fmt.Errorf("expected at least 2 outputs, found %d", len(msgTx.TxOut)) 202 return 203 } 204 205 for _, txIn := range msgTx.TxIn { 206 if txIn.Sequence != wire.MaxTxInSequenceNum { 207 err = errors.New("input has non-max sequence number") 208 return 209 } 210 } 211 212 // Fidelity bond (output 0) 213 bondOut := msgTx.TxOut[0] 214 class, addrs := stdscript.ExtractAddrs(bondOut.Version, bondOut.PkScript, chainParams) 215 if class != stdscript.STScriptHash || len(addrs) != 1 { // addrs check is redundant for p2sh 216 err = fmt.Errorf("bad bond pkScript (class = %v)", class) 217 return 218 } 219 scriptHash := txscript.ExtractScriptHash(bondOut.PkScript) 220 221 // Bond account commitment (output 1) 222 acctCommitOut := msgTx.TxOut[1] 223 acct, lock, pkh, err := dexdcr.ExtractBondCommitDataV0(acctCommitOut.Version, acctCommitOut.PkScript) 224 if err != nil { 225 err = fmt.Errorf("invalid bond commitment output: %w", err) 226 return 227 } 228 229 // Reconstruct and check the bond redeem script. 230 bondScript, err := dexdcr.MakeBondScript(ver, lock, pkh[:]) 231 if err != nil { 232 err = fmt.Errorf("failed to build bond output redeem script: %w", err) 233 return 234 } 235 if !bytes.Equal(dcrutil.Hash160(bondScript), scriptHash) { 236 err = fmt.Errorf("script hash check failed for output 0 of %s", msgTx.TxHash()) 237 return 238 } 239 // lock, pkh, _ := dexdcr.ExtractBondDetailsV0(bondOut.Version, bondScript) 240 241 txid := msgTx.TxHash() 242 bondCoinID = toCoinID(&txid, 0) 243 amt = bondOut.Value 244 bondAddr = addrs[0].String() // don't convert address, must match type we specified 245 lockTime = int64(lock) 246 bondPubKeyHash = pkh[:] 247 248 return 249 } 250 251 // Backend is an asset backend for Decred. It has methods for fetching output 252 // information and subscribing to block updates. It maintains a cache of block 253 // data for quick lookups. Backend implements asset.Backend, so provides 254 // exported methods for DEX-related blockchain info. 255 type Backend struct { 256 ctx context.Context 257 cfg *config 258 // If an rpcclient.Client is used for the node, keeping a reference at client 259 // will result in (Client).Shutdown() being called on context cancellation. 260 client *rpcclient.Client 261 // node is used throughout for RPC calls, and in typical use will be the same 262 // as client. For testing, it can be set to a stub. 263 node dcrNode 264 // The backend provides block notification channels through it BlockChannel 265 // method. signalMtx locks the blockChans array. 266 signalMtx sync.RWMutex 267 blockChans map[chan *asset.BlockUpdate]struct{} 268 // The block cache stores just enough info about the blocks to prevent future 269 // calls to GetBlockVerbose. 270 blockCache *blockCache 271 // A logger will be provided by the DEX. All logging should use the provided 272 // logger. 273 log dex.Logger 274 // nodeRelay is the NodeRelay address. 275 nodeRelay string 276 } 277 278 // Check that Backend satisfies the Backend interface. 279 var _ asset.Backend = (*Backend)(nil) 280 281 // unconnectedDCR returns a Backend without a node. The node should be set 282 // before use. 283 func unconnectedDCR(cfg *asset.BackendConfig, dcrConfig *config) *Backend { 284 return &Backend{ 285 cfg: dcrConfig, 286 blockCache: newBlockCache(cfg.Logger), 287 log: cfg.Logger, 288 blockChans: make(map[chan *asset.BlockUpdate]struct{}), 289 nodeRelay: cfg.RelayAddr, 290 } 291 } 292 293 // NewBackend is the exported constructor by which the DEX will import the 294 // Backend. If configPath is an empty string, the backend will attempt to read 295 // the settings directly from the dcrd config file in its default system 296 // location. 297 func NewBackend(cfg *asset.BackendConfig) (*Backend, error) { 298 // loadConfig will set fields if defaults are used and set the chainParams 299 // package variable. 300 dcrConfig, err := loadConfig(cfg.ConfigPath, cfg.Net) 301 if err != nil { 302 return nil, err 303 } 304 return unconnectedDCR(cfg, dcrConfig), nil 305 } 306 307 func (dcr *Backend) shutdown() { 308 if dcr.client != nil { 309 dcr.client.Shutdown() 310 dcr.client.WaitForShutdown() 311 } 312 } 313 314 // Connect connects to the node RPC server. A dex.Connector. 315 func (dcr *Backend) Connect(ctx context.Context) (_ *sync.WaitGroup, err error) { 316 var client *rpcclient.Client 317 if dcr.nodeRelay == "" { 318 client, err = connectNodeRPC(dcr.cfg.RPCListen, dcr.cfg.RPCUser, dcr.cfg.RPCPass, dcr.cfg.RPCCert) 319 } else { 320 client, err = connectNodeRelay(dcr.nodeRelay, dcr.cfg.RPCUser, dcr.cfg.RPCPass) 321 } 322 if err != nil { 323 return nil, err 324 } 325 dcr.client = client 326 327 // Ensure the network of the connected node is correct for the expected 328 // dex.Network. 329 net, err := dcr.client.GetCurrentNet(ctx) 330 if err != nil { 331 dcr.shutdown() 332 return nil, fmt.Errorf("getcurrentnet failure: %w", err) 333 } 334 var wantCurrencyNet wire.CurrencyNet 335 switch dcr.cfg.Network { 336 case dex.Testnet: 337 wantCurrencyNet = wire.TestNet3 338 case dex.Mainnet: 339 wantCurrencyNet = wire.MainNet 340 case dex.Regtest: // dex.Simnet 341 wantCurrencyNet = wire.SimNet 342 } 343 if net != wantCurrencyNet { 344 dcr.shutdown() 345 return nil, fmt.Errorf("wrong net %v", net.String()) 346 } 347 348 // Check the required API versions. 349 versions, err := dcr.client.Version(ctx) 350 if err != nil { 351 dcr.shutdown() 352 return nil, fmt.Errorf("DCR node version fetch error: %w", err) 353 } 354 355 ver, exists := versions["dcrdjsonrpcapi"] 356 if !exists { 357 dcr.shutdown() 358 return nil, fmt.Errorf("dcrd.Version response missing 'dcrdjsonrpcapi'") 359 } 360 nodeSemver := dex.NewSemver(ver.Major, ver.Minor, ver.Patch) 361 if !dex.SemverCompatibleAny(compatibleNodeRPCVersions, nodeSemver) { 362 dcr.shutdown() 363 return nil, fmt.Errorf("dcrd has an incompatible JSON-RPC version %s, require one of %s", 364 nodeSemver, compatibleNodeRPCVersions) 365 } 366 367 // Verify dcrd has tx index enabled (required for getrawtransaction). 368 info, err := dcr.client.GetInfo(ctx) 369 if err != nil { 370 dcr.shutdown() 371 return nil, fmt.Errorf("dcrd getinfo check failed: %w", err) 372 } 373 if !info.TxIndex { 374 dcr.shutdown() 375 return nil, errors.New("dcrd does not have transaction index enabled (specify --txindex)") 376 } 377 378 dcr.log.Infof("Connected to dcrd (JSON-RPC API v%s) on %v", nodeSemver, net) 379 380 dcr.node = dcr.client 381 dcr.ctx = ctx 382 383 // Prime the cache with the best block. 384 bestHash, _, err := dcr.client.GetBestBlock(ctx) 385 if err != nil { 386 dcr.shutdown() 387 return nil, fmt.Errorf("error getting best block from dcrd: %w", err) 388 } 389 if bestHash != nil { 390 _, err := dcr.getDcrBlock(ctx, bestHash) 391 if err != nil { 392 dcr.shutdown() 393 return nil, fmt.Errorf("error priming the cache: %w", err) 394 } 395 } 396 397 if _, err = dcr.FeeRate(ctx); err != nil { 398 dcr.log.Warnf("Decred backend started without fee estimation available: %v", err) 399 } 400 401 var wg sync.WaitGroup 402 wg.Add(1) 403 go func() { 404 defer wg.Done() 405 dcr.run(ctx) 406 }() 407 return &wg, nil 408 } 409 410 // FeeRate returns the current optimal fee rate in atoms / byte. 411 func (dcr *Backend) FeeRate(ctx context.Context) (uint64, error) { 412 // estimatesmartfee 1 returns extremely high rates on DCR. 413 estimateFeeResult, err := dcr.node.EstimateSmartFee(ctx, 2, chainjson.EstimateSmartFeeConservative) 414 if err != nil { 415 return 0, translateRPCCancelErr(err) 416 } 417 atomsPerKB, err := dcrutil.NewAmount(estimateFeeResult.FeeRate) 418 if err != nil { 419 return 0, err 420 } 421 atomsPerB := uint64(math.Round(float64(atomsPerKB) / 1000)) 422 if atomsPerB == 0 { 423 atomsPerB = 1 424 } 425 return atomsPerB, nil 426 } 427 428 // Info provides some general information about the backend. 429 func (*Backend) Info() *asset.BackendInfo { 430 return &asset.BackendInfo{} 431 } 432 433 // ValidateFeeRate checks that the transaction fees used to initiate the 434 // contract are sufficient. 435 func (dcr *Backend) ValidateFeeRate(c asset.Coin, reqFeeRate uint64) bool { 436 return c.FeeRate() >= reqFeeRate 437 } 438 439 // BlockChannel creates and returns a new channel on which to receive block 440 // updates. If the returned channel is ever blocking, there will be no error 441 // logged from the dcr package. Part of the asset.Backend interface. 442 func (dcr *Backend) BlockChannel(size int) <-chan *asset.BlockUpdate { 443 c := make(chan *asset.BlockUpdate, size) 444 dcr.signalMtx.Lock() 445 defer dcr.signalMtx.Unlock() 446 dcr.blockChans[c] = struct{}{} 447 return c 448 } 449 450 // SendRawTransaction broadcasts a raw transaction, returning a coin ID. 451 func (dcr *Backend) SendRawTransaction(rawtx []byte) (coinID []byte, err error) { 452 msgTx := wire.NewMsgTx() 453 if err = msgTx.Deserialize(bytes.NewReader(rawtx)); err != nil { 454 return nil, err 455 } 456 457 var hash *chainhash.Hash 458 hash, err = dcr.node.SendRawTransaction(dcr.ctx, msgTx, false) // or allow high fees? 459 if err != nil { 460 return 461 } 462 coinID = toCoinID(hash, 0) 463 return 464 } 465 466 // Contract is part of the asset.Backend interface. An asset.Contract is an 467 // output that has been validated as a swap contract for the passed redeem 468 // script. A spendable output is one that can be spent in the next block. Every 469 // regular-tree output from a non-coinbase transaction is spendable immediately. 470 // Coinbase and stake tree outputs are only spendable after CoinbaseMaturity 471 // confirmations. Pubkey scripts can be P2PKH or P2SH in either regular- or 472 // stake-tree flavor. P2PKH supports two alternative signatures, Schnorr and 473 // Edwards. Multi-sig P2SH redeem scripts are supported as well. 474 func (dcr *Backend) Contract(coinID []byte, redeemScript []byte) (*asset.Contract, error) { 475 txHash, vout, err := decodeCoinID(coinID) 476 if err != nil { 477 return nil, fmt.Errorf("error decoding coin ID %x: %w", coinID, err) 478 } 479 480 op, err := dcr.output(txHash, vout, redeemScript) 481 if err != nil { 482 return nil, err 483 } 484 485 return auditContract(op) 486 } 487 488 // ValidateSecret checks that the secret satisfies the contract. 489 func (dcr *Backend) ValidateSecret(secret, contract []byte) bool { 490 _, _, _, secretHash, err := dexdcr.ExtractSwapDetails(contract, chainParams) 491 if err != nil { 492 dcr.log.Errorf("ValidateSecret->ExtractSwapDetails error: %v\n", err) 493 return false 494 } 495 h := sha256.Sum256(secret) 496 return bytes.Equal(h[:], secretHash) 497 } 498 499 // Synced is true if the blockchain is ready for action. 500 func (dcr *Backend) Synced() (bool, error) { 501 // With ws autoreconnect enabled, requests hang when backend is 502 // disconnected. 503 ctx, cancel := context.WithTimeout(dcr.ctx, 2*time.Second) 504 defer cancel() 505 chainInfo, err := dcr.node.GetBlockChainInfo(ctx) 506 if err != nil { 507 return false, fmt.Errorf("GetBlockChainInfo error: %w", translateRPCCancelErr(err)) 508 } 509 return !chainInfo.InitialBlockDownload && chainInfo.Headers-chainInfo.Blocks <= 1, nil 510 } 511 512 // Redemption is an input that redeems a swap contract. 513 func (dcr *Backend) Redemption(redemptionID, contractID, _ []byte) (asset.Coin, error) { 514 txHash, vin, err := decodeCoinID(redemptionID) 515 if err != nil { 516 return nil, fmt.Errorf("error decoding redemption coin ID %x: %w", txHash, err) 517 } 518 input, err := dcr.input(txHash, vin) 519 if err != nil { 520 return nil, err 521 } 522 spends, err := input.spendsCoin(contractID) 523 if err != nil { 524 return nil, err 525 } 526 if !spends { 527 return nil, fmt.Errorf("%x does not spend %x", redemptionID, contractID) 528 } 529 return input, nil 530 } 531 532 // FundingCoin is an unspent output. 533 func (dcr *Backend) FundingCoin(ctx context.Context, coinID []byte, redeemScript []byte) (asset.FundingCoin, error) { 534 txHash, vout, err := decodeCoinID(coinID) 535 if err != nil { 536 return nil, fmt.Errorf("error decoding coin ID %x: %w", coinID, err) 537 } 538 utxo, err := dcr.utxo(ctx, txHash, vout, redeemScript) 539 if err != nil { 540 if isTxNotFoundErr(err) { 541 return nil, asset.CoinNotFoundError 542 } 543 return nil, err 544 } 545 if utxo.nonStandardScript { 546 return nil, fmt.Errorf("non-standard script") 547 } 548 return utxo, nil 549 } 550 551 // ValidateXPub validates the base-58 encoded extended key, and ensures that it 552 // is an extended public, not private, key. 553 func ValidateXPub(xpub string) error { 554 xp, err := hdkeychain.NewKeyFromString(xpub, chainParams) 555 if err != nil { 556 return err 557 } 558 if xp.IsPrivate() { 559 xp.Zero() 560 return fmt.Errorf("extended key is a private key") 561 } 562 return nil 563 } 564 565 func (*Backend) ValidateOrderFunding(swapVal, valSum, _, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool { 566 reqVal := calc.RequiredOrderFunds(swapVal, inputsSize, maxSwaps, dexdcr.InitTxSizeBase, dexdcr.InitTxSize, nfo.MaxFeeRate) 567 return valSum >= reqVal 568 } 569 570 // ValidateCoinID attempts to decode the coinID. 571 func (dcr *Backend) ValidateCoinID(coinID []byte) (string, error) { 572 txid, vout, err := decodeCoinID(coinID) 573 if err != nil { 574 return "", err 575 } 576 return fmt.Sprintf("%v:%d", txid, vout), err 577 } 578 579 // ValidateContract ensures that the swap contract is constructed properly, and 580 // contains valid sender and receiver addresses. 581 func (dcr *Backend) ValidateContract(contract []byte) error { 582 _, _, _, _, err := dexdcr.ExtractSwapDetails(contract, chainParams) 583 return err 584 } 585 586 // CheckSwapAddress checks that the given address is parseable and of the 587 // required type for a swap contract script (p2pkh). 588 func (dcr *Backend) CheckSwapAddress(addr string) bool { 589 dcrAddr, err := stdaddr.DecodeAddress(addr, chainParams) 590 if err != nil { 591 dcr.log.Errorf("DecodeAddress error for %s: %v", addr, err) 592 return false 593 } 594 if _, ok := dcrAddr.(*stdaddr.AddressPubKeyHashEcdsaSecp256k1V0); !ok { 595 dcr.log.Errorf("CheckSwapAddress for %s failed: not a pubkey-hash-ecdsa-secp256k1 address (%T)", 596 dcrAddr.String(), dcrAddr) 597 return false 598 } 599 return true 600 } 601 602 // TxData is the raw transaction bytes. SPV clients rebroadcast the transaction 603 // bytes to get around not having a mempool to check. 604 func (dcr *Backend) TxData(coinID []byte) ([]byte, error) { 605 txHash, _, err := decodeCoinID(coinID) 606 if err != nil { 607 return nil, err 608 } 609 stdaddrTx, err := dcr.node.GetRawTransaction(dcr.ctx, txHash) 610 if err != nil { 611 if isTxNotFoundErr(err) { 612 return nil, asset.CoinNotFoundError 613 } 614 return nil, fmt.Errorf("TxData: GetRawTransactionVerbose for txid %s: %w", txHash, err) 615 } 616 return stdaddrTx.MsgTx().Bytes() 617 } 618 619 // VerifyUnspentCoin attempts to verify a coin ID by decoding the coin ID and 620 // retrieving the corresponding UTXO. If the coin is not found or no longer 621 // unspent, an asset.CoinNotFoundError is returned. 622 func (dcr *Backend) VerifyUnspentCoin(ctx context.Context, coinID []byte) error { 623 txHash, vout, err := decodeCoinID(coinID) 624 if err != nil { 625 return fmt.Errorf("error decoding coin ID %x: %w", coinID, err) 626 } 627 txOut, err := dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeRegular, true) // check regular tree first 628 if err == nil && txOut == nil { 629 txOut, err = dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeStake, true) // check stake tree 630 } 631 if err != nil { 632 return fmt.Errorf("GetTxOut (%s:%d): %w", txHash.String(), vout, translateRPCCancelErr(err)) 633 } 634 if txOut == nil { 635 return asset.CoinNotFoundError 636 } 637 return nil 638 } 639 640 // BondVer returns the latest supported bond version. 641 func (dcr *Backend) BondVer() uint16 { 642 return BondVersion 643 } 644 645 // ParseBondTx makes the package-level ParseBondTx pure function accessible via 646 // a Backend instance. This performs basic validation of a serialized 647 // time-locked fidelity bond transaction given the bond's P2SH redeem script. 648 func (*Backend) ParseBondTx(ver uint16, rawTx []byte) (bondCoinID []byte, amt int64, bondAddr string, 649 bondPubKeyHash []byte, lockTime int64, acct account.AccountID, err error) { 650 return ParseBondTx(ver, rawTx) 651 } 652 653 // BondCoin locates a bond transaction output, validates the entire transaction, 654 // and returns the amount, encoded lockTime and account ID, and the 655 // confirmations of the transaction. It is a CoinNotFoundError if the 656 // transaction output is spent. 657 func (dcr *Backend) BondCoin(ctx context.Context, ver uint16, coinID []byte) (amt, lockTime, confs int64, acct account.AccountID, err error) { 658 txHash, vout, errCoin := decodeCoinID(coinID) 659 if errCoin != nil { 660 err = fmt.Errorf("error decoding coin ID %x: %w", coinID, errCoin) 661 return 662 } 663 664 verboseTx, err := dcr.node.GetRawTransactionVerbose(dcr.ctx, txHash) 665 if err != nil { 666 if isTxNotFoundErr(err) { 667 err = asset.CoinNotFoundError 668 } else { 669 err = translateRPCCancelErr(err) 670 } 671 return 672 } 673 674 if int(vout) > len(verboseTx.Vout)-1 { 675 err = fmt.Errorf("invalid output index for tx with %d outputs", len(verboseTx.Vout)) 676 return 677 } 678 679 confs = verboseTx.Confirmations 680 681 // msgTx, err := msgTxFromHex(verboseTx.Hex) 682 rawTx, err := hex.DecodeString(verboseTx.Hex) // ParseBondTx will deserialize to msgTx, so just get the bytes 683 if err != nil { 684 err = fmt.Errorf("failed to decode transaction %s: %w", txHash, err) 685 return 686 } 687 // rawTx, _ := msgTx.Bytes() 688 689 // tree := determineTxTree(msgTx) 690 txOut, err := dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeRegular, true) // check regular tree first 691 if err == nil && txOut == nil { 692 txOut, err = dcr.node.GetTxOut(ctx, txHash, vout, wire.TxTreeStake, true) // check stake tree 693 } 694 if err != nil { 695 err = fmt.Errorf("GetTxOut error for output %s:%d: %w", 696 txHash, vout, translateRPCCancelErr(err)) 697 return 698 } 699 if txOut == nil { // spent == invalid bond 700 err = asset.CoinNotFoundError 701 return 702 } 703 704 _, amt, _, _, lockTime, acct, err = ParseBondTx(ver, rawTx) 705 return 706 } 707 708 // FeeCoin gets the recipient address, value, and confirmations of a transaction 709 // output encoded by the given coinID. A non-nil error is returned if the 710 // output's pubkey script is not a non-stake P2PKH requiring a single 711 // ECDSA-secp256k1 signature. 712 func (dcr *Backend) FeeCoin(coinID []byte) (addr string, val uint64, confs int64, err error) { 713 txHash, vout, errCoin := decodeCoinID(coinID) 714 if errCoin != nil { 715 err = fmt.Errorf("error decoding coin ID %x: %w", coinID, errCoin) 716 return 717 } 718 719 var txOut *txOutData 720 txOut, confs, err = dcr.outputSummary(txHash, vout) 721 if err != nil { 722 return 723 } 724 725 // No stake outputs, and no multisig. 726 if len(txOut.addresses) != 1 || txOut.sigsRequired != 1 || 727 txOut.scriptType&dexdcr.ScriptStake != 0 { 728 return "", 0, -1, dex.UnsupportedScriptError 729 } 730 731 // Needs to work for legacy fee and new bond txns. 732 switch txOut.scriptType { 733 case dexdcr.ScriptP2SH, dexdcr.ScriptP2PKH: 734 default: 735 return "", 0, -1, dex.UnsupportedScriptError 736 } 737 738 addr = txOut.addresses[0] 739 val = txOut.value 740 741 return 742 } 743 744 // txOutData is transaction output data, including recipient addresses, value, 745 // script type, and number of required signatures. 746 type txOutData struct { 747 value uint64 748 addresses []string 749 sigsRequired int 750 scriptType dexdcr.ScriptType 751 } 752 753 // outputSummary gets transaction output data, including recipient addresses, 754 // value, script type, and number of required signatures, plus the current 755 // confirmations of a transaction output. If the output does not exist, an error 756 // will be returned. Non-standard scripts are not an error. 757 func (dcr *Backend) outputSummary(txHash *chainhash.Hash, vout uint32) (txOut *txOutData, confs int64, err error) { 758 var verboseTx *chainjson.TxRawResult 759 verboseTx, err = dcr.node.GetRawTransactionVerbose(dcr.ctx, txHash) 760 if err != nil { 761 if isTxNotFoundErr(err) { 762 err = asset.CoinNotFoundError 763 } else { 764 err = translateRPCCancelErr(err) 765 } 766 return 767 } 768 769 if int(vout) > len(verboseTx.Vout)-1 { 770 err = fmt.Errorf("invalid output index for tx with %d outputs", len(verboseTx.Vout)) 771 return 772 } 773 774 out := verboseTx.Vout[vout] 775 776 script, err := hex.DecodeString(out.ScriptPubKey.Hex) 777 if err != nil { 778 return nil, -1, dex.UnsupportedScriptError 779 } 780 scriptType, addrs, numRequired := dexdcr.ExtractScriptData(out.ScriptPubKey.Version, script, chainParams) 781 txOut = &txOutData{ 782 value: toAtoms(out.Value), 783 addresses: addrs, // out.ScriptPubKey.Addresses 784 sigsRequired: numRequired, // out.ScriptPubKey.ReqSigs 785 scriptType: scriptType, // integer representation of the string in out.ScriptPubKey.Type 786 } 787 788 confs = verboseTx.Confirmations 789 return 790 } 791 792 // Get the Tx. Transaction info is not cached, so every call will result in a 793 // GetRawTransactionVerbose RPC call. 794 func (dcr *Backend) transaction(txHash *chainhash.Hash, verboseTx *chainjson.TxRawResult) (*Tx, error) { 795 // Figure out if it's a stake transaction 796 msgTx, err := msgTxFromHex(verboseTx.Hex) 797 if err != nil { 798 return nil, fmt.Errorf("failed to decode MsgTx from hex for transaction %s: %w", txHash, err) 799 } 800 isStake := determineTxTree(msgTx) == wire.TxTreeStake 801 802 // If it's not a mempool transaction, get and cache the block data. 803 var blockHash *chainhash.Hash 804 var lastLookup *chainhash.Hash 805 if verboseTx.BlockHash == "" { 806 tipHash := dcr.blockCache.tipHash() 807 if tipHash != zeroHash { 808 lastLookup = &tipHash 809 } 810 } else { 811 blockHash, err = chainhash.NewHashFromStr(verboseTx.BlockHash) 812 if err != nil { 813 return nil, fmt.Errorf("error decoding block hash %s for tx %s: %w", verboseTx.BlockHash, txHash, err) 814 } 815 // Make sure the block info is cached. 816 _, err := dcr.getDcrBlock(dcr.ctx, blockHash) 817 if err != nil { 818 return nil, fmt.Errorf("error caching the block data for transaction %s", txHash) 819 } 820 } 821 822 var sumIn, sumOut uint64 823 // Parse inputs and outputs, grabbing only what's needed. 824 inputs := make([]txIn, 0, len(verboseTx.Vin)) 825 var isCoinbase bool 826 for _, input := range verboseTx.Vin { 827 isCoinbase = input.Coinbase != "" 828 value := toAtoms(input.AmountIn) 829 sumIn += value 830 hash, err := chainhash.NewHashFromStr(input.Txid) 831 if err != nil { 832 return nil, fmt.Errorf("error decoding previous tx hash %s for tx %s: %w", input.Txid, txHash, err) 833 } 834 inputs = append(inputs, txIn{prevTx: *hash, vout: input.Vout, value: value}) 835 } 836 837 outputs := make([]txOut, 0, len(verboseTx.Vout)) 838 for vout, output := range verboseTx.Vout { 839 pkScript, err := hex.DecodeString(output.ScriptPubKey.Hex) 840 if err != nil { 841 return nil, fmt.Errorf("error decoding pubkey script from %s for transaction %d:%d: %w", 842 output.ScriptPubKey.Hex, txHash, vout, err) 843 } 844 sumOut += toAtoms(output.Value) 845 outputs = append(outputs, txOut{ 846 value: toAtoms(output.Value), 847 version: output.ScriptPubKey.Version, 848 pkScript: pkScript, 849 }) 850 } 851 rawTx, err := hex.DecodeString(verboseTx.Hex) 852 if err != nil { 853 return nil, fmt.Errorf("error decoding tx hex: %w", err) 854 } 855 856 feeRate := (sumIn - sumOut) / uint64(len(verboseTx.Hex)/2) 857 if isCoinbase { 858 feeRate = 0 859 } 860 return newTransaction(txHash, blockHash, lastLookup, verboseTx.BlockHeight, isStake, isCoinbase, inputs, outputs, feeRate, rawTx), nil 861 } 862 863 // run processes the queue and monitors the application context. 864 func (dcr *Backend) run(ctx context.Context) { 865 var wg sync.WaitGroup 866 wg.Add(1) 867 // Shut down the RPC client on ctx.Done(). 868 go func() { 869 <-ctx.Done() 870 dcr.shutdown() 871 wg.Done() 872 }() 873 874 blockPoll := time.NewTicker(blockPollInterval) 875 defer blockPoll.Stop() 876 addBlock := func(block *chainjson.GetBlockVerboseResult, reorg bool) { 877 _, err := dcr.blockCache.add(block) 878 if err != nil { 879 dcr.log.Errorf("error adding new best block to cache: %v", err) 880 } 881 dcr.signalMtx.Lock() 882 dcr.log.Tracef("Notifying %d dcr asset consumers of new block at height %d", 883 len(dcr.blockChans), block.Height) 884 for c := range dcr.blockChans { 885 select { 886 case c <- &asset.BlockUpdate{ 887 Err: nil, 888 Reorg: reorg, 889 }: 890 default: 891 // Commented to try sends on future blocks. 892 // close(c) 893 // delete(dcr.blockChans, c) 894 // 895 // TODO: Allow the receiver (e.g. Swapper.Run) to inform done 896 // status so the channels can be retired cleanly rather than 897 // trying them forever. 898 } 899 } 900 dcr.signalMtx.Unlock() 901 } 902 903 sendErr := func(err error) { 904 dcr.log.Error(err) 905 dcr.signalMtx.Lock() 906 for c := range dcr.blockChans { 907 select { 908 case c <- &asset.BlockUpdate{ 909 Err: err, 910 }: 911 default: 912 dcr.log.Errorf("failed to send sending block update on blocking channel") 913 // close(c) 914 // delete(dcr.blockChans, c) 915 } 916 } 917 dcr.signalMtx.Unlock() 918 } 919 920 sendErrFmt := func(s string, a ...any) { 921 sendErr(fmt.Errorf(s, a...)) 922 } 923 924 out: 925 for { 926 select { 927 case <-blockPoll.C: 928 tip := dcr.blockCache.tip() 929 bestHash, err := dcr.node.GetBestBlockHash(ctx) 930 if err != nil { 931 sendErr(asset.NewConnectionError("error retrieving best block: %w", translateRPCCancelErr(err))) 932 continue 933 } 934 if *bestHash == tip.hash { 935 continue 936 } 937 938 best := bestHash.String() 939 block, err := dcr.node.GetBlockVerbose(ctx, bestHash, false) 940 if err != nil { 941 sendErrFmt("error retrieving block %s: %w", best, translateRPCCancelErr(err)) 942 continue 943 } 944 // If this doesn't build on the best known block, look for a reorg. 945 prevHash, err := chainhash.NewHashFromStr(block.PreviousHash) 946 if err != nil { 947 sendErrFmt("error parsing previous hash %s: %w", block.PreviousHash, err) 948 continue 949 } 950 // If it builds on the best block or the cache is empty, it's good to add. 951 if *prevHash == tip.hash || tip.height == 0 { 952 dcr.log.Debugf("New block %s (%d)", bestHash, block.Height) 953 addBlock(block, false) 954 continue 955 } 956 957 // It is either a reorg, or the previous block is not the cached 958 // best block. Crawl blocks backwards until finding a mainchain 959 // block, flagging blocks from the cache as orphans along the way. 960 iHash := &tip.hash 961 reorgHeight := int64(0) 962 for { 963 if *iHash == zeroHash { 964 break 965 } 966 iBlock, err := dcr.node.GetBlockVerbose(ctx, iHash, false) 967 if err != nil { 968 sendErrFmt("error retrieving block %s: %w", iHash, translateRPCCancelErr(err)) 969 break 970 } 971 if iBlock.Confirmations > -1 { 972 // This is a mainchain block, nothing to do. 973 break 974 } 975 if iBlock.Height == 0 { 976 break 977 } 978 reorgHeight = iBlock.Height 979 iHash, err = chainhash.NewHashFromStr(iBlock.PreviousHash) 980 if err != nil { 981 sendErrFmt("error decoding previous hash %s for block %s: %w", 982 iBlock.PreviousHash, iHash.String(), translateRPCCancelErr(err)) 983 // Some blocks on the side chain may not be flagged as 984 // orphaned, but still proceed, flagging the ones we have 985 // identified and adding the new best block to the cache and 986 // setting it to the best block in the cache. 987 break 988 } 989 } 990 991 var reorg bool 992 if reorgHeight > 0 { 993 reorg = true 994 dcr.log.Infof("Tip change from %s (%d) to %s (%d) detected (reorg or just fast blocks).", 995 tip.hash, tip.height, bestHash, block.Height) 996 dcr.blockCache.reorg(reorgHeight) 997 } 998 999 // Now add the new block. 1000 addBlock(block, reorg) 1001 1002 case <-ctx.Done(): 1003 break out 1004 } 1005 } 1006 // Wait for the RPC client to shut down. 1007 wg.Wait() 1008 } 1009 1010 // blockInfo returns block information for the verbose transaction data. The 1011 // current tip hash is also returned as a convenience. 1012 func (dcr *Backend) blockInfo(ctx context.Context, verboseTx *chainjson.TxRawResult) (blockHeight uint32, blockHash chainhash.Hash, tipHash *chainhash.Hash, err error) { 1013 blockHeight = uint32(verboseTx.BlockHeight) 1014 tip := dcr.blockCache.tipHash() 1015 if tip != zeroHash { 1016 tipHash = &tip 1017 } 1018 // Assumed to be valid while in mempool, so skip the validity check. 1019 if verboseTx.Confirmations > 0 { 1020 if blockHeight == 0 { 1021 err = fmt.Errorf("zero block height for output with "+ 1022 "non-zero confirmation count (%s has %d confirmations)", verboseTx.Txid, verboseTx.Confirmations) 1023 return 1024 } 1025 var blk *dcrBlock 1026 blk, err = dcr.getBlockInfo(ctx, verboseTx.BlockHash) 1027 if err != nil { 1028 return 1029 } 1030 blockHeight = blk.height 1031 blockHash = blk.hash 1032 } 1033 return 1034 } 1035 1036 // Get the UTXO, populating the block data along the way. 1037 func (dcr *Backend) utxo(ctx context.Context, txHash *chainhash.Hash, vout uint32, redeemScript []byte) (*UTXO, error) { 1038 txOut, verboseTx, pkScript, err := dcr.getTxOutInfo(ctx, txHash, vout) 1039 if err != nil { 1040 return nil, err 1041 } 1042 if len(verboseTx.Vout) <= int(vout) { // shouldn't happen if gettxout worked 1043 return nil, fmt.Errorf("only %d outputs, requested index %d", len(verboseTx.Vout), vout) 1044 } 1045 1046 scriptVersion := txOut.ScriptPubKey.Version // or verboseTx.Vout[vout].Version 1047 inputNfo, err := dexdcr.InputInfo(scriptVersion, pkScript, redeemScript, chainParams) 1048 if err != nil { 1049 return nil, err 1050 } 1051 scriptType := inputNfo.ScriptType 1052 1053 // If it's a pay-to-script-hash, extract the script hash and check it against 1054 // the hash of the user-supplied redeem script. 1055 if scriptType.IsP2SH() { 1056 scriptHash := dexdcr.ExtractScriptHash(scriptVersion, pkScript) 1057 if !bytes.Equal(stdaddr.Hash160(redeemScript), scriptHash) { 1058 return nil, fmt.Errorf("script hash check failed for utxo %s,%d", txHash, vout) 1059 } 1060 } 1061 1062 blockHeight, blockHash, lastLookup, err := dcr.blockInfo(ctx, verboseTx) 1063 if err != nil { 1064 return nil, err 1065 } 1066 1067 // Coinbase, vote, and revocation transactions must mature before spending. 1068 var maturity int64 1069 if scriptType.IsStake() || txOut.Coinbase { 1070 // TODO: this is specific to the output with stake transactions. Must 1071 // check the output type. 1072 maturity = int64(chainParams.CoinbaseMaturity) 1073 } 1074 if txOut.Confirmations < maturity { 1075 return nil, immatureTransactionError 1076 } 1077 1078 tx, err := dcr.transaction(txHash, verboseTx) 1079 if err != nil { 1080 return nil, fmt.Errorf("error fetching verbose transaction data: %w", err) 1081 } 1082 1083 out := &Output{ 1084 TXIO: TXIO{ 1085 dcr: dcr, 1086 tx: tx, 1087 height: blockHeight, 1088 blockHash: blockHash, 1089 maturity: int32(maturity), 1090 lastLookup: lastLookup, 1091 }, 1092 vout: vout, 1093 scriptType: scriptType, 1094 scriptVersion: scriptVersion, 1095 nonStandardScript: inputNfo.NonStandardScript, 1096 pkScript: pkScript, 1097 redeemScript: redeemScript, 1098 numSigs: inputNfo.ScriptAddrs.NRequired, 1099 // The total size associated with the wire.TxIn. 1100 spendSize: inputNfo.Size(), 1101 value: toAtoms(txOut.Value), 1102 } 1103 return &UTXO{out}, nil 1104 } 1105 1106 // newTXIO creates a TXIO for any transaction, spent or unspent. The caller must 1107 // set the maturity field. 1108 func (dcr *Backend) newTXIO(txHash *chainhash.Hash) (*TXIO, int64, error) { 1109 verboseTx, err := dcr.node.GetRawTransactionVerbose(dcr.ctx, txHash) 1110 if err != nil { 1111 if isTxNotFoundErr(err) { 1112 return nil, 0, asset.CoinNotFoundError 1113 } 1114 return nil, 0, fmt.Errorf("newTXIO: GetRawTransactionVerbose for txid %s: %w", txHash, translateRPCCancelErr(err)) 1115 } 1116 tx, err := dcr.transaction(txHash, verboseTx) 1117 if err != nil { 1118 return nil, 0, fmt.Errorf("error fetching verbose transaction data: %w", err) 1119 } 1120 blockHeight, blockHash, lastLookup, err := dcr.blockInfo(dcr.ctx, verboseTx) 1121 if err != nil { 1122 return nil, 0, err 1123 } 1124 return &TXIO{ 1125 dcr: dcr, 1126 tx: tx, 1127 height: blockHeight, 1128 blockHash: blockHash, 1129 // maturity TODO: move this into an output specific type. 1130 lastLookup: lastLookup, 1131 }, verboseTx.Confirmations, nil 1132 } 1133 1134 // input gets the transaction input. 1135 func (dcr *Backend) input(txHash *chainhash.Hash, vin uint32) (*Input, error) { 1136 txio, _, err := dcr.newTXIO(txHash) 1137 if err != nil { 1138 return nil, err 1139 } 1140 if int(vin) >= len(txio.tx.ins) { 1141 return nil, fmt.Errorf("tx %v has %d outputs (no vin %d)", txHash, len(txio.tx.ins), vin) 1142 } 1143 return &Input{ 1144 TXIO: *txio, 1145 vin: vin, 1146 }, nil 1147 } 1148 1149 // output gets the transaction output. 1150 func (dcr *Backend) output(txHash *chainhash.Hash, vout uint32, redeemScript []byte) (*Output, error) { 1151 txio, confs, err := dcr.newTXIO(txHash) 1152 if err != nil { 1153 return nil, err 1154 } 1155 if int(vout) >= len(txio.tx.outs) { 1156 return nil, fmt.Errorf("tx %v has %d outputs (no vout %d)", txHash, len(txio.tx.outs), vout) 1157 } 1158 1159 txOut := txio.tx.outs[vout] 1160 pkScript := txOut.pkScript 1161 inputNfo, err := dexdcr.InputInfo(txOut.version, pkScript, redeemScript, chainParams) 1162 if err != nil { 1163 return nil, err 1164 } 1165 scriptType := inputNfo.ScriptType 1166 1167 // If it's a pay-to-script-hash, extract the script hash and check it against 1168 // the hash of the user-supplied redeem script. 1169 if scriptType.IsP2SH() { 1170 scriptHash := dexdcr.ExtractScriptHash(txOut.version, pkScript) 1171 if !bytes.Equal(stdaddr.Hash160(redeemScript), scriptHash) { 1172 return nil, fmt.Errorf("script hash check failed for output %s:%d", txHash, vout) 1173 } 1174 } 1175 1176 scrAddrs := inputNfo.ScriptAddrs 1177 addresses := make([]string, len(scrAddrs.PubKeys)+len(scrAddrs.PkHashes)) 1178 for i, addr := range append(scrAddrs.PkHashes, scrAddrs.PubKeys...) { 1179 addresses[i] = addr.String() 1180 } 1181 1182 // Coinbase, vote, and revocation transactions must mature before spending. 1183 var maturity int64 1184 if scriptType.IsStake() || txio.tx.isCoinbase { 1185 maturity = int64(chainParams.CoinbaseMaturity) 1186 } 1187 if confs < maturity { 1188 return nil, immatureTransactionError 1189 } 1190 txio.maturity = int32(maturity) 1191 1192 return &Output{ 1193 TXIO: *txio, 1194 vout: vout, 1195 value: txOut.value, 1196 addresses: addresses, 1197 scriptType: scriptType, 1198 nonStandardScript: inputNfo.NonStandardScript, 1199 pkScript: pkScript, 1200 redeemScript: redeemScript, 1201 numSigs: scrAddrs.NRequired, 1202 // The total size associated with the wire.TxIn. 1203 spendSize: inputNfo.Size(), 1204 }, nil 1205 } 1206 1207 // MsgTxFromHex creates a wire.MsgTx by deserializing the hex transaction. 1208 func msgTxFromHex(txhex string) (*wire.MsgTx, error) { 1209 msgTx := wire.NewMsgTx() 1210 if err := msgTx.Deserialize(hex.NewDecoder(strings.NewReader(txhex))); err != nil { 1211 return nil, err 1212 } 1213 return msgTx, nil 1214 } 1215 1216 // Get information for an unspent transaction output. 1217 func (dcr *Backend) getUnspentTxOut(ctx context.Context, txHash *chainhash.Hash, vout uint32, tree int8) (*chainjson.GetTxOutResult, []byte, error) { 1218 txOut, err := dcr.node.GetTxOut(ctx, txHash, vout, tree, true) 1219 if err != nil { 1220 return nil, nil, fmt.Errorf("GetTxOut error for output %s:%d: %w", 1221 txHash, vout, translateRPCCancelErr(err)) 1222 } 1223 if txOut == nil { 1224 return nil, nil, asset.CoinNotFoundError 1225 } 1226 pkScript, err := hex.DecodeString(txOut.ScriptPubKey.Hex) 1227 if err != nil { 1228 return nil, nil, fmt.Errorf("failed to decode pubkey script from '%s' for output %s:%d", txOut.ScriptPubKey.Hex, txHash, vout) 1229 } 1230 return txOut, pkScript, nil 1231 } 1232 1233 // Get information for an unspent transaction output, plus the verbose 1234 // transaction. 1235 func (dcr *Backend) getTxOutInfo(ctx context.Context, txHash *chainhash.Hash, vout uint32) (*chainjson.GetTxOutResult, *chainjson.TxRawResult, []byte, error) { 1236 verboseTx, err := dcr.node.GetRawTransactionVerbose(ctx, txHash) 1237 if err != nil { 1238 if isTxNotFoundErr(err) { // since we're calling gettxout after this, check now 1239 return nil, nil, nil, asset.CoinNotFoundError 1240 } 1241 return nil, nil, nil, fmt.Errorf("getTxOutInfo: GetRawTransactionVerbose for txid %s: %w", txHash, translateRPCCancelErr(err)) 1242 } 1243 msgTx, err := msgTxFromHex(verboseTx.Hex) 1244 if err != nil { 1245 return nil, nil, nil, fmt.Errorf("failed to decode MsgTx from hex for transaction %s: %w", txHash, err) 1246 } 1247 tree := determineTxTree(msgTx) 1248 txOut, pkScript, err := dcr.getUnspentTxOut(ctx, txHash, vout, tree) 1249 if err != nil { 1250 return nil, nil, nil, err 1251 } 1252 return txOut, verboseTx, pkScript, nil 1253 } 1254 1255 // determineTxTree determines if the transaction is in the regular transaction 1256 // tree (wire.TxTreeRegular) or the stake tree (wire.TxTreeStake). 1257 func determineTxTree(msgTx *wire.MsgTx) int8 { 1258 if stake.DetermineTxType(msgTx) != stake.TxTypeRegular { 1259 return wire.TxTreeStake 1260 } 1261 return wire.TxTreeRegular 1262 } 1263 1264 // Get the block information, checking the cache first. Same as 1265 // getDcrBlock, but takes a string argument. 1266 func (dcr *Backend) getBlockInfo(ctx context.Context, blockid string) (*dcrBlock, error) { 1267 blockHash, err := chainhash.NewHashFromStr(blockid) 1268 if err != nil { 1269 return nil, fmt.Errorf("unable to decode block hash from %s", blockid) 1270 } 1271 return dcr.getDcrBlock(ctx, blockHash) 1272 } 1273 1274 // Get the block information, checking the cache first. 1275 func (dcr *Backend) getDcrBlock(ctx context.Context, blockHash *chainhash.Hash) (*dcrBlock, error) { 1276 cachedBlock, found := dcr.blockCache.block(blockHash) 1277 if found { 1278 return cachedBlock, nil 1279 } 1280 blockVerbose, err := dcr.node.GetBlockVerbose(ctx, blockHash, false) 1281 if err != nil { 1282 return nil, fmt.Errorf("error retrieving block %s: %w", blockHash, translateRPCCancelErr(err)) 1283 } 1284 return dcr.blockCache.add(blockVerbose) 1285 } 1286 1287 // Get the mainchain block at the given height, checking the cache first. 1288 func (dcr *Backend) getMainchainDcrBlock(ctx context.Context, height uint32) (*dcrBlock, error) { 1289 cachedBlock, found := dcr.blockCache.atHeight(height) 1290 if found { 1291 return cachedBlock, nil 1292 } 1293 hash, err := dcr.node.GetBlockHash(ctx, int64(height)) 1294 if err != nil { 1295 // Likely not mined yet. Not an error. 1296 return nil, nil 1297 } 1298 return dcr.getDcrBlock(ctx, hash) 1299 } 1300 1301 // connectNodeRPC attempts to create a new websocket connection to a dcrd node 1302 // with the given credentials and notification handlers. 1303 func connectNodeRPC(host, user, pass, cert string) (*rpcclient.Client, error) { 1304 1305 dcrdCerts, err := os.ReadFile(cert) 1306 if err != nil { 1307 return nil, fmt.Errorf("TLS certificate read error: %w", err) 1308 } 1309 1310 config := &rpcclient.ConnConfig{ 1311 Host: host, 1312 Endpoint: "ws", // websocket 1313 User: user, 1314 Pass: pass, 1315 Certificates: dcrdCerts, 1316 } 1317 1318 dcrdClient, err := rpcclient.New(config, nil) 1319 if err != nil { 1320 return nil, fmt.Errorf("Failed to start dcrd RPC client: %w", err) 1321 } 1322 1323 return dcrdClient, nil 1324 } 1325 1326 func connectNodeRelay(host, user, pass string) (*rpcclient.Client, error) { 1327 config := &rpcclient.ConnConfig{ 1328 Host: host, 1329 HTTPPostMode: true, 1330 DisableTLS: true, 1331 User: user, 1332 Pass: pass, 1333 } 1334 1335 dcrdClient, err := rpcclient.New(config, nil) 1336 if err != nil { 1337 return nil, fmt.Errorf("failed to start dcrd RPC client: %w", err) 1338 } 1339 1340 return dcrdClient, nil 1341 } 1342 1343 // decodeCoinID decodes the coin ID into a tx hash and a vin/vout index. 1344 func decodeCoinID(coinID []byte) (*chainhash.Hash, uint32, error) { 1345 if len(coinID) != 36 { 1346 return nil, 0, fmt.Errorf("coin ID wrong length. expected 36, got %d", len(coinID)) 1347 } 1348 var txHash chainhash.Hash 1349 copy(txHash[:], coinID[:32]) 1350 return &txHash, binary.BigEndian.Uint32(coinID[32:]), nil 1351 } 1352 1353 // toCoinID converts the outpoint to a coin ID. 1354 func toCoinID(txHash *chainhash.Hash, vout uint32) []byte { 1355 hashLen := len(txHash) 1356 b := make([]byte, hashLen+4) 1357 copy(b[:hashLen], txHash[:]) 1358 binary.BigEndian.PutUint32(b[hashLen:], vout) 1359 return b 1360 } 1361 1362 // Convert the DCR value to atoms. 1363 func toAtoms(v float64) uint64 { 1364 return uint64(math.Round(v * conventionalConversionFactor)) 1365 } 1366 1367 // isTxNotFoundErr will return true if the error indicates that the requested 1368 // transaction is not known. 1369 func isTxNotFoundErr(err error) bool { 1370 var rpcErr *dcrjson.RPCError 1371 return errors.As(err, &rpcErr) && rpcErr.Code == dcrjson.ErrRPCNoTxInfo 1372 }