decred.org/dcrwallet/v3@v3.1.0/internal/rpc/jsonrpc/methods.go (about) 1 // Copyright (c) 2013-2016 The btcsuite developers 2 // Copyright (c) 2015-2021 The Decred developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package jsonrpc 7 8 import ( 9 "bytes" 10 "context" 11 "crypto/rand" 12 "encoding/base64" 13 "encoding/binary" 14 "encoding/hex" 15 "encoding/json" 16 "fmt" 17 "math/big" 18 "sort" 19 "strconv" 20 "strings" 21 "sync" 22 "time" 23 24 "decred.org/dcrwallet/v3/errors" 25 "decred.org/dcrwallet/v3/internal/loader" 26 "decred.org/dcrwallet/v3/internal/vsp" 27 "decred.org/dcrwallet/v3/p2p" 28 "decred.org/dcrwallet/v3/rpc/client/dcrd" 29 "decred.org/dcrwallet/v3/rpc/jsonrpc/types" 30 "decred.org/dcrwallet/v3/spv" 31 "decred.org/dcrwallet/v3/version" 32 "decred.org/dcrwallet/v3/wallet" 33 "decred.org/dcrwallet/v3/wallet/txauthor" 34 "decred.org/dcrwallet/v3/wallet/txrules" 35 "decred.org/dcrwallet/v3/wallet/txsizes" 36 "decred.org/dcrwallet/v3/wallet/udb" 37 "github.com/decred/dcrd/blockchain/stake/v5" 38 blockchain "github.com/decred/dcrd/blockchain/standalone/v2" 39 "github.com/decred/dcrd/chaincfg/chainhash" 40 "github.com/decred/dcrd/chaincfg/v3" 41 "github.com/decred/dcrd/dcrec" 42 "github.com/decred/dcrd/dcrec/secp256k1/v4" 43 "github.com/decred/dcrd/dcrjson/v4" 44 "github.com/decred/dcrd/dcrutil/v4" 45 "github.com/decred/dcrd/hdkeychain/v3" 46 dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v4" 47 "github.com/decred/dcrd/txscript/v4" 48 "github.com/decred/dcrd/txscript/v4/sign" 49 "github.com/decred/dcrd/txscript/v4/stdaddr" 50 "github.com/decred/dcrd/txscript/v4/stdscript" 51 "github.com/decred/dcrd/wire" 52 "golang.org/x/sync/errgroup" 53 ) 54 55 // API version constants 56 const ( 57 jsonrpcSemverString = "9.0.0" 58 jsonrpcSemverMajor = 9 59 jsonrpcSemverMinor = 0 60 jsonrpcSemverPatch = 0 61 ) 62 63 const ( 64 // sstxCommitmentString is the string to insert when a verbose 65 // transaction output's pkscript type is a ticket commitment. 66 sstxCommitmentString = "sstxcommitment" 67 68 // The assumed output script version is defined to assist with refactoring 69 // to use actual script versions. 70 scriptVersionAssumed = 0 71 ) 72 73 // confirms returns the number of confirmations for a transaction in a block at 74 // height txHeight (or -1 for an unconfirmed tx) given the chain height 75 // curHeight. 76 func confirms(txHeight, curHeight int32) int32 { 77 switch { 78 case txHeight == -1, txHeight > curHeight: 79 return 0 80 default: 81 return curHeight - txHeight + 1 82 } 83 } 84 85 // the registered rpc handlers 86 var handlers = map[string]handler{ 87 "abandontransaction": {fn: (*Server).abandonTransaction}, 88 "accountaddressindex": {fn: (*Server).accountAddressIndex}, 89 "accountsyncaddressindex": {fn: (*Server).accountSyncAddressIndex}, 90 "accountunlocked": {fn: (*Server).accountUnlocked}, 91 "addmultisigaddress": {fn: (*Server).addMultiSigAddress}, 92 "addtransaction": {fn: (*Server).addTransaction}, 93 "auditreuse": {fn: (*Server).auditReuse}, 94 "consolidate": {fn: (*Server).consolidate}, 95 "createmultisig": {fn: (*Server).createMultiSig}, 96 "createnewaccount": {fn: (*Server).createNewAccount}, 97 "createrawtransaction": {fn: (*Server).createRawTransaction}, 98 "createsignature": {fn: (*Server).createSignature}, 99 "disapprovepercent": {fn: (*Server).disapprovePercent}, 100 "discoverusage": {fn: (*Server).discoverUsage}, 101 "dumpprivkey": {fn: (*Server).dumpPrivKey}, 102 "fundrawtransaction": {fn: (*Server).fundRawTransaction}, 103 "getaccount": {fn: (*Server).getAccount}, 104 "getaccountaddress": {fn: (*Server).getAccountAddress}, 105 "getaddressesbyaccount": {fn: (*Server).getAddressesByAccount}, 106 "getbalance": {fn: (*Server).getBalance}, 107 "getbestblock": {fn: (*Server).getBestBlock}, 108 "getbestblockhash": {fn: (*Server).getBestBlockHash}, 109 "getblockcount": {fn: (*Server).getBlockCount}, 110 "getblockhash": {fn: (*Server).getBlockHash}, 111 "getblockheader": {fn: (*Server).getBlockHeader}, 112 "getblock": {fn: (*Server).getBlock}, 113 "getcoinjoinsbyacct": {fn: (*Server).getcoinjoinsbyacct}, 114 "getcurrentnet": {fn: (*Server).getCurrentNet}, 115 "getinfo": {fn: (*Server).getInfo}, 116 "getmasterpubkey": {fn: (*Server).getMasterPubkey}, 117 "getmultisigoutinfo": {fn: (*Server).getMultisigOutInfo}, 118 "getnewaddress": {fn: (*Server).getNewAddress}, 119 "getpeerinfo": {fn: (*Server).getPeerInfo}, 120 "getrawchangeaddress": {fn: (*Server).getRawChangeAddress}, 121 "getreceivedbyaccount": {fn: (*Server).getReceivedByAccount}, 122 "getreceivedbyaddress": {fn: (*Server).getReceivedByAddress}, 123 "getstakeinfo": {fn: (*Server).getStakeInfo}, 124 "gettickets": {fn: (*Server).getTickets}, 125 "gettransaction": {fn: (*Server).getTransaction}, 126 "gettxout": {fn: (*Server).getTxOut}, 127 "getunconfirmedbalance": {fn: (*Server).getUnconfirmedBalance}, 128 "getvotechoices": {fn: (*Server).getVoteChoices}, 129 "getwalletfee": {fn: (*Server).getWalletFee}, 130 "help": {fn: (*Server).help}, 131 "getcfilterv2": {fn: (*Server).getCFilterV2}, 132 "importcfiltersv2": {fn: (*Server).importCFiltersV2}, 133 "importprivkey": {fn: (*Server).importPrivKey}, 134 "importpubkey": {fn: (*Server).importPubKey}, 135 "importscript": {fn: (*Server).importScript}, 136 "importxpub": {fn: (*Server).importXpub}, 137 "listaccounts": {fn: (*Server).listAccounts}, 138 "listaddresstransactions": {fn: (*Server).listAddressTransactions}, 139 "listalltransactions": {fn: (*Server).listAllTransactions}, 140 "listlockunspent": {fn: (*Server).listLockUnspent}, 141 "listreceivedbyaccount": {fn: (*Server).listReceivedByAccount}, 142 "listreceivedbyaddress": {fn: (*Server).listReceivedByAddress}, 143 "listsinceblock": {fn: (*Server).listSinceBlock}, 144 "listtransactions": {fn: (*Server).listTransactions}, 145 "listunspent": {fn: (*Server).listUnspent}, 146 "lockaccount": {fn: (*Server).lockAccount}, 147 "lockunspent": {fn: (*Server).lockUnspent}, 148 "mixaccount": {fn: (*Server).mixAccount}, 149 "mixoutput": {fn: (*Server).mixOutput}, 150 "purchaseticket": {fn: (*Server).purchaseTicket}, 151 "processunmanagedticket": {fn: (*Server).processUnmanagedTicket}, 152 "redeemmultisigout": {fn: (*Server).redeemMultiSigOut}, 153 "redeemmultisigouts": {fn: (*Server).redeemMultiSigOuts}, 154 "renameaccount": {fn: (*Server).renameAccount}, 155 "rescanwallet": {fn: (*Server).rescanWallet}, 156 "sendfrom": {fn: (*Server).sendFrom}, 157 "sendfromtreasury": {fn: (*Server).sendFromTreasury}, 158 "sendmany": {fn: (*Server).sendMany}, 159 "sendrawtransaction": {fn: (*Server).sendRawTransaction}, 160 "sendtoaddress": {fn: (*Server).sendToAddress}, 161 "sendtomultisig": {fn: (*Server).sendToMultiSig}, 162 "sendtotreasury": {fn: (*Server).sendToTreasury}, 163 "setaccountpassphrase": {fn: (*Server).setAccountPassphrase}, 164 "setdisapprovepercent": {fn: (*Server).setDisapprovePercent}, 165 "settreasurypolicy": {fn: (*Server).setTreasuryPolicy}, 166 "settspendpolicy": {fn: (*Server).setTSpendPolicy}, 167 "settxfee": {fn: (*Server).setTxFee}, 168 "setvotechoice": {fn: (*Server).setVoteChoice}, 169 "signmessage": {fn: (*Server).signMessage}, 170 "signrawtransaction": {fn: (*Server).signRawTransaction}, 171 "signrawtransactions": {fn: (*Server).signRawTransactions}, 172 "stakepooluserinfo": {fn: (*Server).stakePoolUserInfo}, 173 "sweepaccount": {fn: (*Server).sweepAccount}, 174 "syncstatus": {fn: (*Server).syncStatus}, 175 "ticketinfo": {fn: (*Server).ticketInfo}, 176 "ticketsforaddress": {fn: (*Server).ticketsForAddress}, 177 "treasurypolicy": {fn: (*Server).treasuryPolicy}, 178 "tspendpolicy": {fn: (*Server).tspendPolicy}, 179 "unlockaccount": {fn: (*Server).unlockAccount}, 180 "validateaddress": {fn: (*Server).validateAddress}, 181 "validatepredcp0005cf": {fn: (*Server).validatePreDCP0005CF}, 182 "verifymessage": {fn: (*Server).verifyMessage}, 183 "version": {fn: (*Server).version}, 184 "walletinfo": {fn: (*Server).walletInfo}, 185 "walletislocked": {fn: (*Server).walletIsLocked}, 186 "walletlock": {fn: (*Server).walletLock}, 187 "walletpassphrase": {fn: (*Server).walletPassphrase}, 188 "walletpassphrasechange": {fn: (*Server).walletPassphraseChange}, 189 "walletpubpassphrasechange": {fn: (*Server).walletPubPassphraseChange}, 190 191 // Unimplemented/unsupported RPCs which may be found in other 192 // cryptocurrency wallets. 193 "backupwallet": {fn: unimplemented, noHelp: true}, 194 "getwalletinfo": {fn: unimplemented, noHelp: true}, 195 "importwallet": {fn: unimplemented, noHelp: true}, 196 "listaddressgroupings": {fn: unimplemented, noHelp: true}, 197 "dumpwallet": {fn: unsupported, noHelp: true}, 198 "encryptwallet": {fn: unsupported, noHelp: true}, 199 "move": {fn: unsupported, noHelp: true}, 200 "setaccount": {fn: unsupported, noHelp: true}, 201 } 202 203 // unimplemented handles an unimplemented RPC request with the 204 // appropriate error. 205 func unimplemented(*Server, context.Context, interface{}) (interface{}, error) { 206 return nil, &dcrjson.RPCError{ 207 Code: dcrjson.ErrRPCUnimplemented, 208 Message: "Method unimplemented", 209 } 210 } 211 212 // unsupported handles a standard bitcoind RPC request which is 213 // unsupported by dcrwallet due to design differences. 214 func unsupported(*Server, context.Context, interface{}) (interface{}, error) { 215 return nil, &dcrjson.RPCError{ 216 Code: -1, 217 Message: "Request unsupported by dcrwallet", 218 } 219 } 220 221 // lazyHandler is a closure over a requestHandler or passthrough request with 222 // the RPC server's wallet and chain server variables as part of the closure 223 // context. 224 type lazyHandler func() (interface{}, *dcrjson.RPCError) 225 226 // lazyApplyHandler looks up the best request handler func for the method, 227 // returning a closure that will execute it with the (required) wallet and 228 // (optional) consensus RPC server. If no handlers are found and the 229 // chainClient is not nil, the returned handler performs RPC passthrough. 230 func lazyApplyHandler(s *Server, ctx context.Context, request *dcrjson.Request) lazyHandler { 231 handlerData, ok := handlers[request.Method] 232 if !ok { 233 return func() (interface{}, *dcrjson.RPCError) { 234 // Attempt RPC passthrough if possible 235 n, ok := s.walletLoader.NetworkBackend() 236 if !ok { 237 return nil, errRPCClientNotConnected 238 } 239 rpc, ok := n.(*dcrd.RPC) 240 if !ok { 241 return nil, rpcErrorf(dcrjson.ErrRPCClientNotConnected, "RPC passthrough requires dcrd RPC synchronization") 242 } 243 var resp json.RawMessage 244 var params = make([]interface{}, len(request.Params)) 245 for i := range request.Params { 246 params[i] = request.Params[i] 247 } 248 err := rpc.Call(ctx, request.Method, &resp, params...) 249 if ctx.Err() != nil { 250 log.Warnf("Canceled RPC method %v invoked by %v: %v", request.Method, remoteAddr(ctx), err) 251 return nil, &dcrjson.RPCError{ 252 Code: dcrjson.ErrRPCMisc, 253 Message: ctx.Err().Error(), 254 } 255 } 256 if err != nil { 257 return nil, convertError(err) 258 } 259 return resp, nil 260 } 261 } 262 263 return func() (interface{}, *dcrjson.RPCError) { 264 params, err := dcrjson.ParseParams(types.Method(request.Method), request.Params) 265 if err != nil { 266 return nil, dcrjson.ErrRPCInvalidRequest 267 } 268 269 defer func() { 270 if err := ctx.Err(); err != nil { 271 log.Warnf("Canceled RPC method %v invoked by %v: %v", request.Method, remoteAddr(ctx), err) 272 } 273 }() 274 resp, err := handlerData.fn(s, ctx, params) 275 if err != nil { 276 return nil, convertError(err) 277 } 278 return resp, nil 279 } 280 } 281 282 // makeResponse makes the JSON-RPC response struct for the result and error 283 // returned by a requestHandler. The returned response is not ready for 284 // marshaling and sending off to a client, but must be 285 func makeResponse(id, result interface{}, err error) dcrjson.Response { 286 idPtr := idPointer(id) 287 if err != nil { 288 return dcrjson.Response{ 289 ID: idPtr, 290 Error: convertError(err), 291 } 292 } 293 resultBytes, err := json.Marshal(result) 294 if err != nil { 295 return dcrjson.Response{ 296 ID: idPtr, 297 Error: &dcrjson.RPCError{ 298 Code: dcrjson.ErrRPCInternal.Code, 299 Message: "Unexpected error marshalling result", 300 }, 301 } 302 } 303 return dcrjson.Response{ 304 ID: idPtr, 305 Result: json.RawMessage(resultBytes), 306 } 307 } 308 309 // abandonTransaction removes an unconfirmed transaction and all dependent 310 // transactions from the wallet. 311 func (s *Server) abandonTransaction(ctx context.Context, icmd interface{}) (interface{}, error) { 312 cmd := icmd.(*types.AbandonTransactionCmd) 313 w, ok := s.walletLoader.LoadedWallet() 314 if !ok { 315 return nil, errUnloadedWallet 316 } 317 318 hash, err := chainhash.NewHashFromStr(cmd.Hash) 319 if err != nil { 320 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 321 } 322 323 err = w.AbandonTransaction(ctx, hash) 324 return nil, err 325 } 326 327 // accountAddressIndex returns the next address index for the passed 328 // account and branch. 329 func (s *Server) accountAddressIndex(ctx context.Context, icmd interface{}) (interface{}, error) { 330 cmd := icmd.(*types.AccountAddressIndexCmd) 331 w, ok := s.walletLoader.LoadedWallet() 332 if !ok { 333 return nil, errUnloadedWallet 334 } 335 336 account, err := w.AccountNumber(ctx, cmd.Account) 337 if err != nil { 338 if errors.Is(err, errors.NotExist) { 339 return nil, errAccountNotFound 340 } 341 return nil, err 342 } 343 344 extChild, intChild, err := w.BIP0044BranchNextIndexes(ctx, account) 345 if err != nil { 346 return nil, err 347 } 348 switch uint32(cmd.Branch) { 349 case udb.ExternalBranch: 350 return extChild, nil 351 case udb.InternalBranch: 352 return intChild, nil 353 default: 354 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "invalid branch %v", cmd.Branch) 355 } 356 } 357 358 // accountSyncAddressIndex synchronizes the address manager and local address 359 // pool for some account and branch to the passed index. If the current pool 360 // index is beyond the passed index, an error is returned. If the passed index 361 // is the same as the current pool index, nothing is returned. If the syncing 362 // is successful, nothing is returned. 363 func (s *Server) accountSyncAddressIndex(ctx context.Context, icmd interface{}) (interface{}, error) { 364 cmd := icmd.(*types.AccountSyncAddressIndexCmd) 365 w, ok := s.walletLoader.LoadedWallet() 366 if !ok { 367 return nil, errUnloadedWallet 368 } 369 370 account, err := w.AccountNumber(ctx, cmd.Account) 371 if err != nil { 372 if errors.Is(err, errors.NotExist) { 373 return nil, errAccountNotFound 374 } 375 return nil, err 376 } 377 378 branch := uint32(cmd.Branch) 379 index := uint32(cmd.Index) 380 381 if index >= hdkeychain.HardenedKeyStart { 382 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 383 "child index %d exceeds the maximum child index for an account", index) 384 } 385 386 // Additional addresses need to be watched. Since addresses are derived 387 // based on the last used address, this RPC no longer changes the child 388 // indexes that new addresses are derived from. 389 return nil, w.SyncLastReturnedAddress(ctx, account, branch, index) 390 } 391 392 // walletPubKeys decodes each encoded key or address to a public key. If the 393 // address is P2PKH, the wallet is queried for the public key. 394 func walletPubKeys(ctx context.Context, w *wallet.Wallet, keys []string) ([][]byte, error) { 395 pubKeys := make([][]byte, len(keys)) 396 397 for i, key := range keys { 398 addr, err := decodeAddress(key, w.ChainParams()) 399 if err != nil { 400 return nil, err 401 } 402 switch addr := addr.(type) { 403 case *stdaddr.AddressPubKeyEcdsaSecp256k1V0: 404 pubKeys[i] = addr.SerializedPubKey() 405 continue 406 } 407 408 a, err := w.KnownAddress(ctx, addr) 409 if err != nil { 410 if errors.Is(err, errors.NotExist) { 411 return nil, errAddressNotInWallet 412 } 413 return nil, err 414 } 415 var pubKey []byte 416 switch a := a.(type) { 417 case wallet.PubKeyHashAddress: 418 pubKey = a.PubKey() 419 default: 420 err = errors.New("address has no associated public key") 421 return nil, rpcError(dcrjson.ErrRPCInvalidAddressOrKey, err) 422 } 423 pubKeys[i] = pubKey 424 } 425 426 return pubKeys, nil 427 } 428 429 // addMultiSigAddress handles an addmultisigaddress request by adding a 430 // multisig address to the given wallet. 431 func (s *Server) addMultiSigAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 432 cmd := icmd.(*types.AddMultisigAddressCmd) 433 // If an account is specified, ensure that is the imported account. 434 if cmd.Account != nil && *cmd.Account != udb.ImportedAddrAccountName { 435 return nil, errNotImportedAccount 436 } 437 438 w, ok := s.walletLoader.LoadedWallet() 439 if !ok { 440 return nil, errUnloadedWallet 441 } 442 443 pubKeyAddrs, err := walletPubKeys(ctx, w, cmd.Keys) 444 if err != nil { 445 return nil, err 446 } 447 script, err := stdscript.MultiSigScriptV0(cmd.NRequired, pubKeyAddrs...) 448 if err != nil { 449 return nil, err 450 } 451 452 err = w.ImportScript(ctx, script) 453 if err != nil && !errors.Is(err, errors.Exist) { 454 return nil, err 455 } 456 457 return stdaddr.NewAddressScriptHashV0(script, w.ChainParams()) 458 } 459 460 func (s *Server) addTransaction(ctx context.Context, icmd interface{}) (interface{}, error) { 461 cmd := icmd.(*types.AddTransactionCmd) 462 w, ok := s.walletLoader.LoadedWallet() 463 if !ok { 464 return nil, errUnloadedWallet 465 } 466 467 blockHash, err := chainhash.NewHashFromStr(cmd.BlockHash) 468 if err != nil { 469 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 470 } 471 472 tx := new(wire.MsgTx) 473 err = tx.Deserialize(hex.NewDecoder(strings.NewReader(cmd.Transaction))) 474 if err != nil { 475 return nil, rpcError(dcrjson.ErrRPCDeserialization, err) 476 } 477 478 err = w.AddTransaction(ctx, tx, blockHash) 479 return nil, err 480 } 481 482 // auditReuse returns an object keying reused addresses to two or more outputs 483 // referencing them. 484 func (s *Server) auditReuse(ctx context.Context, icmd interface{}) (interface{}, error) { 485 cmd := icmd.(*types.AuditReuseCmd) 486 w, ok := s.walletLoader.LoadedWallet() 487 if !ok { 488 return nil, errUnloadedWallet 489 } 490 491 var since int32 492 if cmd.Since != nil { 493 since = *cmd.Since 494 } 495 496 reuse := make(map[string][]string) 497 inRange := make(map[string]struct{}) 498 params := w.ChainParams() 499 err := w.GetTransactions(ctx, func(b *wallet.Block) (bool, error) { 500 for _, tx := range b.Transactions { 501 // Votes and revocations are skipped because they must 502 // only pay to addresses previously committed to by 503 // ticket purchases, and this "address reuse" is 504 // expected. 505 switch tx.Type { 506 case wallet.TransactionTypeVote, wallet.TransactionTypeRevocation: 507 continue 508 } 509 for _, out := range tx.MyOutputs { 510 addr := out.Address.String() 511 outpoints := reuse[addr] 512 outpoint := wire.OutPoint{Hash: *tx.Hash, Index: out.Index} 513 reuse[addr] = append(outpoints, outpoint.String()) 514 if b.Header == nil || int32(b.Header.Height) >= since { 515 inRange[addr] = struct{}{} 516 } 517 } 518 if tx.Type != wallet.TransactionTypeTicketPurchase { 519 continue 520 } 521 ticket := new(wire.MsgTx) 522 err := ticket.Deserialize(bytes.NewReader(tx.Transaction)) 523 if err != nil { 524 return false, err 525 } 526 for i := 1; i < len(ticket.TxOut); i += 2 { // iterate commitments 527 out := ticket.TxOut[i] 528 addr, err := stake.AddrFromSStxPkScrCommitment(out.PkScript, params) 529 if err != nil { 530 return false, err 531 } 532 have, err := w.HaveAddress(ctx, addr) 533 if err != nil { 534 return false, err 535 } 536 if !have { 537 continue 538 } 539 s := addr.String() 540 outpoints := reuse[s] 541 outpoint := wire.OutPoint{Hash: *tx.Hash, Index: uint32(i)} 542 reuse[s] = append(outpoints, outpoint.String()) 543 if b.Header == nil || int32(b.Header.Height) >= since { 544 inRange[s] = struct{}{} 545 } 546 } 547 } 548 return false, nil 549 }, nil, nil) 550 if err != nil { 551 return nil, err 552 } 553 for s, outpoints := range reuse { 554 if len(outpoints) <= 1 { 555 delete(reuse, s) 556 continue 557 } 558 if _, ok := inRange[s]; !ok { 559 delete(reuse, s) 560 } 561 } 562 return reuse, nil 563 } 564 565 // consolidate handles a consolidate request by returning attempting to compress 566 // as many inputs as given and then returning the txHash and error. 567 func (s *Server) consolidate(ctx context.Context, icmd interface{}) (interface{}, error) { 568 cmd := icmd.(*types.ConsolidateCmd) 569 w, ok := s.walletLoader.LoadedWallet() 570 if !ok { 571 return nil, errUnloadedWallet 572 } 573 574 account := uint32(udb.DefaultAccountNum) 575 var err error 576 if cmd.Account != nil { 577 account, err = w.AccountNumber(ctx, *cmd.Account) 578 if err != nil { 579 if errors.Is(err, errors.NotExist) { 580 return nil, errAccountNotFound 581 } 582 return nil, err 583 } 584 } 585 586 // Set change address if specified. 587 var changeAddr stdaddr.Address 588 if cmd.Address != nil { 589 if *cmd.Address != "" { 590 addr, err := decodeAddress(*cmd.Address, w.ChainParams()) 591 if err != nil { 592 return nil, err 593 } 594 changeAddr = addr 595 } 596 } 597 598 // TODO In the future this should take the optional account and 599 // only consolidate UTXOs found within that account. 600 txHash, err := w.Consolidate(ctx, cmd.Inputs, account, changeAddr) 601 if err != nil { 602 return nil, err 603 } 604 605 return txHash.String(), nil 606 } 607 608 // createMultiSig handles an createmultisig request by returning a 609 // multisig address for the given inputs. 610 func (s *Server) createMultiSig(ctx context.Context, icmd interface{}) (interface{}, error) { 611 cmd := icmd.(*types.CreateMultisigCmd) 612 w, ok := s.walletLoader.LoadedWallet() 613 if !ok { 614 return nil, errUnloadedWallet 615 } 616 617 pubKeys, err := walletPubKeys(ctx, w, cmd.Keys) 618 if err != nil { 619 return nil, err 620 } 621 script, err := stdscript.MultiSigScriptV0(cmd.NRequired, pubKeys...) 622 if err != nil { 623 return nil, err 624 } 625 626 address, err := stdaddr.NewAddressScriptHashV0(script, w.ChainParams()) 627 if err != nil { 628 return nil, err 629 } 630 631 return types.CreateMultiSigResult{ 632 Address: address.String(), 633 RedeemScript: hex.EncodeToString(script), 634 }, nil 635 } 636 637 // createRawTransaction handles createrawtransaction commands. 638 func (s *Server) createRawTransaction(ctx context.Context, icmd interface{}) (interface{}, error) { 639 cmd := icmd.(*types.CreateRawTransactionCmd) 640 641 // Validate expiry, if given. 642 if cmd.Expiry != nil && *cmd.Expiry < 0 { 643 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "Expiry out of range") 644 } 645 646 // Validate the locktime, if given. 647 if cmd.LockTime != nil && 648 (*cmd.LockTime < 0 || 649 *cmd.LockTime > int64(wire.MaxTxInSequenceNum)) { 650 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "Locktime out of range") 651 } 652 653 // Add all transaction inputs to a new transaction after performing 654 // some validity checks. 655 mtx := wire.NewMsgTx() 656 for _, input := range cmd.Inputs { 657 txHash, err := chainhash.NewHashFromStr(input.Txid) 658 if err != nil { 659 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 660 } 661 662 switch input.Tree { 663 case wire.TxTreeRegular, wire.TxTreeStake: 664 default: 665 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 666 "Tx tree must be regular or stake") 667 } 668 669 amt, err := dcrutil.NewAmount(input.Amount) 670 if err != nil { 671 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 672 } 673 if amt < 0 { 674 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 675 "Positive input amount is required") 676 } 677 678 prevOut := wire.NewOutPoint(txHash, input.Vout, input.Tree) 679 txIn := wire.NewTxIn(prevOut, int64(amt), nil) 680 if cmd.LockTime != nil && *cmd.LockTime != 0 { 681 txIn.Sequence = wire.MaxTxInSequenceNum - 1 682 } 683 mtx.AddTxIn(txIn) 684 } 685 686 // Add all transaction outputs to the transaction after performing 687 // some validity checks. 688 for encodedAddr, amount := range cmd.Amounts { 689 // Decode the provided address. This also ensures the network encoded 690 // with the address matches the network the server is currently on. 691 addr, err := stdaddr.DecodeAddress(encodedAddr, s.activeNet) 692 if err != nil { 693 return nil, rpcErrorf(dcrjson.ErrRPCInvalidAddressOrKey, 694 "Address %q: %v", encodedAddr, err) 695 } 696 697 // Ensure the address is one of the supported types. 698 switch addr.(type) { 699 case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0: 700 case *stdaddr.AddressScriptHashV0: 701 default: 702 return nil, rpcErrorf(dcrjson.ErrRPCInvalidAddressOrKey, 703 "Invalid type: %T", addr) 704 } 705 706 // Create a new script which pays to the provided address. 707 vers, pkScript := addr.PaymentScript() 708 709 atomic, err := dcrutil.NewAmount(amount) 710 if err != nil { 711 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, 712 "New amount: %v", err) 713 } 714 // Ensure amount is in the valid range for monetary amounts. 715 if atomic <= 0 || atomic > dcrutil.MaxAmount { 716 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 717 "Amount outside valid range: %v", atomic) 718 } 719 720 txOut := &wire.TxOut{ 721 Value: int64(atomic), 722 Version: vers, 723 PkScript: pkScript, 724 } 725 mtx.AddTxOut(txOut) 726 } 727 728 // Set the Locktime, if given. 729 if cmd.LockTime != nil { 730 mtx.LockTime = uint32(*cmd.LockTime) 731 } 732 733 // Set the Expiry, if given. 734 if cmd.Expiry != nil { 735 mtx.Expiry = uint32(*cmd.Expiry) 736 } 737 738 // Return the serialized and hex-encoded transaction. 739 sb := new(strings.Builder) 740 err := mtx.Serialize(hex.NewEncoder(sb)) 741 if err != nil { 742 return nil, err 743 } 744 return sb.String(), nil 745 } 746 747 // createSignature creates a signature using the private key of a wallet 748 // address for a transaction input script. The serialized compressed public 749 // key of the address is also returned. 750 func (s *Server) createSignature(ctx context.Context, icmd interface{}) (interface{}, error) { 751 cmd := icmd.(*types.CreateSignatureCmd) 752 w, ok := s.walletLoader.LoadedWallet() 753 if !ok { 754 return nil, errUnloadedWallet 755 } 756 757 serializedTx, err := hex.DecodeString(cmd.SerializedTransaction) 758 if err != nil { 759 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 760 } 761 762 var tx wire.MsgTx 763 err = tx.Deserialize(bytes.NewReader(serializedTx)) 764 if err != nil { 765 return nil, rpcError(dcrjson.ErrRPCDeserialization, err) 766 } 767 768 if cmd.InputIndex >= len(tx.TxIn) { 769 return nil, rpcErrorf(dcrjson.ErrRPCMisc, 770 "transaction input %d does not exist", cmd.InputIndex) 771 } 772 773 addr, err := decodeAddress(cmd.Address, w.ChainParams()) 774 if err != nil { 775 return nil, err 776 } 777 778 hashType := txscript.SigHashType(cmd.HashType) 779 prevOutScript, err := hex.DecodeString(cmd.PreviousPkScript) 780 if err != nil { 781 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 782 } 783 784 sig, pubkey, err := w.CreateSignature(ctx, &tx, uint32(cmd.InputIndex), 785 addr, hashType, prevOutScript) 786 if err != nil { 787 return nil, err 788 } 789 790 return &types.CreateSignatureResult{ 791 Signature: hex.EncodeToString(sig), 792 PublicKey: hex.EncodeToString(pubkey), 793 }, nil 794 } 795 796 // disapprovePercent returns the wallets current disapprove percentage. 797 func (s *Server) disapprovePercent(ctx context.Context, _ interface{}) (interface{}, error) { 798 w, ok := s.walletLoader.LoadedWallet() 799 if !ok { 800 return nil, errUnloadedWallet 801 } 802 return w.DisapprovePercent(), nil 803 } 804 805 func (s *Server) discoverUsage(ctx context.Context, icmd interface{}) (interface{}, error) { 806 cmd := icmd.(*types.DiscoverUsageCmd) 807 w, ok := s.walletLoader.LoadedWallet() 808 if !ok { 809 return nil, errUnloadedWallet 810 } 811 812 n, ok := s.walletLoader.NetworkBackend() 813 if !ok { 814 return nil, errNoNetwork 815 } 816 817 startBlock := w.ChainParams().GenesisHash 818 if cmd.StartBlock != nil { 819 h, err := chainhash.NewHashFromStr(*cmd.StartBlock) 820 if err != nil { 821 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "startblock: %v", err) 822 } 823 startBlock = *h 824 } 825 discoverAccounts := cmd.DiscoverAccounts != nil && *cmd.DiscoverAccounts 826 827 gapLimit := w.GapLimit() 828 if cmd.GapLimit != nil { 829 gapLimit = *cmd.GapLimit 830 } 831 832 err := w.DiscoverActiveAddresses(ctx, n, &startBlock, discoverAccounts, gapLimit) 833 return nil, err 834 } 835 836 // dumpPrivKey handles a dumpprivkey request with the private key 837 // for a single address, or an appropriate error if the wallet 838 // is locked. 839 func (s *Server) dumpPrivKey(ctx context.Context, icmd interface{}) (interface{}, error) { 840 cmd := icmd.(*types.DumpPrivKeyCmd) 841 w, ok := s.walletLoader.LoadedWallet() 842 if !ok { 843 return nil, errUnloadedWallet 844 } 845 846 addr, err := decodeAddress(cmd.Address, w.ChainParams()) 847 if err != nil { 848 return nil, err 849 } 850 851 key, err := w.DumpWIFPrivateKey(ctx, addr) 852 if err != nil { 853 if errors.Is(err, errors.Locked) { 854 return nil, errWalletUnlockNeeded 855 } 856 return nil, err 857 } 858 return key, nil 859 } 860 861 func (s *Server) fundRawTransaction(ctx context.Context, icmd interface{}) (interface{}, error) { 862 cmd := icmd.(*types.FundRawTransactionCmd) 863 w, ok := s.walletLoader.LoadedWallet() 864 if !ok { 865 return nil, errUnloadedWallet 866 } 867 868 var ( 869 changeAddress string 870 feeRate = w.RelayFee() 871 confs = int32(1) 872 ) 873 if cmd.Options != nil { 874 opts := cmd.Options 875 var err error 876 if opts.ChangeAddress != nil { 877 changeAddress = *opts.ChangeAddress 878 } 879 if opts.FeeRate != nil { 880 feeRate, err = dcrutil.NewAmount(*opts.FeeRate) 881 if err != nil { 882 return nil, err 883 } 884 } 885 if opts.ConfTarget != nil { 886 confs = *opts.ConfTarget 887 if confs < 0 { 888 return nil, errors.New("confs must be non-negative") 889 } 890 } 891 } 892 893 tx := new(wire.MsgTx) 894 err := tx.Deserialize(hex.NewDecoder(strings.NewReader(cmd.HexString))) 895 if err != nil { 896 return nil, err 897 } 898 // Existing inputs are problematic. Without information about 899 // how large the input scripts will be once signed, the wallet is 900 // unable to perform correct fee estimation. If fundrawtransaction 901 // is changed later to work on a PSDT structure that includes this 902 // information, this functionality may be enabled. For now, prevent 903 // the method from continuing. 904 if len(tx.TxIn) != 0 { 905 return nil, errors.New("transaction must not already have inputs") 906 } 907 908 accountNum, err := w.AccountNumber(ctx, cmd.FundAccount) 909 if err != nil { 910 return nil, err 911 } 912 913 // Because there are no other inputs, a new transaction can be created. 914 var changeSource txauthor.ChangeSource 915 if changeAddress != "" { 916 var err error 917 changeSource, err = makeScriptChangeSource(changeAddress, w.ChainParams()) 918 if err != nil { 919 return nil, err 920 } 921 } 922 atx, err := w.NewUnsignedTransaction(ctx, tx.TxOut, feeRate, accountNum, confs, 923 wallet.OutputSelectionAlgorithmDefault, changeSource, nil) 924 if err != nil { 925 return nil, err 926 } 927 928 // Include chosen inputs and change output (if any) in decoded 929 // transaction. 930 tx.TxIn = atx.Tx.TxIn 931 if atx.ChangeIndex >= 0 { 932 tx.TxOut = append(tx.TxOut, atx.Tx.TxOut[atx.ChangeIndex]) 933 } 934 935 // Determine the absolute fee of the funded transaction 936 fee := atx.TotalInput 937 for i := range tx.TxOut { 938 fee -= dcrutil.Amount(tx.TxOut[i].Value) 939 } 940 941 b := new(strings.Builder) 942 b.Grow(2 * tx.SerializeSize()) 943 err = tx.Serialize(hex.NewEncoder(b)) 944 if err != nil { 945 return nil, err 946 } 947 res := &types.FundRawTransactionResult{ 948 Hex: b.String(), 949 Fee: fee.ToCoin(), 950 } 951 return res, nil 952 } 953 954 // getAddressesByAccount handles a getaddressesbyaccount request by returning 955 // all addresses for an account, or an error if the requested account does 956 // not exist. 957 func (s *Server) getAddressesByAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 958 cmd := icmd.(*types.GetAddressesByAccountCmd) 959 w, ok := s.walletLoader.LoadedWallet() 960 if !ok { 961 return nil, errUnloadedWallet 962 } 963 964 if cmd.Account == "imported" { 965 addrs, err := w.ImportedAddresses(ctx, cmd.Account) 966 if err != nil { 967 return nil, err 968 } 969 return knownAddressMarshaler(addrs), nil 970 } 971 972 account, err := w.AccountNumber(ctx, cmd.Account) 973 if err != nil { 974 if errors.Is(err, errors.NotExist) { 975 return nil, errAccountNotFound 976 } 977 return nil, err 978 } 979 980 xpub, err := w.AccountXpub(ctx, account) 981 if err != nil { 982 return nil, err 983 } 984 extBranch, err := xpub.Child(0) 985 if err != nil { 986 return nil, err 987 } 988 intBranch, err := xpub.Child(1) 989 if err != nil { 990 return nil, err 991 } 992 endExt, endInt, err := w.BIP0044BranchNextIndexes(ctx, account) 993 if err != nil { 994 return nil, err 995 } 996 params := w.ChainParams() 997 addrs := make([]string, 0, endExt+endInt) 998 appendAddrs := func(branchKey *hdkeychain.ExtendedKey, n uint32) error { 999 for i := uint32(0); i < n; i++ { 1000 child, err := branchKey.Child(i) 1001 if errors.Is(err, hdkeychain.ErrInvalidChild) { 1002 continue 1003 } 1004 if err != nil { 1005 return err 1006 } 1007 pkh := dcrutil.Hash160(child.SerializedPubKey()) 1008 addr, _ := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0( 1009 pkh, params) 1010 addrs = append(addrs, addr.String()) 1011 } 1012 return nil 1013 } 1014 err = appendAddrs(extBranch, endExt) 1015 if err != nil { 1016 return nil, err 1017 } 1018 err = appendAddrs(intBranch, endInt) 1019 if err != nil { 1020 return nil, err 1021 } 1022 return addressStringsMarshaler(addrs), nil 1023 } 1024 1025 // getBalance handles a getbalance request by returning the balance for an 1026 // account (wallet), or an error if the requested account does not 1027 // exist. 1028 func (s *Server) getBalance(ctx context.Context, icmd interface{}) (interface{}, error) { 1029 cmd := icmd.(*types.GetBalanceCmd) 1030 w, ok := s.walletLoader.LoadedWallet() 1031 if !ok { 1032 return nil, errUnloadedWallet 1033 } 1034 1035 minConf := int32(*cmd.MinConf) 1036 if minConf < 0 { 1037 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "minconf must be non-negative") 1038 } 1039 1040 accountName := "*" 1041 if cmd.Account != nil { 1042 accountName = *cmd.Account 1043 } 1044 1045 blockHash, _ := w.MainChainTip(ctx) 1046 result := types.GetBalanceResult{ 1047 BlockHash: blockHash.String(), 1048 } 1049 1050 if accountName == "*" { 1051 balances, err := w.AccountBalances(ctx, int32(*cmd.MinConf)) 1052 if err != nil { 1053 return nil, err 1054 } 1055 1056 var ( 1057 totImmatureCoinbase dcrutil.Amount 1058 totImmatureStakegen dcrutil.Amount 1059 totLocked dcrutil.Amount 1060 totSpendable dcrutil.Amount 1061 totUnconfirmed dcrutil.Amount 1062 totVotingAuthority dcrutil.Amount 1063 cumTot dcrutil.Amount 1064 ) 1065 1066 balancesLen := uint32(len(balances)) 1067 result.Balances = make([]types.GetAccountBalanceResult, 0, balancesLen) 1068 1069 for _, bal := range balances { 1070 accountName, err := w.AccountName(ctx, bal.Account) 1071 if err != nil { 1072 // Expect account lookup to succeed 1073 if errors.Is(err, errors.NotExist) { 1074 return nil, rpcError(dcrjson.ErrRPCInternal.Code, err) 1075 } 1076 return nil, err 1077 } 1078 1079 totImmatureCoinbase += bal.ImmatureCoinbaseRewards 1080 totImmatureStakegen += bal.ImmatureStakeGeneration 1081 totLocked += bal.LockedByTickets 1082 totSpendable += bal.Spendable 1083 totUnconfirmed += bal.Unconfirmed 1084 totVotingAuthority += bal.VotingAuthority 1085 cumTot += bal.Total 1086 1087 json := types.GetAccountBalanceResult{ 1088 AccountName: accountName, 1089 ImmatureCoinbaseRewards: bal.ImmatureCoinbaseRewards.ToCoin(), 1090 ImmatureStakeGeneration: bal.ImmatureStakeGeneration.ToCoin(), 1091 LockedByTickets: bal.LockedByTickets.ToCoin(), 1092 Spendable: bal.Spendable.ToCoin(), 1093 Total: bal.Total.ToCoin(), 1094 Unconfirmed: bal.Unconfirmed.ToCoin(), 1095 VotingAuthority: bal.VotingAuthority.ToCoin(), 1096 } 1097 1098 result.Balances = append(result.Balances, json) 1099 } 1100 1101 result.TotalImmatureCoinbaseRewards = totImmatureCoinbase.ToCoin() 1102 result.TotalImmatureStakeGeneration = totImmatureStakegen.ToCoin() 1103 result.TotalLockedByTickets = totLocked.ToCoin() 1104 result.TotalSpendable = totSpendable.ToCoin() 1105 result.TotalUnconfirmed = totUnconfirmed.ToCoin() 1106 result.TotalVotingAuthority = totVotingAuthority.ToCoin() 1107 result.CumulativeTotal = cumTot.ToCoin() 1108 } else { 1109 account, err := w.AccountNumber(ctx, accountName) 1110 if err != nil { 1111 if errors.Is(err, errors.NotExist) { 1112 return nil, errAccountNotFound 1113 } 1114 return nil, err 1115 } 1116 1117 bal, err := w.AccountBalance(ctx, account, int32(*cmd.MinConf)) 1118 if err != nil { 1119 // Expect account lookup to succeed 1120 if errors.Is(err, errors.NotExist) { 1121 return nil, rpcError(dcrjson.ErrRPCInternal.Code, err) 1122 } 1123 return nil, err 1124 } 1125 json := types.GetAccountBalanceResult{ 1126 AccountName: accountName, 1127 ImmatureCoinbaseRewards: bal.ImmatureCoinbaseRewards.ToCoin(), 1128 ImmatureStakeGeneration: bal.ImmatureStakeGeneration.ToCoin(), 1129 LockedByTickets: bal.LockedByTickets.ToCoin(), 1130 Spendable: bal.Spendable.ToCoin(), 1131 Total: bal.Total.ToCoin(), 1132 Unconfirmed: bal.Unconfirmed.ToCoin(), 1133 VotingAuthority: bal.VotingAuthority.ToCoin(), 1134 } 1135 result.Balances = append(result.Balances, json) 1136 } 1137 1138 return result, nil 1139 } 1140 1141 // getBestBlock handles a getbestblock request by returning a JSON object 1142 // with the height and hash of the most recently processed block. 1143 func (s *Server) getBestBlock(ctx context.Context, icmd interface{}) (interface{}, error) { 1144 w, ok := s.walletLoader.LoadedWallet() 1145 if !ok { 1146 return nil, errUnloadedWallet 1147 } 1148 1149 hash, height := w.MainChainTip(ctx) 1150 result := &dcrdtypes.GetBestBlockResult{ 1151 Hash: hash.String(), 1152 Height: int64(height), 1153 } 1154 return result, nil 1155 } 1156 1157 // getBestBlockHash handles a getbestblockhash request by returning the hash 1158 // of the most recently processed block. 1159 func (s *Server) getBestBlockHash(ctx context.Context, icmd interface{}) (interface{}, error) { 1160 w, ok := s.walletLoader.LoadedWallet() 1161 if !ok { 1162 return nil, errUnloadedWallet 1163 } 1164 1165 hash, _ := w.MainChainTip(ctx) 1166 return hash.String(), nil 1167 } 1168 1169 // getBlockCount handles a getblockcount request by returning the chain height 1170 // of the most recently processed block. 1171 func (s *Server) getBlockCount(ctx context.Context, icmd interface{}) (interface{}, error) { 1172 w, ok := s.walletLoader.LoadedWallet() 1173 if !ok { 1174 return nil, errUnloadedWallet 1175 } 1176 1177 _, height := w.MainChainTip(ctx) 1178 return height, nil 1179 } 1180 1181 // getBlockHash handles a getblockhash request by returning the main chain hash 1182 // for a block at some height. 1183 func (s *Server) getBlockHash(ctx context.Context, icmd interface{}) (interface{}, error) { 1184 cmd := icmd.(*types.GetBlockHashCmd) 1185 w, ok := s.walletLoader.LoadedWallet() 1186 if !ok { 1187 return nil, errUnloadedWallet 1188 } 1189 1190 height := int32(cmd.Index) 1191 id := wallet.NewBlockIdentifierFromHeight(height) 1192 info, err := w.BlockInfo(ctx, id) 1193 if err != nil { 1194 return nil, err 1195 } 1196 return info.Hash.String(), nil 1197 } 1198 1199 // getBlockHeader implements the getblockheader command. 1200 func (s *Server) getBlockHeader(ctx context.Context, icmd interface{}) (interface{}, error) { 1201 cmd := icmd.(*types.GetBlockHeaderCmd) 1202 w, ok := s.walletLoader.LoadedWallet() 1203 if !ok { 1204 return nil, errUnloadedWallet 1205 } 1206 1207 // Attempt RPC passthrough if connected to DCRD. 1208 n, err := w.NetworkBackend() 1209 if err != nil { 1210 return nil, err 1211 } 1212 if rpc, ok := n.(*dcrd.RPC); ok { 1213 var resp json.RawMessage 1214 err := rpc.Call(ctx, "getblockheader", &resp, cmd.Hash, cmd.Verbose) 1215 return resp, err 1216 } 1217 1218 blockHash, err := chainhash.NewHashFromStr(cmd.Hash) 1219 if err != nil { 1220 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 1221 } 1222 1223 blockHeader, err := w.BlockHeader(ctx, blockHash) 1224 if err != nil { 1225 return nil, err 1226 } 1227 1228 // When the verbose flag isn't set, simply return the serialized block 1229 // header as a hex-encoded string. 1230 if cmd.Verbose == nil || !*cmd.Verbose { 1231 var headerBuf bytes.Buffer 1232 err := blockHeader.Serialize(&headerBuf) 1233 if err != nil { 1234 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Could not serialize block header: %v", err) 1235 } 1236 return hex.EncodeToString(headerBuf.Bytes()), nil 1237 } 1238 1239 // The verbose flag is set, so generate the JSON object and return it. 1240 1241 // Get next block hash unless there are none. 1242 var nextHashString string 1243 confirmations := int64(-1) 1244 mainChainHasBlock, _, err := w.BlockInMainChain(ctx, blockHash) 1245 if err != nil { 1246 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Error checking if block is in mainchain: %v", err) 1247 } 1248 if mainChainHasBlock { 1249 blockHeight := int32(blockHeader.Height) 1250 _, bestHeight := w.MainChainTip(ctx) 1251 if blockHeight < bestHeight { 1252 nextBlockID := wallet.NewBlockIdentifierFromHeight(blockHeight + 1) 1253 nextBlockInfo, err := w.BlockInfo(ctx, nextBlockID) 1254 if err != nil { 1255 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Info not found for next block: %v", err) 1256 } 1257 nextHashString = nextBlockInfo.Hash.String() 1258 } 1259 confirmations = int64(confirms(blockHeight, bestHeight)) 1260 } 1261 1262 // Calculate past median time. Look at the last 11 blocks, starting 1263 // with the requested block, which is consistent with dcrd. 1264 iBlkHeader := blockHeader // start with the block header for the requested block 1265 timestamps := make([]int64, 0, 11) 1266 for i := 0; i < cap(timestamps); i++ { 1267 timestamps = append(timestamps, iBlkHeader.Timestamp.Unix()) 1268 if iBlkHeader.Height == 0 { 1269 break 1270 } 1271 iBlkHeader, err = w.BlockHeader(ctx, &iBlkHeader.PrevBlock) 1272 if err != nil { 1273 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Info not found for previous block: %v", err) 1274 } 1275 } 1276 sort.Slice(timestamps, func(i, j int) bool { 1277 return timestamps[i] < timestamps[j] 1278 }) 1279 medianTime := timestamps[len(timestamps)/2] 1280 1281 // Determine the PoW hash. When the v1 PoW hash differs from the 1282 // block hash, this is assumed to be v2 (DCP0011). More advanced 1283 // selection logic will be necessary if the PoW hash changes again in 1284 // the future. 1285 powHash := blockHeader.PowHashV1() 1286 if powHash != *blockHash { 1287 powHash = blockHeader.PowHashV2() 1288 } 1289 1290 return &dcrdtypes.GetBlockHeaderVerboseResult{ 1291 Hash: blockHash.String(), 1292 PowHash: powHash.String(), 1293 Confirmations: confirmations, 1294 Version: blockHeader.Version, 1295 MerkleRoot: blockHeader.MerkleRoot.String(), 1296 StakeRoot: blockHeader.StakeRoot.String(), 1297 VoteBits: blockHeader.VoteBits, 1298 FinalState: hex.EncodeToString(blockHeader.FinalState[:]), 1299 Voters: blockHeader.Voters, 1300 FreshStake: blockHeader.FreshStake, 1301 Revocations: blockHeader.Revocations, 1302 PoolSize: blockHeader.PoolSize, 1303 Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), 1304 SBits: dcrutil.Amount(blockHeader.SBits).ToCoin(), 1305 Height: blockHeader.Height, 1306 Size: blockHeader.Size, 1307 Time: blockHeader.Timestamp.Unix(), 1308 MedianTime: medianTime, 1309 Nonce: blockHeader.Nonce, 1310 ExtraData: hex.EncodeToString(blockHeader.ExtraData[:]), 1311 StakeVersion: blockHeader.StakeVersion, 1312 Difficulty: difficultyRatio(blockHeader.Bits, w.ChainParams()), 1313 ChainWork: "", // unset because wallet is not equipped to easily calculate the cummulative chainwork 1314 PreviousHash: blockHeader.PrevBlock.String(), 1315 NextHash: nextHashString, 1316 }, nil 1317 } 1318 1319 // getBlock implements the getblock command. 1320 func (s *Server) getBlock(ctx context.Context, icmd interface{}) (interface{}, error) { 1321 cmd := icmd.(*types.GetBlockCmd) 1322 w, ok := s.walletLoader.LoadedWallet() 1323 if !ok { 1324 return nil, errUnloadedWallet 1325 } 1326 n, err := w.NetworkBackend() 1327 if err != nil { 1328 return nil, err 1329 } 1330 1331 // Attempt RPC passthrough if connected to DCRD. 1332 if rpc, ok := n.(*dcrd.RPC); ok { 1333 var resp json.RawMessage 1334 err := rpc.Call(ctx, "getblock", &resp, cmd.Hash, cmd.Verbose, cmd.VerboseTx) 1335 return resp, err 1336 } 1337 1338 blockHash, err := chainhash.NewHashFromStr(cmd.Hash) 1339 if err != nil { 1340 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 1341 } 1342 1343 blocks, err := n.Blocks(ctx, []*chainhash.Hash{blockHash}) 1344 if err != nil { 1345 return nil, err 1346 } 1347 if len(blocks) == 0 { 1348 // Should never happen but protects against a possible panic on 1349 // the following code. 1350 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Network returned 0 blocks") 1351 } 1352 1353 blk := blocks[0] 1354 1355 // When the verbose flag isn't set, simply return the 1356 // network-serialized block as a hex-encoded string. 1357 if cmd.Verbose == nil || !*cmd.Verbose { 1358 b := new(strings.Builder) 1359 b.Grow(2 * blk.SerializeSize()) 1360 err = blk.Serialize(hex.NewEncoder(b)) 1361 if err != nil { 1362 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Could not serialize block: %v", err) 1363 } 1364 return b.String(), nil 1365 } 1366 1367 // Get next block hash unless there are none. 1368 var nextHashString string 1369 blockHeader := &blk.Header 1370 confirmations := int64(-1) 1371 mainChainHasBlock, _, err := w.BlockInMainChain(ctx, blockHash) 1372 if err != nil { 1373 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Error checking if block is in mainchain: %v", err) 1374 } 1375 if mainChainHasBlock { 1376 blockHeight := int32(blockHeader.Height) 1377 _, bestHeight := w.MainChainTip(ctx) 1378 if blockHeight < bestHeight { 1379 nextBlockID := wallet.NewBlockIdentifierFromHeight(blockHeight + 1) 1380 nextBlockInfo, err := w.BlockInfo(ctx, nextBlockID) 1381 if err != nil { 1382 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Info not found for next block: %v", err) 1383 } 1384 nextHashString = nextBlockInfo.Hash.String() 1385 } 1386 confirmations = int64(confirms(blockHeight, bestHeight)) 1387 } 1388 1389 // Calculate past median time. Look at the last 11 blocks, starting 1390 // with the requested block, which is consistent with dcrd. 1391 timestamps := make([]int64, 0, 11) 1392 for iBlkHeader := blockHeader; ; { 1393 timestamps = append(timestamps, iBlkHeader.Timestamp.Unix()) 1394 if iBlkHeader.Height == 0 || len(timestamps) == cap(timestamps) { 1395 break 1396 } 1397 iBlkHeader, err = w.BlockHeader(ctx, &iBlkHeader.PrevBlock) 1398 if err != nil { 1399 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Info not found for previous block: %v", err) 1400 } 1401 } 1402 sort.Slice(timestamps, func(i, j int) bool { 1403 return timestamps[i] < timestamps[j] 1404 }) 1405 medianTime := timestamps[len(timestamps)/2] 1406 1407 // Determine the PoW hash. When the v1 PoW hash differs from the 1408 // block hash, this is assumed to be v2 (DCP0011). More advanced 1409 // selection logic will be necessary if the PoW hash changes again in 1410 // the future. 1411 powHash := blockHeader.PowHashV1() 1412 if powHash != *blockHash { 1413 powHash = blockHeader.PowHashV2() 1414 } 1415 1416 sbitsFloat := float64(blockHeader.SBits) / dcrutil.AtomsPerCoin 1417 blockReply := dcrdtypes.GetBlockVerboseResult{ 1418 Hash: cmd.Hash, 1419 PoWHash: powHash.String(), 1420 Version: blockHeader.Version, 1421 MerkleRoot: blockHeader.MerkleRoot.String(), 1422 StakeRoot: blockHeader.StakeRoot.String(), 1423 PreviousHash: blockHeader.PrevBlock.String(), 1424 MedianTime: medianTime, 1425 Nonce: blockHeader.Nonce, 1426 VoteBits: blockHeader.VoteBits, 1427 FinalState: hex.EncodeToString(blockHeader.FinalState[:]), 1428 Voters: blockHeader.Voters, 1429 FreshStake: blockHeader.FreshStake, 1430 Revocations: blockHeader.Revocations, 1431 PoolSize: blockHeader.PoolSize, 1432 Time: blockHeader.Timestamp.Unix(), 1433 StakeVersion: blockHeader.StakeVersion, 1434 Confirmations: confirmations, 1435 Height: int64(blockHeader.Height), 1436 Size: int32(blk.Header.Size), 1437 Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), 1438 SBits: sbitsFloat, 1439 Difficulty: difficultyRatio(blockHeader.Bits, w.ChainParams()), 1440 ChainWork: "", // unset because wallet is not equipped to easily calculate the cummulative chainwork 1441 ExtraData: hex.EncodeToString(blockHeader.ExtraData[:]), 1442 NextHash: nextHashString, 1443 } 1444 1445 // The coinbase must be version 3 once the treasury agenda is active. 1446 isTreasuryEnabled := blk.Transactions[0].Version >= wire.TxVersionTreasury 1447 1448 if cmd.VerboseTx == nil || !*cmd.VerboseTx { 1449 transactions := blk.Transactions 1450 txNames := make([]string, len(transactions)) 1451 for i, tx := range transactions { 1452 txNames[i] = tx.TxHash().String() 1453 } 1454 blockReply.Tx = txNames 1455 1456 stransactions := blk.STransactions 1457 stxNames := make([]string, len(stransactions)) 1458 for i, tx := range stransactions { 1459 stxNames[i] = tx.TxHash().String() 1460 } 1461 blockReply.STx = stxNames 1462 } else { 1463 txns := blk.Transactions 1464 rawTxns := make([]dcrdtypes.TxRawResult, len(txns)) 1465 for i, tx := range txns { 1466 rawTxn, err := createTxRawResult(w.ChainParams(), tx, uint32(i), blockHeader, confirmations, isTreasuryEnabled) 1467 if err != nil { 1468 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Could not create transaction: %v", err) 1469 } 1470 rawTxns[i] = *rawTxn 1471 } 1472 blockReply.RawTx = rawTxns 1473 1474 stxns := blk.STransactions 1475 rawSTxns := make([]dcrdtypes.TxRawResult, len(stxns)) 1476 for i, tx := range stxns { 1477 rawSTxn, err := createTxRawResult(w.ChainParams(), tx, uint32(i), blockHeader, confirmations, isTreasuryEnabled) 1478 if err != nil { 1479 return nil, rpcErrorf(dcrjson.ErrRPCInternal.Code, "Could not create stake transaction: %v", err) 1480 } 1481 rawSTxns[i] = *rawSTxn 1482 } 1483 blockReply.RawSTx = rawSTxns 1484 } 1485 1486 return blockReply, nil 1487 } 1488 1489 func createTxRawResult(chainParams *chaincfg.Params, mtx *wire.MsgTx, blkIdx uint32, blkHeader *wire.BlockHeader, 1490 confirmations int64, isTreasuryEnabled bool) (*dcrdtypes.TxRawResult, error) { 1491 1492 b := new(strings.Builder) 1493 b.Grow(2 * mtx.SerializeSize()) 1494 err := mtx.Serialize(hex.NewEncoder(b)) 1495 if err != nil { 1496 return nil, err 1497 } 1498 1499 txReply := &dcrdtypes.TxRawResult{ 1500 Hex: b.String(), 1501 Txid: mtx.CachedTxHash().String(), 1502 Version: int32(mtx.Version), 1503 LockTime: mtx.LockTime, 1504 Expiry: mtx.Expiry, 1505 Vin: createVinList(mtx, isTreasuryEnabled), 1506 Vout: createVoutList(mtx, chainParams, nil, isTreasuryEnabled), 1507 BlockHash: blkHeader.BlockHash().String(), 1508 BlockHeight: int64(blkHeader.Height), 1509 BlockIndex: blkIdx, 1510 Confirmations: confirmations, 1511 Time: blkHeader.Timestamp.Unix(), 1512 Blocktime: blkHeader.Timestamp.Unix(), // identical to Time in bitcoind too 1513 } 1514 1515 return txReply, nil 1516 } 1517 1518 // createVinList returns a slice of JSON objects for the inputs of the passed 1519 // transaction. 1520 func createVinList(mtx *wire.MsgTx, isTreasuryEnabled bool) []dcrdtypes.Vin { 1521 // Treasurybase transactions only have a single txin by definition. 1522 // 1523 // NOTE: This check MUST come before the coinbase check because a 1524 // treasurybase will be identified as a coinbase as well. 1525 vinList := make([]dcrdtypes.Vin, len(mtx.TxIn)) 1526 if isTreasuryEnabled && blockchain.IsTreasuryBase(mtx) { 1527 txIn := mtx.TxIn[0] 1528 vinEntry := &vinList[0] 1529 vinEntry.Treasurybase = true 1530 vinEntry.Sequence = txIn.Sequence 1531 vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() 1532 vinEntry.BlockHeight = txIn.BlockHeight 1533 vinEntry.BlockIndex = txIn.BlockIndex 1534 return vinList 1535 } 1536 1537 // Coinbase transactions only have a single txin by definition. 1538 if blockchain.IsCoinBaseTx(mtx, isTreasuryEnabled) { 1539 txIn := mtx.TxIn[0] 1540 vinEntry := &vinList[0] 1541 vinEntry.Coinbase = hex.EncodeToString(txIn.SignatureScript) 1542 vinEntry.Sequence = txIn.Sequence 1543 vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() 1544 vinEntry.BlockHeight = txIn.BlockHeight 1545 vinEntry.BlockIndex = txIn.BlockIndex 1546 return vinList 1547 } 1548 1549 // Treasury spend transactions only have a single txin by definition. 1550 if isTreasuryEnabled && stake.IsTSpend(mtx) { 1551 txIn := mtx.TxIn[0] 1552 vinEntry := &vinList[0] 1553 vinEntry.TreasurySpend = hex.EncodeToString(txIn.SignatureScript) 1554 vinEntry.Sequence = txIn.Sequence 1555 vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() 1556 vinEntry.BlockHeight = txIn.BlockHeight 1557 vinEntry.BlockIndex = txIn.BlockIndex 1558 return vinList 1559 } 1560 1561 // Stakebase transactions (votes) have two inputs: a null stake base 1562 // followed by an input consuming a ticket's stakesubmission. 1563 isSSGen := stake.IsSSGen(mtx) 1564 1565 for i, txIn := range mtx.TxIn { 1566 // Handle only the null input of a stakebase differently. 1567 if isSSGen && i == 0 { 1568 vinEntry := &vinList[0] 1569 vinEntry.Stakebase = hex.EncodeToString(txIn.SignatureScript) 1570 vinEntry.Sequence = txIn.Sequence 1571 vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() 1572 vinEntry.BlockHeight = txIn.BlockHeight 1573 vinEntry.BlockIndex = txIn.BlockIndex 1574 continue 1575 } 1576 1577 // The disassembled string will contain [error] inline 1578 // if the script doesn't fully parse, so ignore the 1579 // error here. 1580 disbuf, _ := txscript.DisasmString(txIn.SignatureScript) 1581 1582 vinEntry := &vinList[i] 1583 vinEntry.Txid = txIn.PreviousOutPoint.Hash.String() 1584 vinEntry.Vout = txIn.PreviousOutPoint.Index 1585 vinEntry.Tree = txIn.PreviousOutPoint.Tree 1586 vinEntry.Sequence = txIn.Sequence 1587 vinEntry.AmountIn = dcrutil.Amount(txIn.ValueIn).ToCoin() 1588 vinEntry.BlockHeight = txIn.BlockHeight 1589 vinEntry.BlockIndex = txIn.BlockIndex 1590 vinEntry.ScriptSig = &dcrdtypes.ScriptSig{ 1591 Asm: disbuf, 1592 Hex: hex.EncodeToString(txIn.SignatureScript), 1593 } 1594 } 1595 1596 return vinList 1597 } 1598 1599 // createVoutList returns a slice of JSON objects for the outputs of the passed 1600 // transaction. 1601 func createVoutList(mtx *wire.MsgTx, chainParams *chaincfg.Params, filterAddrMap map[string]struct{}, isTreasuryEnabled bool) []dcrdtypes.Vout { 1602 txType := stake.DetermineTxType(mtx) 1603 voutList := make([]dcrdtypes.Vout, 0, len(mtx.TxOut)) 1604 for i, v := range mtx.TxOut { 1605 // The disassembled string will contain [error] inline if the 1606 // script doesn't fully parse, so ignore the error here. 1607 disbuf, _ := txscript.DisasmString(v.PkScript) 1608 1609 // Attempt to extract addresses from the public key script. In 1610 // the case of stake submission transactions, the odd outputs 1611 // contain a commitment address, so detect that case 1612 // accordingly. 1613 var addrs []stdaddr.Address 1614 var scriptClass string 1615 var reqSigs uint16 1616 var commitAmt *dcrutil.Amount 1617 if txType == stake.TxTypeSStx && (i%2 != 0) { 1618 scriptClass = sstxCommitmentString 1619 addr, err := stake.AddrFromSStxPkScrCommitment(v.PkScript, 1620 chainParams) 1621 if err != nil { 1622 log.Warnf("failed to decode ticket "+ 1623 "commitment addr output for tx hash "+ 1624 "%v, output idx %v", mtx.TxHash(), i) 1625 } else { 1626 addrs = []stdaddr.Address{addr} 1627 } 1628 amt, err := stake.AmountFromSStxPkScrCommitment(v.PkScript) 1629 if err != nil { 1630 log.Warnf("failed to decode ticket "+ 1631 "commitment amt output for tx hash %v"+ 1632 ", output idx %v", mtx.TxHash(), i) 1633 } else { 1634 commitAmt = &amt 1635 } 1636 } else { 1637 // Ignore the error here since an error means the script 1638 // couldn't parse and there is no additional information 1639 // about it anyways. 1640 var sc stdscript.ScriptType 1641 sc, addrs = stdscript.ExtractAddrs(v.Version, v.PkScript, chainParams) 1642 reqSigs = stdscript.DetermineRequiredSigs(v.Version, v.PkScript) 1643 scriptClass = sc.String() 1644 } 1645 1646 // Encode the addresses while checking if the address passes the 1647 // filter when needed. 1648 passesFilter := len(filterAddrMap) == 0 1649 encodedAddrs := make([]string, len(addrs)) 1650 for j, addr := range addrs { 1651 encodedAddr := addr.String() 1652 encodedAddrs[j] = encodedAddr 1653 1654 // No need to check the map again if the filter already 1655 // passes. 1656 if passesFilter { 1657 continue 1658 } 1659 if _, exists := filterAddrMap[encodedAddr]; exists { 1660 passesFilter = true 1661 } 1662 } 1663 1664 if !passesFilter { 1665 continue 1666 } 1667 1668 var vout dcrdtypes.Vout 1669 voutSPK := &vout.ScriptPubKey 1670 vout.N = uint32(i) 1671 vout.Value = dcrutil.Amount(v.Value).ToCoin() 1672 vout.Version = v.Version 1673 voutSPK.Addresses = encodedAddrs 1674 voutSPK.Asm = disbuf 1675 voutSPK.Hex = hex.EncodeToString(v.PkScript) 1676 voutSPK.Type = scriptClass 1677 voutSPK.ReqSigs = int32(reqSigs) 1678 if commitAmt != nil { 1679 voutSPK.CommitAmt = dcrjson.Float64(commitAmt.ToCoin()) 1680 } 1681 1682 voutList = append(voutList, vout) 1683 } 1684 1685 return voutList 1686 } 1687 1688 // difficultyRatio returns the proof-of-work difficulty as a multiple of the 1689 // minimum difficulty using the passed bits field from the header of a block. 1690 func difficultyRatio(bits uint32, params *chaincfg.Params) float64 { 1691 // The minimum difficulty is the max possible proof-of-work limit bits 1692 // converted back to a number. Note this is not the same as the proof 1693 // of work limit directly because the block difficulty is encoded in a 1694 // block with the compact form which loses precision. 1695 max := blockchain.CompactToBig(params.PowLimitBits) 1696 target := blockchain.CompactToBig(bits) 1697 1698 difficulty := new(big.Rat).SetFrac(max, target) 1699 outString := difficulty.FloatString(8) 1700 diff, err := strconv.ParseFloat(outString, 64) 1701 if err != nil { 1702 log.Errorf("Cannot get difficulty: %v", err) 1703 return 0 1704 } 1705 return diff 1706 } 1707 1708 // syncStatus handles a syncstatus request. 1709 func (s *Server) syncStatus(ctx context.Context, icmd interface{}) (interface{}, error) { 1710 w, ok := s.walletLoader.LoadedWallet() 1711 if !ok { 1712 return nil, errUnloadedWallet 1713 } 1714 n, err := w.NetworkBackend() 1715 if err != nil { 1716 return nil, err 1717 } 1718 1719 walletBestHash, walletBestHeight := w.MainChainTip(ctx) 1720 bestBlock, err := w.BlockInfo(ctx, wallet.NewBlockIdentifierFromHash(&walletBestHash)) 1721 if err != nil { 1722 return nil, err 1723 } 1724 _24HoursAgo := time.Now().UTC().Add(-24 * time.Hour).Unix() 1725 walletBestBlockTooOld := bestBlock.Timestamp < _24HoursAgo 1726 1727 var synced bool 1728 var targetHeight int32 1729 1730 if syncer, ok := n.(*spv.Syncer); ok { 1731 synced = syncer.Synced() 1732 targetHeight = syncer.EstimateMainChainTip(ctx) 1733 } else if rpc, ok := n.(*dcrd.RPC); ok { 1734 var chainInfo *dcrdtypes.GetBlockChainInfoResult 1735 err := rpc.Call(ctx, "getblockchaininfo", &chainInfo) 1736 if err != nil { 1737 return nil, err 1738 } 1739 synced = chainInfo.Headers == int64(walletBestHeight) 1740 targetHeight = int32(chainInfo.Headers) 1741 } 1742 1743 var headersFetchProgress float32 1744 blocksToFetch := targetHeight - walletBestHeight 1745 if blocksToFetch <= 0 { 1746 headersFetchProgress = 1 1747 } else { 1748 totalHeadersToFetch := targetHeight - w.InitialHeight() 1749 headersFetchProgress = 1 - (float32(blocksToFetch) / float32(totalHeadersToFetch)) 1750 } 1751 1752 return &types.SyncStatusResult{ 1753 Synced: synced, 1754 InitialBlockDownload: walletBestBlockTooOld, 1755 HeadersFetchProgress: headersFetchProgress, 1756 }, nil 1757 } 1758 1759 // getCurrentNet handles a getcurrentnet request. 1760 func (s *Server) getCurrentNet(ctx context.Context, icmd interface{}) (interface{}, error) { 1761 return s.activeNet.Net, nil 1762 } 1763 1764 // getInfo handles a getinfo request by returning a structure containing 1765 // information about the current state of the wallet. 1766 func (s *Server) getInfo(ctx context.Context, icmd interface{}) (interface{}, error) { 1767 w, ok := s.walletLoader.LoadedWallet() 1768 if !ok { 1769 return nil, errUnloadedWallet 1770 } 1771 1772 tipHash, tipHeight := w.MainChainTip(ctx) 1773 tipHeader, err := w.BlockHeader(ctx, &tipHash) 1774 if err != nil { 1775 return nil, err 1776 } 1777 1778 balances, err := w.AccountBalances(ctx, 1) 1779 if err != nil { 1780 return nil, err 1781 } 1782 var spendableBalance dcrutil.Amount 1783 for _, balance := range balances { 1784 spendableBalance += balance.Spendable 1785 } 1786 1787 info := &types.InfoResult{ 1788 Version: version.Integer, 1789 ProtocolVersion: int32(p2p.Pver), 1790 WalletVersion: version.Integer, 1791 Balance: spendableBalance.ToCoin(), 1792 Blocks: tipHeight, 1793 TimeOffset: 0, 1794 Connections: 0, 1795 Proxy: "", 1796 Difficulty: difficultyRatio(tipHeader.Bits, w.ChainParams()), 1797 TestNet: w.ChainParams().Net == wire.TestNet3, 1798 KeypoolOldest: 0, 1799 KeypoolSize: 0, 1800 UnlockedUntil: 0, 1801 PaytxFee: w.RelayFee().ToCoin(), 1802 RelayFee: 0, 1803 Errors: "", 1804 } 1805 1806 n, _ := s.walletLoader.NetworkBackend() 1807 if rpc, ok := n.(*dcrd.RPC); ok { 1808 var consensusInfo dcrdtypes.InfoChainResult 1809 err := rpc.Call(ctx, "getinfo", &consensusInfo) 1810 if err != nil { 1811 return nil, err 1812 } 1813 info.Version = consensusInfo.Version 1814 info.ProtocolVersion = consensusInfo.ProtocolVersion 1815 info.TimeOffset = consensusInfo.TimeOffset 1816 info.Connections = consensusInfo.Connections 1817 info.Proxy = consensusInfo.Proxy 1818 info.RelayFee = consensusInfo.RelayFee 1819 info.Errors = consensusInfo.Errors 1820 } 1821 1822 return info, nil 1823 } 1824 1825 func decodeAddress(s string, params *chaincfg.Params) (stdaddr.Address, error) { 1826 // Secp256k1 pubkey as a string, handle differently. 1827 if len(s) == 66 || len(s) == 130 { 1828 pubKeyBytes, err := hex.DecodeString(s) 1829 if err != nil { 1830 return nil, err 1831 } 1832 pubKeyAddr, err := stdaddr.NewAddressPubKeyEcdsaSecp256k1V0Raw( 1833 pubKeyBytes, params) 1834 if err != nil { 1835 return nil, err 1836 } 1837 1838 return pubKeyAddr, nil 1839 } 1840 1841 addr, err := stdaddr.DecodeAddress(s, params) 1842 if err != nil { 1843 return nil, rpcErrorf(dcrjson.ErrRPCInvalidAddressOrKey, 1844 "invalid address %q: decode failed: %#q", s, err) 1845 } 1846 return addr, nil 1847 } 1848 1849 func decodeStakeAddress(s string, params *chaincfg.Params) (stdaddr.StakeAddress, error) { 1850 a, err := decodeAddress(s, params) 1851 if err != nil { 1852 return nil, err 1853 } 1854 if sa, ok := a.(stdaddr.StakeAddress); ok { 1855 return sa, nil 1856 } 1857 return nil, rpcErrorf(dcrjson.ErrRPCInvalidAddressOrKey, 1858 "invalid stake address %q", s) 1859 } 1860 1861 // getAccount handles a getaccount request by returning the account name 1862 // associated with a single address. 1863 func (s *Server) getAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 1864 cmd := icmd.(*types.GetAccountCmd) 1865 w, ok := s.walletLoader.LoadedWallet() 1866 if !ok { 1867 return nil, errUnloadedWallet 1868 } 1869 1870 addr, err := decodeAddress(cmd.Address, w.ChainParams()) 1871 if err != nil { 1872 return nil, err 1873 } 1874 1875 a, err := w.KnownAddress(ctx, addr) 1876 if err != nil { 1877 if errors.Is(err, errors.NotExist) { 1878 return nil, errAddressNotInWallet 1879 } 1880 return nil, err 1881 } 1882 1883 return a.AccountName(), nil 1884 } 1885 1886 // getAccountAddress handles a getaccountaddress by returning the most 1887 // recently-created chained address that has not yet been used (does not yet 1888 // appear in the blockchain, or any tx that has arrived in the dcrd mempool). 1889 // If the most recently-requested address has been used, a new address (the 1890 // next chained address in the keypool) is used. This can fail if the keypool 1891 // runs out (and will return dcrjson.ErrRPCWalletKeypoolRanOut if that happens). 1892 func (s *Server) getAccountAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 1893 cmd := icmd.(*types.GetAccountAddressCmd) 1894 w, ok := s.walletLoader.LoadedWallet() 1895 if !ok { 1896 return nil, errUnloadedWallet 1897 } 1898 1899 account, err := w.AccountNumber(ctx, cmd.Account) 1900 if err != nil { 1901 if errors.Is(err, errors.NotExist) { 1902 return nil, errAccountNotFound 1903 } 1904 return nil, err 1905 } 1906 addr, err := w.CurrentAddress(account) 1907 if err != nil { 1908 // Expect account lookup to succeed 1909 if errors.Is(err, errors.NotExist) { 1910 return nil, rpcError(dcrjson.ErrRPCInternal.Code, err) 1911 } 1912 return nil, err 1913 } 1914 1915 return addr.String(), nil 1916 } 1917 1918 // getUnconfirmedBalance handles a getunconfirmedbalance extension request 1919 // by returning the current unconfirmed balance of an account. 1920 func (s *Server) getUnconfirmedBalance(ctx context.Context, icmd interface{}) (interface{}, error) { 1921 cmd := icmd.(*types.GetUnconfirmedBalanceCmd) 1922 w, ok := s.walletLoader.LoadedWallet() 1923 if !ok { 1924 return nil, errUnloadedWallet 1925 } 1926 1927 acctName := "default" 1928 if cmd.Account != nil { 1929 acctName = *cmd.Account 1930 } 1931 account, err := w.AccountNumber(ctx, acctName) 1932 if err != nil { 1933 if errors.Is(err, errors.NotExist) { 1934 return nil, errAccountNotFound 1935 } 1936 return nil, err 1937 } 1938 bals, err := w.AccountBalance(ctx, account, 1) 1939 if err != nil { 1940 // Expect account lookup to succeed 1941 if errors.Is(err, errors.NotExist) { 1942 return nil, rpcError(dcrjson.ErrRPCInternal.Code, err) 1943 } 1944 return nil, err 1945 } 1946 1947 return (bals.Total - bals.Spendable).ToCoin(), nil 1948 } 1949 1950 // getCFilterV2 implements the getcfilterv2 command. 1951 func (s *Server) getCFilterV2(ctx context.Context, icmd interface{}) (interface{}, error) { 1952 cmd := icmd.(*types.GetCFilterV2Cmd) 1953 blockHash, err := chainhash.NewHashFromStr(cmd.BlockHash) 1954 if err != nil { 1955 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 1956 } 1957 1958 w, ok := s.walletLoader.LoadedWallet() 1959 if !ok { 1960 return nil, errUnloadedWallet 1961 } 1962 1963 key, filter, err := w.CFilterV2(ctx, blockHash) 1964 if err != nil { 1965 return nil, err 1966 } 1967 1968 return &types.GetCFilterV2Result{ 1969 BlockHash: cmd.BlockHash, 1970 Filter: hex.EncodeToString(filter.Bytes()), 1971 Key: hex.EncodeToString(key[:]), 1972 }, nil 1973 } 1974 1975 // importCFiltersV2 handles an importcfiltersv2 request by parsing the provided 1976 // hex-encoded filters into bytes and importing them into the wallet. 1977 func (s *Server) importCFiltersV2(ctx context.Context, icmd interface{}) (interface{}, error) { 1978 cmd := icmd.(*types.ImportCFiltersV2Cmd) 1979 1980 w, ok := s.walletLoader.LoadedWallet() 1981 if !ok { 1982 return nil, errUnloadedWallet 1983 } 1984 1985 filterData := make([][]byte, len(cmd.Filters)) 1986 for i, fdhex := range cmd.Filters { 1987 var err error 1988 filterData[i], err = hex.DecodeString(fdhex) 1989 if err != nil { 1990 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParams.Code, "filter %d is not a valid hex string", i) 1991 } 1992 } 1993 1994 err := w.ImportCFiltersV2(ctx, cmd.StartHeight, filterData) 1995 if err != nil { 1996 return nil, rpcError(dcrjson.ErrRPCInvalidRequest.Code, err) 1997 } 1998 1999 return nil, nil 2000 } 2001 2002 // importPrivKey handles an importprivkey request by parsing 2003 // a WIF-encoded private key and adding it to an account. 2004 func (s *Server) importPrivKey(ctx context.Context, icmd interface{}) (interface{}, error) { 2005 cmd := icmd.(*types.ImportPrivKeyCmd) 2006 w, ok := s.walletLoader.LoadedWallet() 2007 if !ok { 2008 return nil, errUnloadedWallet 2009 } 2010 2011 rescan := true 2012 if cmd.Rescan != nil { 2013 rescan = *cmd.Rescan 2014 } 2015 scanFrom := int32(0) 2016 if cmd.ScanFrom != nil { 2017 scanFrom = int32(*cmd.ScanFrom) 2018 } 2019 n, ok := s.walletLoader.NetworkBackend() 2020 if rescan && !ok { 2021 return nil, errNoNetwork 2022 } 2023 2024 // Ensure that private keys are only imported to the correct account. 2025 // 2026 // Yes, Label is the account name. 2027 if cmd.Label != nil && *cmd.Label != udb.ImportedAddrAccountName { 2028 return nil, errNotImportedAccount 2029 } 2030 2031 wif, err := dcrutil.DecodeWIF(cmd.PrivKey, w.ChainParams().PrivateKeyID) 2032 if err != nil { 2033 return nil, rpcErrorf(dcrjson.ErrRPCInvalidAddressOrKey, "WIF decode failed: %v", err) 2034 } 2035 2036 // Import the private key, handling any errors. 2037 _, err = w.ImportPrivateKey(ctx, wif) 2038 if err != nil { 2039 switch { 2040 case errors.Is(err, errors.Exist): 2041 // Do not return duplicate key errors to the client. 2042 return nil, nil 2043 case errors.Is(err, errors.Locked): 2044 return nil, errWalletUnlockNeeded 2045 default: 2046 return nil, err 2047 } 2048 } 2049 2050 if rescan { 2051 // TODO: This is not synchronized with process shutdown and 2052 // will cause panics when the DB is closed mid-transaction. 2053 go w.RescanFromHeight(context.Background(), n, scanFrom) 2054 } 2055 2056 return nil, nil 2057 } 2058 2059 // importPubKey handles an importpubkey request by importing a hex-encoded 2060 // compressed 33-byte secp256k1 public key with sign byte, as well as its 2061 // derived P2PKH address. This method may only be used by watching-only 2062 // wallets and with the special "imported" account. 2063 func (s *Server) importPubKey(ctx context.Context, icmd interface{}) (interface{}, error) { 2064 cmd := icmd.(*types.ImportPubKeyCmd) 2065 w, ok := s.walletLoader.LoadedWallet() 2066 if !ok { 2067 return nil, errUnloadedWallet 2068 } 2069 2070 rescan := true 2071 if cmd.Rescan != nil { 2072 rescan = *cmd.Rescan 2073 } 2074 scanFrom := int32(0) 2075 if cmd.ScanFrom != nil { 2076 scanFrom = int32(*cmd.ScanFrom) 2077 } 2078 n, ok := s.walletLoader.NetworkBackend() 2079 if rescan && !ok { 2080 return nil, errNoNetwork 2081 } 2082 2083 if cmd.Label != nil && *cmd.Label != udb.ImportedAddrAccountName { 2084 return nil, errNotImportedAccount 2085 } 2086 2087 pk, err := hex.DecodeString(cmd.PubKey) 2088 if err != nil { 2089 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 2090 } 2091 2092 _, err = w.ImportPublicKey(ctx, pk) 2093 if errors.Is(err, errors.Exist) { 2094 // Do not return duplicate address errors, and skip any 2095 // rescans. 2096 return nil, nil 2097 } 2098 if err != nil { 2099 return nil, err 2100 } 2101 2102 if rescan { 2103 // TODO: This is not synchronized with process shutdown and 2104 // will cause panics when the DB is closed mid-transaction. 2105 go w.RescanFromHeight(context.Background(), n, scanFrom) 2106 } 2107 2108 return nil, nil 2109 } 2110 2111 // importScript imports a redeem script for a P2SH output. 2112 func (s *Server) importScript(ctx context.Context, icmd interface{}) (interface{}, error) { 2113 cmd := icmd.(*types.ImportScriptCmd) 2114 w, ok := s.walletLoader.LoadedWallet() 2115 if !ok { 2116 return nil, errUnloadedWallet 2117 } 2118 2119 rescan := true 2120 if cmd.Rescan != nil { 2121 rescan = *cmd.Rescan 2122 } 2123 scanFrom := int32(0) 2124 if cmd.ScanFrom != nil { 2125 scanFrom = int32(*cmd.ScanFrom) 2126 } 2127 n, ok := s.walletLoader.NetworkBackend() 2128 if rescan && !ok { 2129 return nil, errNoNetwork 2130 } 2131 2132 rs, err := hex.DecodeString(cmd.Hex) 2133 if err != nil { 2134 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 2135 } 2136 if len(rs) == 0 { 2137 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "empty script") 2138 } 2139 2140 err = w.ImportScript(ctx, rs) 2141 if errors.Is(err, errors.Exist) { 2142 return nil, nil 2143 } 2144 if err != nil { 2145 return nil, err 2146 } 2147 2148 if rescan { 2149 // TODO: This is not synchronized with process shutdown and 2150 // will cause panics when the DB is closed mid-transaction. 2151 go w.RescanFromHeight(context.Background(), n, scanFrom) 2152 } 2153 2154 return nil, nil 2155 } 2156 2157 func (s *Server) importXpub(ctx context.Context, icmd interface{}) (interface{}, error) { 2158 cmd := icmd.(*types.ImportXpubCmd) 2159 w, ok := s.walletLoader.LoadedWallet() 2160 if !ok { 2161 return nil, errUnloadedWallet 2162 } 2163 2164 xpub, err := hdkeychain.NewKeyFromString(cmd.Xpub, w.ChainParams()) 2165 if err != nil { 2166 return nil, err 2167 } 2168 2169 return nil, w.ImportXpubAccount(ctx, cmd.Name, xpub) 2170 } 2171 2172 // createNewAccount handles a createnewaccount request by creating and 2173 // returning a new account. If the last account has no transaction history 2174 // as per BIP 0044 a new account cannot be created so an error will be returned. 2175 func (s *Server) createNewAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 2176 cmd := icmd.(*types.CreateNewAccountCmd) 2177 w, ok := s.walletLoader.LoadedWallet() 2178 if !ok { 2179 return nil, errUnloadedWallet 2180 } 2181 2182 // The wildcard * is reserved by the rpc server with the special meaning 2183 // of "all accounts", so disallow naming accounts to this string. 2184 if cmd.Account == "*" { 2185 return nil, errReservedAccountName 2186 } 2187 2188 _, err := w.NextAccount(ctx, cmd.Account) 2189 if err != nil { 2190 if errors.Is(err, errors.Locked) { 2191 return nil, rpcErrorf(dcrjson.ErrRPCWalletUnlockNeeded, "creating new accounts requires an unlocked wallet") 2192 } 2193 return nil, err 2194 } 2195 return nil, nil 2196 } 2197 2198 // renameAccount handles a renameaccount request by renaming an account. 2199 // If the account does not exist an appropriate error will be returned. 2200 func (s *Server) renameAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 2201 cmd := icmd.(*types.RenameAccountCmd) 2202 w, ok := s.walletLoader.LoadedWallet() 2203 if !ok { 2204 return nil, errUnloadedWallet 2205 } 2206 2207 // The wildcard * is reserved by the rpc server with the special meaning 2208 // of "all accounts", so disallow naming accounts to this string. 2209 if cmd.NewAccount == "*" { 2210 return nil, errReservedAccountName 2211 } 2212 2213 // Check that given account exists 2214 account, err := w.AccountNumber(ctx, cmd.OldAccount) 2215 if err != nil { 2216 if errors.Is(err, errors.NotExist) { 2217 return nil, errAccountNotFound 2218 } 2219 return nil, err 2220 } 2221 err = w.RenameAccount(ctx, account, cmd.NewAccount) 2222 return nil, err 2223 } 2224 2225 // getMultisigOutInfo displays information about a given multisignature 2226 // output. 2227 func (s *Server) getMultisigOutInfo(ctx context.Context, icmd interface{}) (interface{}, error) { 2228 cmd := icmd.(*types.GetMultisigOutInfoCmd) 2229 w, ok := s.walletLoader.LoadedWallet() 2230 if !ok { 2231 return nil, errUnloadedWallet 2232 } 2233 2234 hash, err := chainhash.NewHashFromStr(cmd.Hash) 2235 if err != nil { 2236 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 2237 } 2238 2239 // Multisig outs are always in TxTreeRegular. 2240 op := &wire.OutPoint{ 2241 Hash: *hash, 2242 Index: cmd.Index, 2243 Tree: wire.TxTreeRegular, 2244 } 2245 2246 p2shOutput, err := w.FetchP2SHMultiSigOutput(ctx, op) 2247 if err != nil { 2248 return nil, err 2249 } 2250 2251 // Get the list of pubkeys required to sign. 2252 _, pubkeyAddrs := stdscript.ExtractAddrs(scriptVersionAssumed, p2shOutput.RedeemScript, w.ChainParams()) 2253 pubkeys := make([]string, 0, len(pubkeyAddrs)) 2254 for _, pka := range pubkeyAddrs { 2255 switch pka := pka.(type) { 2256 case *stdaddr.AddressPubKeyEcdsaSecp256k1V0: 2257 pubkeys = append(pubkeys, hex.EncodeToString(pka.SerializedPubKey())) 2258 } 2259 } 2260 2261 result := &types.GetMultisigOutInfoResult{ 2262 Address: p2shOutput.P2SHAddress.String(), 2263 RedeemScript: hex.EncodeToString(p2shOutput.RedeemScript), 2264 M: p2shOutput.M, 2265 N: p2shOutput.N, 2266 Pubkeys: pubkeys, 2267 TxHash: p2shOutput.OutPoint.Hash.String(), 2268 Amount: p2shOutput.OutputAmount.ToCoin(), 2269 } 2270 if !p2shOutput.ContainingBlock.None() { 2271 result.BlockHeight = uint32(p2shOutput.ContainingBlock.Height) 2272 result.BlockHash = p2shOutput.ContainingBlock.Hash.String() 2273 } 2274 if p2shOutput.Redeemer != nil { 2275 result.Spent = true 2276 result.SpentBy = p2shOutput.Redeemer.TxHash.String() 2277 result.SpentByIndex = p2shOutput.Redeemer.InputIndex 2278 } 2279 return result, nil 2280 } 2281 2282 // getNewAddress handles a getnewaddress request by returning a new 2283 // address for an account. If the account does not exist an appropriate 2284 // error is returned. 2285 func (s *Server) getNewAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 2286 cmd := icmd.(*types.GetNewAddressCmd) 2287 w, ok := s.walletLoader.LoadedWallet() 2288 if !ok { 2289 return nil, errUnloadedWallet 2290 } 2291 2292 var callOpts []wallet.NextAddressCallOption 2293 if cmd.GapPolicy != nil { 2294 switch *cmd.GapPolicy { 2295 case "": 2296 case "error": 2297 callOpts = append(callOpts, wallet.WithGapPolicyError()) 2298 case "ignore": 2299 callOpts = append(callOpts, wallet.WithGapPolicyIgnore()) 2300 case "wrap": 2301 callOpts = append(callOpts, wallet.WithGapPolicyWrap()) 2302 default: 2303 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "unknown gap policy %q", *cmd.GapPolicy) 2304 } 2305 } 2306 2307 acctName := "default" 2308 if cmd.Account != nil { 2309 acctName = *cmd.Account 2310 } 2311 account, err := w.AccountNumber(ctx, acctName) 2312 if err != nil { 2313 if errors.Is(err, errors.NotExist) { 2314 return nil, errAccountNotFound 2315 } 2316 return nil, err 2317 } 2318 2319 addr, err := w.NewExternalAddress(ctx, account, callOpts...) 2320 if err != nil { 2321 return nil, err 2322 } 2323 return addr.String(), nil 2324 } 2325 2326 // getRawChangeAddress handles a getrawchangeaddress request by creating 2327 // and returning a new change address for an account. 2328 // 2329 // Note: bitcoind allows specifying the account as an optional parameter, 2330 // but ignores the parameter. 2331 func (s *Server) getRawChangeAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 2332 cmd := icmd.(*types.GetRawChangeAddressCmd) 2333 w, ok := s.walletLoader.LoadedWallet() 2334 if !ok { 2335 return nil, errUnloadedWallet 2336 } 2337 2338 acctName := "default" 2339 if cmd.Account != nil { 2340 acctName = *cmd.Account 2341 } 2342 account, err := w.AccountNumber(ctx, acctName) 2343 if err != nil { 2344 if errors.Is(err, errors.NotExist) { 2345 return nil, errAccountNotFound 2346 } 2347 return nil, err 2348 } 2349 2350 addr, err := w.NewChangeAddress(ctx, account) 2351 if err != nil { 2352 return nil, err 2353 } 2354 2355 // Return the new payment address string. 2356 return addr.String(), nil 2357 } 2358 2359 // getReceivedByAccount handles a getreceivedbyaccount request by returning 2360 // the total amount received by addresses of an account. 2361 func (s *Server) getReceivedByAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 2362 cmd := icmd.(*types.GetReceivedByAccountCmd) 2363 w, ok := s.walletLoader.LoadedWallet() 2364 if !ok { 2365 return nil, errUnloadedWallet 2366 } 2367 2368 account, err := w.AccountNumber(ctx, cmd.Account) 2369 if err != nil { 2370 if errors.Is(err, errors.NotExist) { 2371 return nil, errAccountNotFound 2372 } 2373 return nil, err 2374 } 2375 2376 // Transactions are not tracked for imported xpub accounts. 2377 if account > udb.ImportedAddrAccount { 2378 return 0.0, nil 2379 } 2380 2381 // TODO: This is more inefficient that it could be, but the entire 2382 // algorithm is already dominated by reading every transaction in the 2383 // wallet's history. 2384 results, err := w.TotalReceivedForAccounts(ctx, int32(*cmd.MinConf)) 2385 if err != nil { 2386 return nil, err 2387 } 2388 acctIndex := int(account) 2389 if account == udb.ImportedAddrAccount { 2390 acctIndex = len(results) - 1 2391 } 2392 return results[acctIndex].TotalReceived.ToCoin(), nil 2393 } 2394 2395 // getReceivedByAddress handles a getreceivedbyaddress request by returning 2396 // the total amount received by a single address. 2397 func (s *Server) getReceivedByAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 2398 cmd := icmd.(*types.GetReceivedByAddressCmd) 2399 w, ok := s.walletLoader.LoadedWallet() 2400 if !ok { 2401 return nil, errUnloadedWallet 2402 } 2403 2404 addr, err := decodeAddress(cmd.Address, w.ChainParams()) 2405 if err != nil { 2406 return nil, err 2407 } 2408 total, err := w.TotalReceivedForAddr(ctx, addr, int32(*cmd.MinConf)) 2409 if err != nil { 2410 if errors.Is(err, errors.NotExist) { 2411 return nil, errAddressNotInWallet 2412 } 2413 return nil, err 2414 } 2415 2416 return total.ToCoin(), nil 2417 } 2418 2419 // getMasterPubkey handles a getmasterpubkey request by returning the wallet 2420 // master pubkey encoded as a string. 2421 func (s *Server) getMasterPubkey(ctx context.Context, icmd interface{}) (interface{}, error) { 2422 cmd := icmd.(*types.GetMasterPubkeyCmd) 2423 w, ok := s.walletLoader.LoadedWallet() 2424 if !ok { 2425 return nil, errUnloadedWallet 2426 } 2427 2428 // If no account is passed, we provide the extended public key 2429 // for the default account number. 2430 account := uint32(udb.DefaultAccountNum) 2431 if cmd.Account != nil { 2432 var err error 2433 account, err = w.AccountNumber(ctx, *cmd.Account) 2434 if err != nil { 2435 if errors.Is(err, errors.NotExist) { 2436 return nil, errAccountNotFound 2437 } 2438 return nil, err 2439 } 2440 } 2441 2442 xpub, err := w.AccountXpub(ctx, account) 2443 if err != nil { 2444 return nil, err 2445 } 2446 2447 log.Warnf("Attention: Extended public keys must not be shared with or " + 2448 "leaked to external parties, such as VSPs, in combination with " + 2449 "any account private key; this reveals all private keys of " + 2450 "this account") 2451 2452 return xpub.String(), nil 2453 } 2454 2455 // getPeerInfo responds to the getpeerinfo request. 2456 // It gets the network backend and views the data on remote peers when in spv mode 2457 func (s *Server) getPeerInfo(ctx context.Context, icmd interface{}) (interface{}, error) { 2458 w, ok := s.walletLoader.LoadedWallet() 2459 if !ok { 2460 return nil, errUnloadedWallet 2461 } 2462 n, err := w.NetworkBackend() 2463 if err != nil { 2464 return nil, err 2465 } 2466 2467 syncer, ok := n.(*spv.Syncer) 2468 if !ok { 2469 var resp []*types.GetPeerInfoResult 2470 if rpc, ok := n.(*dcrd.RPC); ok { 2471 err := rpc.Call(ctx, "getpeerinfo", &resp) 2472 if err != nil { 2473 return nil, err 2474 } 2475 } 2476 return resp, nil 2477 } 2478 2479 rps := syncer.GetRemotePeers() 2480 infos := make([]*types.GetPeerInfoResult, 0, len(rps)) 2481 2482 for _, rp := range rps { 2483 info := &types.GetPeerInfoResult{ 2484 ID: int32(rp.ID()), 2485 Addr: rp.RemoteAddr().String(), 2486 AddrLocal: rp.LocalAddr().String(), 2487 Services: fmt.Sprintf("%08d", uint64(rp.Services())), 2488 Version: rp.Pver(), 2489 SubVer: rp.UA(), 2490 StartingHeight: int64(rp.InitialHeight()), 2491 BanScore: int32(rp.BanScore()), 2492 } 2493 infos = append(infos, info) 2494 } 2495 sort.Slice(infos, func(i, j int) bool { 2496 return infos[i].ID < infos[j].ID 2497 }) 2498 return infos, nil 2499 } 2500 2501 // getStakeInfo gets a large amounts of information about the stake environment 2502 // and a number of statistics about local staking in the wallet. 2503 func (s *Server) getStakeInfo(ctx context.Context, icmd interface{}) (interface{}, error) { 2504 w, ok := s.walletLoader.LoadedWallet() 2505 if !ok { 2506 return nil, errUnloadedWallet 2507 } 2508 2509 var rpc *dcrd.RPC 2510 n, _ := s.walletLoader.NetworkBackend() 2511 if client, ok := n.(*dcrd.RPC); ok { 2512 rpc = client 2513 } 2514 var sinfo *wallet.StakeInfoData 2515 var err error 2516 if rpc != nil { 2517 sinfo, err = w.StakeInfoPrecise(ctx, rpc) 2518 } else { 2519 sinfo, err = w.StakeInfo(ctx) 2520 } 2521 if err != nil { 2522 return nil, err 2523 } 2524 2525 var proportionLive, proportionMissed float64 2526 if sinfo.PoolSize > 0 { 2527 proportionLive = float64(sinfo.Live) / float64(sinfo.PoolSize) 2528 } 2529 if sinfo.Missed > 0 { 2530 proportionMissed = float64(sinfo.Missed) / (float64(sinfo.Voted + sinfo.Missed)) 2531 } 2532 2533 resp := &types.GetStakeInfoResult{ 2534 BlockHeight: sinfo.BlockHeight, 2535 Difficulty: sinfo.Sdiff.ToCoin(), 2536 TotalSubsidy: sinfo.TotalSubsidy.ToCoin(), 2537 2538 OwnMempoolTix: sinfo.OwnMempoolTix, 2539 Immature: sinfo.Immature, 2540 Unspent: sinfo.Unspent, 2541 Voted: sinfo.Voted, 2542 Revoked: sinfo.Revoked, 2543 UnspentExpired: sinfo.UnspentExpired, 2544 2545 PoolSize: sinfo.PoolSize, 2546 AllMempoolTix: sinfo.AllMempoolTix, 2547 Live: sinfo.Live, 2548 ProportionLive: proportionLive, 2549 Missed: sinfo.Missed, 2550 ProportionMissed: proportionMissed, 2551 Expired: sinfo.Expired, 2552 } 2553 2554 return resp, nil 2555 } 2556 2557 // getTickets handles a gettickets request by returning the hashes of the tickets 2558 // currently owned by wallet, encoded as strings. 2559 func (s *Server) getTickets(ctx context.Context, icmd interface{}) (interface{}, error) { 2560 cmd := icmd.(*types.GetTicketsCmd) 2561 w, ok := s.walletLoader.LoadedWallet() 2562 if !ok { 2563 return nil, errUnloadedWallet 2564 } 2565 2566 n, _ := s.walletLoader.NetworkBackend() 2567 rpc, ok := n.(*dcrd.RPC) 2568 if !ok { 2569 return nil, errRPCClientNotConnected 2570 } 2571 2572 ticketHashes, err := w.LiveTicketHashes(ctx, rpc, cmd.IncludeImmature) 2573 if err != nil { 2574 return nil, err 2575 } 2576 2577 // Compose a slice of strings to return. 2578 ticketHashStrs := make([]string, 0, len(ticketHashes)) 2579 for i := range ticketHashes { 2580 ticketHashStrs = append(ticketHashStrs, ticketHashes[i].String()) 2581 } 2582 2583 return &types.GetTicketsResult{Hashes: ticketHashStrs}, nil 2584 } 2585 2586 // getTransaction handles a gettransaction request by returning details about 2587 // a single transaction saved by wallet. 2588 func (s *Server) getTransaction(ctx context.Context, icmd interface{}) (interface{}, error) { 2589 cmd := icmd.(*types.GetTransactionCmd) 2590 w, ok := s.walletLoader.LoadedWallet() 2591 if !ok { 2592 return nil, errUnloadedWallet 2593 } 2594 2595 txHash, err := chainhash.NewHashFromStr(cmd.Txid) 2596 if err != nil { 2597 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 2598 } 2599 2600 // returns nil details when not found 2601 txd, err := wallet.UnstableAPI(w).TxDetails(ctx, txHash) 2602 if errors.Is(err, errors.NotExist) { 2603 return nil, rpcErrorf(dcrjson.ErrRPCNoTxInfo, "no information for transaction") 2604 } else if err != nil { 2605 return nil, err 2606 } 2607 2608 _, tipHeight := w.MainChainTip(ctx) 2609 2610 var b strings.Builder 2611 b.Grow(2 * txd.MsgTx.SerializeSize()) 2612 err = txd.MsgTx.Serialize(hex.NewEncoder(&b)) 2613 if err != nil { 2614 return nil, err 2615 } 2616 2617 // TODO: Add a "generated" field to this result type. "generated":true 2618 // is only added if the transaction is a coinbase. 2619 ret := types.GetTransactionResult{ 2620 TxID: cmd.Txid, 2621 Hex: b.String(), 2622 Time: txd.Received.Unix(), 2623 TimeReceived: txd.Received.Unix(), 2624 WalletConflicts: []string{}, // Not saved 2625 //Generated: compat.IsEitherCoinBaseTx(&details.MsgTx), 2626 } 2627 2628 if txd.Block.Height != -1 { 2629 ret.BlockHash = txd.Block.Hash.String() 2630 ret.BlockTime = txd.Block.Time.Unix() 2631 ret.Confirmations = int64(confirms(txd.Block.Height, 2632 tipHeight)) 2633 } 2634 2635 var ( 2636 debitTotal dcrutil.Amount 2637 creditTotal dcrutil.Amount 2638 fee dcrutil.Amount 2639 negFeeF64 float64 2640 ) 2641 for _, deb := range txd.Debits { 2642 debitTotal += deb.Amount 2643 } 2644 for _, cred := range txd.Credits { 2645 creditTotal += cred.Amount 2646 } 2647 // Fee can only be determined if every input is a debit. 2648 if len(txd.Debits) == len(txd.MsgTx.TxIn) { 2649 var outputTotal dcrutil.Amount 2650 for _, output := range txd.MsgTx.TxOut { 2651 outputTotal += dcrutil.Amount(output.Value) 2652 } 2653 fee = debitTotal - outputTotal 2654 negFeeF64 = (-fee).ToCoin() 2655 } 2656 ret.Amount = (creditTotal - debitTotal).ToCoin() 2657 ret.Fee = negFeeF64 2658 2659 details, err := w.ListTransactionDetails(ctx, txHash) 2660 if err != nil { 2661 return nil, err 2662 } 2663 ret.Details = make([]types.GetTransactionDetailsResult, len(details)) 2664 for i, d := range details { 2665 ret.Details[i] = types.GetTransactionDetailsResult{ 2666 Account: d.Account, 2667 Address: d.Address, 2668 Amount: d.Amount, 2669 Category: d.Category, 2670 InvolvesWatchOnly: d.InvolvesWatchOnly, 2671 Fee: d.Fee, 2672 Vout: d.Vout, 2673 } 2674 } 2675 2676 return ret, nil 2677 } 2678 2679 // getTxOut handles a gettxout request by returning details about an unspent 2680 // output. In SPV mode, details are only returned for transaction outputs that 2681 // are relevant to the wallet. 2682 // To match the behavior in RPC mode, (nil, nil) is returned if the transaction 2683 // output could not be found (never existed or was pruned) or is spent by another 2684 // transaction already in the main chain. Mined transactions that are spent by 2685 // a mempool transaction are not affected by this. 2686 func (s *Server) getTxOut(ctx context.Context, icmd interface{}) (interface{}, error) { 2687 cmd := icmd.(*types.GetTxOutCmd) 2688 w, ok := s.walletLoader.LoadedWallet() 2689 if !ok { 2690 return nil, errUnloadedWallet 2691 } 2692 2693 // Attempt RPC passthrough if connected to DCRD. 2694 n, err := w.NetworkBackend() 2695 if err != nil { 2696 return nil, err 2697 } 2698 if rpc, ok := n.(*dcrd.RPC); ok { 2699 var resp json.RawMessage 2700 err := rpc.Call(ctx, "gettxout", &resp, cmd.Txid, cmd.Vout, cmd.Tree, cmd.IncludeMempool) 2701 return resp, err 2702 } 2703 2704 txHash, err := chainhash.NewHashFromStr(cmd.Txid) 2705 if err != nil { 2706 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 2707 } 2708 2709 if cmd.Tree != wire.TxTreeRegular && cmd.Tree != wire.TxTreeStake { 2710 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "Tx tree must be regular or stake") 2711 } 2712 2713 // Attempt to read the unspent txout info from wallet. 2714 outpoint := wire.OutPoint{Hash: *txHash, Index: cmd.Vout, Tree: cmd.Tree} 2715 utxo, err := w.UnspentOutput(ctx, outpoint, *cmd.IncludeMempool) 2716 if err != nil && !errors.Is(err, errors.NotExist) { 2717 return nil, err 2718 } 2719 if utxo == nil { 2720 return nil, nil // output is spent or does not exist. 2721 } 2722 2723 // Disassemble script into single line printable format. The 2724 // disassembled string will contain [error] inline if the script 2725 // doesn't fully parse, so ignore the error here. 2726 disbuf, _ := txscript.DisasmString(utxo.PkScript) 2727 2728 // Get further info about the script. Ignore the error here since an 2729 // error means the script couldn't parse and there is no additional 2730 // information about it anyways. 2731 scriptClass, addrs := stdscript.ExtractAddrs(scriptVersionAssumed, utxo.PkScript, s.activeNet) 2732 reqSigs := stdscript.DetermineRequiredSigs(scriptVersionAssumed, utxo.PkScript) 2733 addresses := make([]string, len(addrs)) 2734 for i, addr := range addrs { 2735 addresses[i] = addr.String() 2736 } 2737 2738 bestHash, bestHeight := w.MainChainTip(ctx) 2739 var confirmations int64 2740 if utxo.Block.Height != -1 { 2741 confirmations = int64(confirms(utxo.Block.Height, bestHeight)) 2742 } 2743 2744 return &dcrdtypes.GetTxOutResult{ 2745 BestBlock: bestHash.String(), 2746 Confirmations: confirmations, 2747 Value: utxo.Amount.ToCoin(), 2748 ScriptPubKey: dcrdtypes.ScriptPubKeyResult{ 2749 Asm: disbuf, 2750 Hex: hex.EncodeToString(utxo.PkScript), 2751 ReqSigs: int32(reqSigs), 2752 Type: scriptClass.String(), 2753 Addresses: addresses, 2754 }, 2755 Coinbase: utxo.FromCoinBase, 2756 }, nil 2757 } 2758 2759 // getVoteChoices handles a getvotechoices request by returning configured vote 2760 // preferences for each agenda of the latest supported stake version. 2761 func (s *Server) getVoteChoices(ctx context.Context, icmd interface{}) (interface{}, error) { 2762 cmd := icmd.(*types.GetVoteChoicesCmd) 2763 w, ok := s.walletLoader.LoadedWallet() 2764 if !ok { 2765 return nil, errUnloadedWallet 2766 } 2767 2768 var ticketHash *chainhash.Hash 2769 if cmd.TicketHash != nil { 2770 hash, err := chainhash.NewHashFromStr(*cmd.TicketHash) 2771 if err != nil { 2772 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 2773 } 2774 ticketHash = hash 2775 } 2776 2777 version, agendas := wallet.CurrentAgendas(w.ChainParams()) 2778 resp := &types.GetVoteChoicesResult{ 2779 Version: version, 2780 Choices: make([]types.VoteChoice, len(agendas)), 2781 } 2782 2783 choices, _, err := w.AgendaChoices(ctx, ticketHash) 2784 if err != nil { 2785 return nil, err 2786 } 2787 2788 for i := range choices { 2789 resp.Choices[i] = types.VoteChoice{ 2790 AgendaID: choices[i].AgendaID, 2791 AgendaDescription: agendas[i].Vote.Description, 2792 ChoiceID: choices[i].ChoiceID, 2793 ChoiceDescription: "", // Set below 2794 } 2795 for j := range agendas[i].Vote.Choices { 2796 if choices[i].ChoiceID == agendas[i].Vote.Choices[j].Id { 2797 resp.Choices[i].ChoiceDescription = agendas[i].Vote.Choices[j].Description 2798 break 2799 } 2800 } 2801 } 2802 2803 return resp, nil 2804 } 2805 2806 // getWalletFee returns the currently set tx fee for the requested wallet 2807 func (s *Server) getWalletFee(ctx context.Context, icmd interface{}) (interface{}, error) { 2808 w, ok := s.walletLoader.LoadedWallet() 2809 if !ok { 2810 return nil, errUnloadedWallet 2811 } 2812 2813 return w.RelayFee().ToCoin(), nil 2814 } 2815 2816 // These generators create the following global variables in this package: 2817 // 2818 // var localeHelpDescs map[string]func() map[string]string 2819 // var requestUsages string 2820 // 2821 // localeHelpDescs maps from locale strings (e.g. "en_US") to a function that 2822 // builds a map of help texts for each RPC server method. This prevents help 2823 // text maps for every locale map from being rooted and created during init. 2824 // Instead, the appropriate function is looked up when help text is first needed 2825 // using the current locale and saved to the global below for further reuse. 2826 // 2827 // requestUsages contains single line usages for every supported request, 2828 // separated by newlines. It is set during init. These usages are used for all 2829 // locales. 2830 // 2831 //go:generate go run ../../rpchelp/genrpcserverhelp.go jsonrpc 2832 //go:generate gofmt -w rpcserverhelp.go 2833 2834 var helpDescs map[string]string 2835 var helpDescsMu sync.Mutex // Help may execute concurrently, so synchronize access. 2836 2837 // help handles the help request by returning one line usage of all available 2838 // methods, or full help for a specific method. The chainClient is optional, 2839 // and this is simply a helper function for the HelpNoChainRPC and 2840 // HelpWithChainRPC handlers. 2841 func (s *Server) help(ctx context.Context, icmd interface{}) (interface{}, error) { 2842 cmd := icmd.(*types.HelpCmd) 2843 // TODO: The "help" RPC should use a HTTP POST client when calling down to 2844 // dcrd for additional help methods. This avoids including websocket-only 2845 // requests in the help, which are not callable by wallet JSON-RPC clients. 2846 var rpc *dcrd.RPC 2847 n, _ := s.walletLoader.NetworkBackend() 2848 if client, ok := n.(*dcrd.RPC); ok { 2849 rpc = client 2850 } 2851 if cmd.Command == nil || *cmd.Command == "" { 2852 // Prepend chain server usage if it is available. 2853 usages := requestUsages 2854 if rpc != nil { 2855 var usage string 2856 err := rpc.Call(ctx, "help", &usage) 2857 if err != nil { 2858 return nil, err 2859 } 2860 if usage != "" { 2861 usages = "Chain server usage:\n\n" + usage + "\n\n" + 2862 "Wallet server usage (overrides chain requests):\n\n" + 2863 requestUsages 2864 } 2865 } 2866 return usages, nil 2867 } 2868 2869 defer helpDescsMu.Unlock() 2870 helpDescsMu.Lock() 2871 2872 if helpDescs == nil { 2873 // TODO: Allow other locales to be set via config or detemine 2874 // this from environment variables. For now, hardcode US 2875 // English. 2876 helpDescs = localeHelpDescs["en_US"]() 2877 } 2878 2879 helpText, ok := helpDescs[*cmd.Command] 2880 if ok { 2881 return helpText, nil 2882 } 2883 2884 // Return the chain server's detailed help if possible. 2885 var chainHelp string 2886 if rpc != nil { 2887 err := rpc.Call(ctx, "help", &chainHelp, *cmd.Command) 2888 if err != nil { 2889 return nil, err 2890 } 2891 } 2892 if chainHelp != "" { 2893 return chainHelp, nil 2894 } 2895 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "no help for method %q", *cmd.Command) 2896 } 2897 2898 // listAccounts handles a listaccounts request by returning a map of account 2899 // names to their balances. 2900 func (s *Server) listAccounts(ctx context.Context, icmd interface{}) (interface{}, error) { 2901 cmd := icmd.(*types.ListAccountsCmd) 2902 w, ok := s.walletLoader.LoadedWallet() 2903 if !ok { 2904 return nil, errUnloadedWallet 2905 } 2906 2907 accountBalances := map[string]float64{} 2908 results, err := w.AccountBalances(ctx, int32(*cmd.MinConf)) 2909 if err != nil { 2910 return nil, err 2911 } 2912 for _, result := range results { 2913 accountName, err := w.AccountName(ctx, result.Account) 2914 if err != nil { 2915 // Expect name lookup to succeed 2916 if errors.Is(err, errors.NotExist) { 2917 return nil, rpcError(dcrjson.ErrRPCInternal.Code, err) 2918 } 2919 return nil, err 2920 } 2921 accountBalances[accountName] = result.Spendable.ToCoin() 2922 } 2923 // Return the map. This will be marshaled into a JSON object. 2924 return accountBalances, nil 2925 } 2926 2927 // listLockUnspent handles a listlockunspent request by returning an slice of 2928 // all locked outpoints. 2929 func (s *Server) listLockUnspent(ctx context.Context, icmd interface{}) (interface{}, error) { 2930 w, ok := s.walletLoader.LoadedWallet() 2931 if !ok { 2932 return nil, errUnloadedWallet 2933 } 2934 2935 var account string 2936 cmd := icmd.(*types.ListLockUnspentCmd) 2937 if cmd.Account != nil { 2938 account = *cmd.Account 2939 } 2940 return w.LockedOutpoints(ctx, account) 2941 } 2942 2943 // listReceivedByAccount handles a listreceivedbyaccount request by returning 2944 // a slice of objects, each one containing: 2945 // 2946 // "account": the receiving account; 2947 // "amount": total amount received by the account; 2948 // "confirmations": number of confirmations of the most recent transaction. 2949 // 2950 // It takes two parameters: 2951 // 2952 // "minconf": minimum number of confirmations to consider a transaction - 2953 // default: one; 2954 // "includeempty": whether or not to include addresses that have no transactions - 2955 // default: false. 2956 func (s *Server) listReceivedByAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 2957 cmd := icmd.(*types.ListReceivedByAccountCmd) 2958 w, ok := s.walletLoader.LoadedWallet() 2959 if !ok { 2960 return nil, errUnloadedWallet 2961 } 2962 2963 results, err := w.TotalReceivedForAccounts(ctx, int32(*cmd.MinConf)) 2964 if err != nil { 2965 return nil, err 2966 } 2967 2968 jsonResults := make([]types.ListReceivedByAccountResult, 0, len(results)) 2969 for _, result := range results { 2970 jsonResults = append(jsonResults, types.ListReceivedByAccountResult{ 2971 Account: result.AccountName, 2972 Amount: result.TotalReceived.ToCoin(), 2973 Confirmations: uint64(result.LastConfirmation), 2974 }) 2975 } 2976 return jsonResults, nil 2977 } 2978 2979 // listReceivedByAddress handles a listreceivedbyaddress request by returning 2980 // a slice of objects, each one containing: 2981 // 2982 // "account": the account of the receiving address; 2983 // "address": the receiving address; 2984 // "amount": total amount received by the address; 2985 // "confirmations": number of confirmations of the most recent transaction. 2986 // 2987 // It takes two parameters: 2988 // 2989 // "minconf": minimum number of confirmations to consider a transaction - 2990 // default: one; 2991 // "includeempty": whether or not to include addresses that have no transactions - 2992 // default: false. 2993 func (s *Server) listReceivedByAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 2994 cmd := icmd.(*types.ListReceivedByAddressCmd) 2995 w, ok := s.walletLoader.LoadedWallet() 2996 if !ok { 2997 return nil, errUnloadedWallet 2998 } 2999 3000 // Intermediate data for each address. 3001 type AddrData struct { 3002 // Total amount received. 3003 amount dcrutil.Amount 3004 // Number of confirmations of the last transaction. 3005 confirmations int32 3006 // Hashes of transactions which include an output paying to the address 3007 tx []string 3008 } 3009 3010 _, tipHeight := w.MainChainTip(ctx) 3011 3012 // Intermediate data for all addresses. 3013 allAddrData := make(map[string]AddrData) 3014 // Create an AddrData entry for each active address in the account. 3015 // Otherwise we'll just get addresses from transactions later. 3016 sortedAddrs, err := w.SortedActivePaymentAddresses(ctx) 3017 if err != nil { 3018 return nil, err 3019 } 3020 for _, address := range sortedAddrs { 3021 // There might be duplicates, just overwrite them. 3022 allAddrData[address] = AddrData{} 3023 } 3024 3025 minConf := *cmd.MinConf 3026 var endHeight int32 3027 if minConf == 0 { 3028 endHeight = -1 3029 } else { 3030 endHeight = tipHeight - int32(minConf) + 1 3031 } 3032 err = wallet.UnstableAPI(w).RangeTransactions(ctx, 0, endHeight, func(details []udb.TxDetails) (bool, error) { 3033 confirmations := confirms(details[0].Block.Height, tipHeight) 3034 for _, tx := range details { 3035 for _, cred := range tx.Credits { 3036 pkVersion := tx.MsgTx.TxOut[cred.Index].Version 3037 pkScript := tx.MsgTx.TxOut[cred.Index].PkScript 3038 _, addrs := stdscript.ExtractAddrs(pkVersion, pkScript, w.ChainParams()) 3039 for _, addr := range addrs { 3040 addrStr := addr.String() 3041 addrData, ok := allAddrData[addrStr] 3042 if ok { 3043 addrData.amount += cred.Amount 3044 // Always overwrite confirmations with newer ones. 3045 addrData.confirmations = confirmations 3046 } else { 3047 addrData = AddrData{ 3048 amount: cred.Amount, 3049 confirmations: confirmations, 3050 } 3051 } 3052 addrData.tx = append(addrData.tx, tx.Hash.String()) 3053 allAddrData[addrStr] = addrData 3054 } 3055 } 3056 } 3057 return false, nil 3058 }) 3059 if err != nil { 3060 return nil, err 3061 } 3062 3063 // Massage address data into output format. 3064 numAddresses := len(allAddrData) 3065 ret := make([]types.ListReceivedByAddressResult, numAddresses) 3066 idx := 0 3067 for address, addrData := range allAddrData { 3068 ret[idx] = types.ListReceivedByAddressResult{ 3069 Address: address, 3070 Amount: addrData.amount.ToCoin(), 3071 Confirmations: uint64(addrData.confirmations), 3072 TxIDs: addrData.tx, 3073 } 3074 idx++ 3075 } 3076 return ret, nil 3077 } 3078 3079 // listSinceBlock handles a listsinceblock request by returning an array of maps 3080 // with details of sent and received wallet transactions since the given block. 3081 func (s *Server) listSinceBlock(ctx context.Context, icmd interface{}) (interface{}, error) { 3082 cmd := icmd.(*types.ListSinceBlockCmd) 3083 w, ok := s.walletLoader.LoadedWallet() 3084 if !ok { 3085 return nil, errUnloadedWallet 3086 } 3087 3088 targetConf := int32(*cmd.TargetConfirmations) 3089 if targetConf < 1 { 3090 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "target_confirmations must be positive") 3091 } 3092 3093 tipHash, tipHeight := w.MainChainTip(ctx) 3094 lastBlock := &tipHash 3095 if targetConf > 0 { 3096 id := wallet.NewBlockIdentifierFromHeight((tipHeight + 1) - targetConf) 3097 info, err := w.BlockInfo(ctx, id) 3098 if err != nil { 3099 return nil, err 3100 } 3101 3102 lastBlock = &info.Hash 3103 } 3104 3105 // TODO: This must begin at the fork point in the main chain, not the height 3106 // of this block. 3107 var end int32 3108 if cmd.BlockHash != nil { 3109 hash, err := chainhash.NewHashFromStr(*cmd.BlockHash) 3110 if err != nil { 3111 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3112 } 3113 header, err := w.BlockHeader(ctx, hash) 3114 if err != nil { 3115 return nil, err 3116 } 3117 end = int32(header.Height) 3118 } 3119 3120 txInfoList, err := w.ListSinceBlock(ctx, -1, end, tipHeight) 3121 if err != nil { 3122 return nil, err 3123 } 3124 3125 res := &types.ListSinceBlockResult{ 3126 Transactions: txInfoList, 3127 LastBlock: lastBlock.String(), 3128 } 3129 return res, nil 3130 } 3131 3132 // listTransactions handles a listtransactions request by returning an 3133 // array of maps with details of sent and recevied wallet transactions. 3134 func (s *Server) listTransactions(ctx context.Context, icmd interface{}) (interface{}, error) { 3135 cmd := icmd.(*types.ListTransactionsCmd) 3136 w, ok := s.walletLoader.LoadedWallet() 3137 if !ok { 3138 return nil, errUnloadedWallet 3139 } 3140 3141 // TODO: ListTransactions does not currently understand the difference 3142 // between transactions pertaining to one account from another. This 3143 // will be resolved when wtxmgr is combined with the waddrmgr namespace. 3144 3145 if cmd.Account != nil && *cmd.Account != "*" { 3146 // For now, don't bother trying to continue if the user 3147 // specified an account, since this can't be (easily or 3148 // efficiently) calculated. 3149 return nil, 3150 errors.E(`Transactions can not be searched by account. ` + 3151 `Use "*" to reference all accounts.`) 3152 } 3153 3154 return w.ListTransactions(ctx, *cmd.From, *cmd.Count) 3155 } 3156 3157 // listAddressTransactions handles a listaddresstransactions request by 3158 // returning an array of maps with details of spent and received wallet 3159 // transactions. The form of the reply is identical to listtransactions, 3160 // but the array elements are limited to transaction details which are 3161 // about the addresess included in the request. 3162 func (s *Server) listAddressTransactions(ctx context.Context, icmd interface{}) (interface{}, error) { 3163 cmd := icmd.(*types.ListAddressTransactionsCmd) 3164 w, ok := s.walletLoader.LoadedWallet() 3165 if !ok { 3166 return nil, errUnloadedWallet 3167 } 3168 3169 if cmd.Account != nil && *cmd.Account != "*" { 3170 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3171 "listing transactions for addresses may only be done for all accounts") 3172 } 3173 3174 // Decode addresses. 3175 hash160Map := make(map[string]struct{}) 3176 for _, addrStr := range cmd.Addresses { 3177 addr, err := decodeAddress(addrStr, w.ChainParams()) 3178 if err != nil { 3179 return nil, err 3180 } 3181 hash160er, ok := addr.(stdaddr.Hash160er) 3182 if !ok { 3183 // Not tracked by the wallet so skip reporting history 3184 // of this address. 3185 continue 3186 } 3187 hash160Map[string(hash160er.Hash160()[:])] = struct{}{} 3188 } 3189 3190 return w.ListAddressTransactions(ctx, hash160Map) 3191 } 3192 3193 // listAllTransactions handles a listalltransactions request by returning 3194 // a map with details of sent and recevied wallet transactions. This is 3195 // similar to ListTransactions, except it takes only a single optional 3196 // argument for the account name and replies with all transactions. 3197 func (s *Server) listAllTransactions(ctx context.Context, icmd interface{}) (interface{}, error) { 3198 cmd := icmd.(*types.ListAllTransactionsCmd) 3199 w, ok := s.walletLoader.LoadedWallet() 3200 if !ok { 3201 return nil, errUnloadedWallet 3202 } 3203 3204 if cmd.Account != nil && *cmd.Account != "*" { 3205 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3206 "listing all transactions may only be done for all accounts") 3207 } 3208 3209 return w.ListAllTransactions(ctx) 3210 } 3211 3212 // listUnspent handles the listunspent command. 3213 func (s *Server) listUnspent(ctx context.Context, icmd interface{}) (interface{}, error) { 3214 cmd := icmd.(*types.ListUnspentCmd) 3215 w, ok := s.walletLoader.LoadedWallet() 3216 if !ok { 3217 return nil, errUnloadedWallet 3218 } 3219 3220 var addresses map[string]struct{} 3221 if cmd.Addresses != nil { 3222 addresses = make(map[string]struct{}) 3223 // confirm that all of them are good: 3224 for _, as := range *cmd.Addresses { 3225 a, err := decodeAddress(as, w.ChainParams()) 3226 if err != nil { 3227 return nil, err 3228 } 3229 addresses[a.String()] = struct{}{} 3230 } 3231 } 3232 3233 var account string 3234 if cmd.Account != nil { 3235 account = *cmd.Account 3236 } 3237 result, err := w.ListUnspent(ctx, int32(*cmd.MinConf), int32(*cmd.MaxConf), addresses, account) 3238 if err != nil { 3239 if errors.Is(err, errors.NotExist) { 3240 return nil, errAddressNotInWallet 3241 } 3242 return nil, err 3243 } 3244 return result, nil 3245 } 3246 3247 // lockUnspent handles the lockunspent command. 3248 func (s *Server) lockUnspent(ctx context.Context, icmd interface{}) (interface{}, error) { 3249 cmd := icmd.(*types.LockUnspentCmd) 3250 w, ok := s.walletLoader.LoadedWallet() 3251 if !ok { 3252 return nil, errUnloadedWallet 3253 } 3254 3255 switch { 3256 case cmd.Unlock && len(cmd.Transactions) == 0: 3257 w.ResetLockedOutpoints() 3258 default: 3259 for _, input := range cmd.Transactions { 3260 txHash, err := chainhash.NewHashFromStr(input.Txid) 3261 if err != nil { 3262 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3263 } 3264 if cmd.Unlock { 3265 w.UnlockOutpoint(txHash, input.Vout) 3266 } else { 3267 w.LockOutpoint(txHash, input.Vout) 3268 } 3269 } 3270 } 3271 return true, nil 3272 } 3273 3274 // purchaseTicket indicates to the wallet that a ticket should be purchased 3275 // using all currently available funds. If the ticket could not be purchased 3276 // because there are not enough eligible funds, an error will be returned. 3277 func (s *Server) purchaseTicket(ctx context.Context, icmd interface{}) (interface{}, error) { 3278 // Enforce valid and positive spend limit. 3279 cmd := icmd.(*types.PurchaseTicketCmd) 3280 w, ok := s.walletLoader.LoadedWallet() 3281 if !ok { 3282 return nil, errUnloadedWallet 3283 } 3284 3285 n, err := w.NetworkBackend() 3286 if err != nil { 3287 return nil, err 3288 } 3289 3290 spendLimit, err := dcrutil.NewAmount(cmd.SpendLimit) 3291 if err != nil { 3292 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 3293 } 3294 if spendLimit < 0 { 3295 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative spend limit") 3296 } 3297 3298 account, err := w.AccountNumber(ctx, cmd.FromAccount) 3299 if err != nil { 3300 if errors.Is(err, errors.NotExist) { 3301 return nil, errAccountNotFound 3302 } 3303 return nil, err 3304 } 3305 3306 // Override the minimum number of required confirmations if specified 3307 // and enforce it is positive. 3308 minConf := int32(1) 3309 if cmd.MinConf != nil { 3310 minConf = int32(*cmd.MinConf) 3311 if minConf < 0 { 3312 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative minconf") 3313 } 3314 } 3315 3316 // Set ticket address if specified. 3317 var ticketAddr stdaddr.StakeAddress 3318 if cmd.TicketAddress != nil && *cmd.TicketAddress != "" { 3319 addr, err := decodeStakeAddress(*cmd.TicketAddress, w.ChainParams()) 3320 if err != nil { 3321 return nil, err 3322 } 3323 ticketAddr = addr 3324 } 3325 3326 numTickets := 1 3327 if cmd.NumTickets != nil { 3328 if *cmd.NumTickets > 1 { 3329 numTickets = *cmd.NumTickets 3330 } 3331 } 3332 3333 // Set pool address if specified. 3334 var poolAddr stdaddr.StakeAddress 3335 var poolFee float64 3336 if cmd.PoolAddress != nil && *cmd.PoolAddress != "" { 3337 addr, err := decodeStakeAddress(*cmd.PoolAddress, w.ChainParams()) 3338 if err != nil { 3339 return nil, err 3340 } 3341 poolAddr = addr 3342 3343 // Attempt to get the amount to send to 3344 // the pool after. 3345 if cmd.PoolFees == nil { 3346 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3347 "pool address set without pool fee") 3348 } 3349 poolFee = *cmd.PoolFees 3350 if !txrules.ValidPoolFeeRate(poolFee) { 3351 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3352 "pool fee percentage %v", poolFee) 3353 } 3354 } 3355 3356 // Set the expiry if specified. 3357 expiry := int32(0) 3358 if cmd.Expiry != nil { 3359 expiry = int32(*cmd.Expiry) 3360 } 3361 3362 dontSignTx := false 3363 if cmd.DontSignTx != nil { 3364 dontSignTx = *cmd.DontSignTx 3365 } 3366 3367 var csppServer string 3368 var mixedAccount uint32 3369 var mixedAccountBranch uint32 3370 var mixedSplitAccount uint32 3371 var changeAccount = account 3372 3373 if s.cfg.CSPPServer != "" { 3374 csppServer = s.cfg.CSPPServer 3375 mixedAccount, err = w.AccountNumber(ctx, s.cfg.MixAccount) 3376 if err != nil { 3377 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3378 "CSPP Server set, but error on mixed account: %v", err) 3379 } 3380 mixedAccountBranch = s.cfg.MixBranch 3381 if mixedAccountBranch != 0 && mixedAccountBranch != 1 { 3382 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3383 "MixedAccountBranch should be 0 or 1.") 3384 } 3385 _, err = w.AccountNumber(ctx, s.cfg.TicketSplitAccount) 3386 if err != nil { 3387 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3388 "CSPP Server set, but error on mixedSplitAccount: %v", err) 3389 } 3390 _, err = w.AccountNumber(ctx, s.cfg.MixChangeAccount) 3391 if err != nil { 3392 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3393 "CSPP Server set, but error on changeAccount: %v", err) 3394 } 3395 } 3396 3397 var vspClient *vsp.Client 3398 if s.cfg.VSPHost != "" { 3399 cfg := vsp.Config{ 3400 URL: s.cfg.VSPHost, 3401 PubKey: s.cfg.VSPPubKey, 3402 Dialer: s.cfg.Dial, 3403 Wallet: w, 3404 Policy: vsp.Policy{ 3405 MaxFee: s.cfg.VSPMaxFee, 3406 FeeAcct: account, 3407 ChangeAcct: changeAccount, 3408 }, 3409 } 3410 vspClient, err = loader.VSP(cfg) 3411 if err != nil { 3412 return nil, rpcErrorf(dcrjson.ErrRPCMisc, 3413 "VSP Server instance failed to start: %v", err) 3414 } 3415 } 3416 3417 request := &wallet.PurchaseTicketsRequest{ 3418 Count: numTickets, 3419 SourceAccount: account, 3420 VotingAddress: ticketAddr, 3421 MinConf: minConf, 3422 Expiry: expiry, 3423 DontSignTx: dontSignTx, 3424 VSPAddress: poolAddr, 3425 VSPFees: poolFee, 3426 3427 // CSPP 3428 CSPPServer: csppServer, 3429 DialCSPPServer: s.cfg.DialCSPPServer, 3430 MixedAccount: mixedAccount, 3431 MixedAccountBranch: mixedAccountBranch, 3432 MixedSplitAccount: mixedSplitAccount, 3433 ChangeAccount: changeAccount, 3434 } 3435 3436 if vspClient != nil { 3437 request.VSPFeePaymentProcess = vspClient.Process 3438 request.VSPFeeProcess = vspClient.FeePercentage 3439 } 3440 3441 ticketsResponse, err := w.PurchaseTickets(ctx, n, request) 3442 if err != nil { 3443 return nil, err 3444 } 3445 ticketsTx := ticketsResponse.Tickets 3446 splitTx := ticketsResponse.SplitTx 3447 3448 // If dontSignTx is false, we return the TicketHashes of the published txs. 3449 if !dontSignTx { 3450 hashes := ticketsResponse.TicketHashes 3451 hashStrs := make([]string, len(hashes)) 3452 for i := range hashes { 3453 hashStrs[i] = hashes[i].String() 3454 } 3455 3456 return hashStrs, err 3457 } 3458 3459 // Otherwise we return its unsigned tickets bytes and the splittx, so a 3460 // cold wallet can handle it. 3461 var stringBuilder strings.Builder 3462 unsignedTickets := make([]string, len(ticketsTx)) 3463 for i, mtx := range ticketsTx { 3464 err = mtx.Serialize(hex.NewEncoder(&stringBuilder)) 3465 if err != nil { 3466 return nil, err 3467 } 3468 unsignedTickets[i] = stringBuilder.String() 3469 stringBuilder.Reset() 3470 } 3471 3472 err = splitTx.Serialize(hex.NewEncoder(&stringBuilder)) 3473 if err != nil { 3474 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 3475 } 3476 3477 splitTxString := stringBuilder.String() 3478 3479 return types.CreateUnsignedTicketResult{ 3480 UnsignedTickets: unsignedTickets, 3481 SplitTx: splitTxString, 3482 }, nil 3483 } 3484 3485 // processUnmanagedTicket takes a ticket hash as an argument and attempts to 3486 // start managing it for the set vsp client from the config. 3487 func (s *Server) processUnmanagedTicket(ctx context.Context, icmd interface{}) (interface{}, error) { 3488 cmd := icmd.(*types.ProcessUnmanagedTicketCmd) 3489 3490 var ticketHash *chainhash.Hash 3491 if cmd.TicketHash != nil { 3492 hash, err := chainhash.NewHashFromStr(*cmd.TicketHash) 3493 if err != nil { 3494 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 3495 } 3496 ticketHash = hash 3497 } else { 3498 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "ticket hash must be provided") 3499 } 3500 vspHost := s.cfg.VSPHost 3501 if vspHost == "" { 3502 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "vsphost must be set in options") 3503 } 3504 vspClient, err := loader.LookupVSP(vspHost) 3505 if err != nil { 3506 return nil, err 3507 } 3508 3509 err = vspClient.ProcessTicket(ctx, ticketHash) 3510 if err != nil { 3511 return nil, err 3512 } 3513 3514 return nil, nil 3515 3516 } 3517 3518 // makeOutputs creates a slice of transaction outputs from a pair of address 3519 // strings to amounts. This is used to create the outputs to include in newly 3520 // created transactions from a JSON object describing the output destinations 3521 // and amounts. 3522 func makeOutputs(pairs map[string]dcrutil.Amount, chainParams *chaincfg.Params) ([]*wire.TxOut, error) { 3523 outputs := make([]*wire.TxOut, 0, len(pairs)) 3524 for addrStr, amt := range pairs { 3525 if amt < 0 { 3526 return nil, errNeedPositiveAmount 3527 } 3528 addr, err := decodeAddress(addrStr, chainParams) 3529 if err != nil { 3530 return nil, err 3531 } 3532 3533 vers, pkScript := addr.PaymentScript() 3534 3535 outputs = append(outputs, &wire.TxOut{ 3536 Value: int64(amt), 3537 PkScript: pkScript, 3538 Version: vers, 3539 }) 3540 } 3541 return outputs, nil 3542 } 3543 3544 // sendPairs creates and sends payment transactions. 3545 // It returns the transaction hash in string format upon success 3546 // All errors are returned in dcrjson.RPCError format 3547 func (s *Server) sendPairs(ctx context.Context, w *wallet.Wallet, amounts map[string]dcrutil.Amount, account uint32, minconf int32) (string, error) { 3548 changeAccount := account 3549 if s.cfg.CSPPServer != "" && s.cfg.MixAccount != "" && s.cfg.MixChangeAccount != "" { 3550 mixAccount, err := w.AccountNumber(ctx, s.cfg.MixAccount) 3551 if err != nil { 3552 return "", err 3553 } 3554 if account == mixAccount { 3555 changeAccount, err = w.AccountNumber(ctx, s.cfg.MixChangeAccount) 3556 if err != nil { 3557 return "", err 3558 } 3559 } 3560 } 3561 3562 outputs, err := makeOutputs(amounts, w.ChainParams()) 3563 if err != nil { 3564 return "", err 3565 } 3566 txSha, err := w.SendOutputs(ctx, outputs, account, changeAccount, minconf) 3567 if err != nil { 3568 if errors.Is(err, errors.Locked) { 3569 return "", errWalletUnlockNeeded 3570 } 3571 if errors.Is(err, errors.InsufficientBalance) { 3572 return "", rpcError(dcrjson.ErrRPCWalletInsufficientFunds, err) 3573 } 3574 return "", err 3575 } 3576 3577 return txSha.String(), nil 3578 } 3579 3580 // sendAmountToTreasury creates and sends payment transactions to the treasury. 3581 // It returns the transaction hash in string format upon success All errors are 3582 // returned in dcrjson.RPCError format 3583 func (s *Server) sendAmountToTreasury(ctx context.Context, w *wallet.Wallet, amount dcrutil.Amount, account uint32, minconf int32) (string, error) { 3584 changeAccount := account 3585 if s.cfg.CSPPServer != "" { 3586 mixAccount, err := w.AccountNumber(ctx, s.cfg.MixAccount) 3587 if err != nil { 3588 return "", err 3589 } 3590 if account == mixAccount { 3591 changeAccount, err = w.AccountNumber(ctx, 3592 s.cfg.MixChangeAccount) 3593 if err != nil { 3594 return "", err 3595 } 3596 } 3597 } 3598 3599 outputs := []*wire.TxOut{ 3600 { 3601 Value: int64(amount), 3602 PkScript: []byte{txscript.OP_TADD}, 3603 Version: wire.DefaultPkScriptVersion, 3604 }, 3605 } 3606 txSha, err := w.SendOutputsToTreasury(ctx, outputs, account, 3607 changeAccount, minconf) 3608 if err != nil { 3609 if errors.Is(err, errors.Locked) { 3610 return "", errWalletUnlockNeeded 3611 } 3612 if errors.Is(err, errors.InsufficientBalance) { 3613 return "", rpcError(dcrjson.ErrRPCWalletInsufficientFunds, 3614 err) 3615 } 3616 return "", err 3617 } 3618 3619 return txSha.String(), nil 3620 } 3621 3622 // sendOutputsFromTreasury creates and sends payment transactions from the treasury. 3623 // It returns the transaction hash in string format upon success All errors are 3624 // returned in dcrjson.RPCError format 3625 func (s *Server) sendOutputsFromTreasury(ctx context.Context, w *wallet.Wallet, cmd types.SendFromTreasuryCmd) (string, error) { 3626 // Look to see if the we have the private key imported. 3627 publicKey, err := decodeAddress(cmd.Key, w.ChainParams()) 3628 if err != nil { 3629 return "", err 3630 } 3631 privKey, zero, err := w.LoadPrivateKey(ctx, publicKey) 3632 if err != nil { 3633 return "", err 3634 } 3635 defer zero() 3636 3637 _, tipHeight := w.MainChainTip(ctx) 3638 3639 // OP_RETURN <8 Bytes ValueIn><24 byte random>. The encoded ValueIn is 3640 // added at the end of this function. 3641 var payload [32]byte 3642 _, err = rand.Read(payload[8:]) 3643 if err != nil { 3644 return "", rpcErrorf(dcrjson.ErrRPCInternal.Code, 3645 "sendOutputsFromTreasury Read: %v", err) 3646 } 3647 builder := txscript.NewScriptBuilder() 3648 builder.AddOp(txscript.OP_RETURN) 3649 builder.AddData(payload[:]) 3650 opretScript, err := builder.Script() 3651 if err != nil { 3652 return "", rpcErrorf(dcrjson.ErrRPCInternal.Code, 3653 "sendOutputsFromTreasury NewScriptBuilder: %v", err) 3654 } 3655 msgTx := wire.NewMsgTx() 3656 msgTx.Version = wire.TxVersionTreasury 3657 msgTx.AddTxOut(wire.NewTxOut(0, opretScript)) 3658 3659 // Calculate expiry. 3660 msgTx.Expiry = blockchain.CalcTSpendExpiry(int64(tipHeight+1), 3661 w.ChainParams().TreasuryVoteInterval, 3662 w.ChainParams().TreasuryVoteIntervalMultiplier) 3663 3664 // OP_TGEN and calculate totals. 3665 var totalPayout dcrutil.Amount 3666 for address, amount := range cmd.Amounts { 3667 amt, err := dcrutil.NewAmount(amount) 3668 if err != nil { 3669 return "", rpcError(dcrjson.ErrRPCInvalidParameter, err) 3670 } 3671 3672 // While looping calculate total amount 3673 totalPayout += amt 3674 3675 // Decode address. 3676 addr, err := decodeStakeAddress(address, w.ChainParams()) 3677 if err != nil { 3678 return "", err 3679 } 3680 3681 // Create OP_TGEN prefixed script. 3682 vers, script := addr.PayFromTreasuryScript() 3683 3684 // Make sure this is not dust. 3685 txOut := &wire.TxOut{ 3686 Value: int64(amt), 3687 Version: vers, 3688 PkScript: script, 3689 } 3690 if txrules.IsDustOutput(txOut, w.RelayFee()) { 3691 return "", rpcErrorf(dcrjson.ErrRPCInvalidParameter, 3692 "Amount is dust: %v %v", addr, amt) 3693 } 3694 3695 // Add to transaction. 3696 msgTx.AddTxOut(txOut) 3697 } 3698 3699 // Calculate fee. Inputs are <signature> <compressed key> OP_TSPEND. 3700 estimatedFee := txsizes.EstimateSerializeSize([]int{txsizes.TSPENDInputSize}, 3701 msgTx.TxOut, 0) 3702 fee := txrules.FeeForSerializeSize(w.RelayFee(), estimatedFee) 3703 3704 // Assemble TxIn. 3705 msgTx.AddTxIn(&wire.TxIn{ 3706 // Stakebase transactions have no inputs, so previous outpoint 3707 // is zero hash and max index. 3708 PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{}, 3709 wire.MaxPrevOutIndex, wire.TxTreeRegular), 3710 Sequence: wire.MaxTxInSequenceNum, 3711 ValueIn: int64(fee) + int64(totalPayout), 3712 BlockHeight: wire.NullBlockHeight, 3713 BlockIndex: wire.NullBlockIndex, 3714 SignatureScript: []byte{}, // Empty for now 3715 }) 3716 3717 // Encode total amount in first 8 bytes of TxOut[0] OP_RETURN. 3718 binary.LittleEndian.PutUint64(msgTx.TxOut[0].PkScript[2:2+8], 3719 uint64(fee)+uint64(totalPayout)) 3720 3721 // Calculate TSpend signature without SigHashType. 3722 privKeyBytes := privKey.Serialize() 3723 sigscript, err := sign.TSpendSignatureScript(msgTx, privKeyBytes) 3724 if err != nil { 3725 return "", err 3726 } 3727 msgTx.TxIn[0].SignatureScript = sigscript 3728 3729 _, _, err = stake.CheckTSpend(msgTx) 3730 if err != nil { 3731 return "", err 3732 } 3733 3734 // Send to dcrd. 3735 n, ok := s.walletLoader.NetworkBackend() 3736 if !ok { 3737 return "", errNoNetwork 3738 } 3739 err = n.PublishTransactions(ctx, msgTx) 3740 if err != nil { 3741 return "", err 3742 } 3743 3744 return msgTx.TxHash().String(), nil 3745 } 3746 3747 // treasuryPolicy returns voting policies for treasury spends by a particular 3748 // key. If a key is specified, that policy is returned; otherwise the policies 3749 // for all keys are returned in an array. If both a key and ticket hash are 3750 // provided, the per-ticket key policy is returned. 3751 func (s *Server) treasuryPolicy(ctx context.Context, icmd interface{}) (interface{}, error) { 3752 cmd := icmd.(*types.TreasuryPolicyCmd) 3753 w, ok := s.walletLoader.LoadedWallet() 3754 if !ok { 3755 return nil, errUnloadedWallet 3756 } 3757 3758 var ticketHash *chainhash.Hash 3759 if cmd.Ticket != nil && *cmd.Ticket != "" { 3760 var err error 3761 ticketHash, err = chainhash.NewHashFromStr(*cmd.Ticket) 3762 if err != nil { 3763 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3764 } 3765 } 3766 3767 if cmd.Key != nil && *cmd.Key != "" { 3768 pikey, err := hex.DecodeString(*cmd.Key) 3769 if err != nil { 3770 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3771 } 3772 var policy string 3773 switch w.TreasuryKeyPolicy(pikey, ticketHash) { 3774 case stake.TreasuryVoteYes: 3775 policy = "yes" 3776 case stake.TreasuryVoteNo: 3777 policy = "no" 3778 default: 3779 policy = "abstain" 3780 } 3781 res := &types.TreasuryPolicyResult{ 3782 Key: *cmd.Key, 3783 Policy: policy, 3784 } 3785 if cmd.Ticket != nil { 3786 res.Ticket = *cmd.Ticket 3787 } 3788 return res, nil 3789 } 3790 3791 policies := w.TreasuryKeyPolicies() 3792 res := make([]types.TreasuryPolicyResult, 0, len(policies)) 3793 for i := range policies { 3794 var policy string 3795 switch policies[i].Policy { 3796 case stake.TreasuryVoteYes: 3797 policy = "yes" 3798 case stake.TreasuryVoteNo: 3799 policy = "no" 3800 } 3801 r := types.TreasuryPolicyResult{ 3802 Key: hex.EncodeToString(policies[i].PiKey), 3803 Policy: policy, 3804 } 3805 if policies[i].Ticket != nil { 3806 r.Ticket = policies[i].Ticket.String() 3807 } 3808 res = append(res, r) 3809 } 3810 return res, nil 3811 } 3812 3813 // setDisapprovePercent sets the wallet's disapprove percentage. 3814 func (s *Server) setDisapprovePercent(ctx context.Context, icmd interface{}) (interface{}, error) { 3815 if s.activeNet.Net == wire.MainNet { 3816 return nil, dcrjson.ErrInvalidRequest 3817 } 3818 cmd := icmd.(*types.SetDisapprovePercentCmd) 3819 if cmd.Percent > 100 { 3820 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, 3821 errors.New("percent must be from 0 to 100")) 3822 } 3823 w, ok := s.walletLoader.LoadedWallet() 3824 if !ok { 3825 return nil, errUnloadedWallet 3826 } 3827 w.SetDisapprovePercent(cmd.Percent) 3828 return nil, nil 3829 } 3830 3831 // setTreasuryPolicy saves the voting policy for treasury spends by a particular 3832 // key, and optionally, setting the key policy used by a specific ticket. 3833 // 3834 // If a VSP host is configured in the application settings, the voting 3835 // preferences will also be set with the VSP. 3836 func (s *Server) setTreasuryPolicy(ctx context.Context, icmd interface{}) (interface{}, error) { 3837 cmd := icmd.(*types.SetTreasuryPolicyCmd) 3838 w, ok := s.walletLoader.LoadedWallet() 3839 if !ok { 3840 return nil, errUnloadedWallet 3841 } 3842 3843 var ticketHash *chainhash.Hash 3844 if cmd.Ticket != nil && *cmd.Ticket != "" { 3845 if len(*cmd.Ticket) != chainhash.MaxHashStringSize { 3846 err := fmt.Errorf("invalid ticket hash length, expected %d got %d", 3847 chainhash.MaxHashStringSize, len(*cmd.Ticket)) 3848 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3849 } 3850 var err error 3851 ticketHash, err = chainhash.NewHashFromStr(*cmd.Ticket) 3852 if err != nil { 3853 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3854 } 3855 } 3856 3857 pikey, err := hex.DecodeString(cmd.Key) 3858 if err != nil { 3859 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3860 } 3861 if len(pikey) != secp256k1.PubKeyBytesLenCompressed { 3862 err := errors.New("treasury key must be 33 bytes") 3863 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 3864 } 3865 var policy stake.TreasuryVoteT 3866 switch cmd.Policy { 3867 case "abstain", "invalid", "": 3868 policy = stake.TreasuryVoteInvalid 3869 case "yes": 3870 policy = stake.TreasuryVoteYes 3871 case "no": 3872 policy = stake.TreasuryVoteNo 3873 default: 3874 err := fmt.Errorf("unknown policy %q", cmd.Policy) 3875 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 3876 } 3877 3878 err = w.SetTreasuryKeyPolicy(ctx, pikey, policy, ticketHash) 3879 if err != nil { 3880 return nil, err 3881 } 3882 3883 // Update voting preferences on VSPs if required. 3884 policyMap := map[string]string{ 3885 cmd.Key: cmd.Policy, 3886 } 3887 err = s.updateVSPVoteChoices(ctx, w, ticketHash, nil, nil, policyMap) 3888 3889 return nil, err 3890 } 3891 3892 // tspendPolicy returns voting policies for particular treasury spends 3893 // transactions. If a tspend transaction hash is specified, that policy is 3894 // returned; otherwise the policies for all known tspends are returned in an 3895 // array. If both a tspend transaction hash and a ticket hash are provided, 3896 // the per-ticket tspend policy is returned. 3897 func (s *Server) tspendPolicy(ctx context.Context, icmd interface{}) (interface{}, error) { 3898 cmd := icmd.(*types.TSpendPolicyCmd) 3899 w, ok := s.walletLoader.LoadedWallet() 3900 if !ok { 3901 return nil, errUnloadedWallet 3902 } 3903 3904 var ticketHash *chainhash.Hash 3905 if cmd.Ticket != nil && *cmd.Ticket != "" { 3906 var err error 3907 ticketHash, err = chainhash.NewHashFromStr(*cmd.Ticket) 3908 if err != nil { 3909 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3910 } 3911 } 3912 3913 if cmd.Hash != nil && *cmd.Hash != "" { 3914 hash, err := chainhash.NewHashFromStr(*cmd.Hash) 3915 if err != nil { 3916 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3917 } 3918 var policy string 3919 switch w.TSpendPolicy(hash, ticketHash) { 3920 case stake.TreasuryVoteYes: 3921 policy = "yes" 3922 case stake.TreasuryVoteNo: 3923 policy = "no" 3924 default: 3925 policy = "abstain" 3926 } 3927 res := &types.TSpendPolicyResult{ 3928 Hash: *cmd.Hash, 3929 Policy: policy, 3930 } 3931 if cmd.Ticket != nil { 3932 res.Ticket = *cmd.Ticket 3933 } 3934 return res, nil 3935 } 3936 3937 tspends := w.GetAllTSpends(ctx) 3938 res := make([]types.TSpendPolicyResult, 0, len(tspends)) 3939 for i := range tspends { 3940 tspendHash := tspends[i].TxHash() 3941 p := w.TSpendPolicy(&tspendHash, ticketHash) 3942 3943 var policy string 3944 switch p { 3945 case stake.TreasuryVoteYes: 3946 policy = "yes" 3947 case stake.TreasuryVoteNo: 3948 policy = "no" 3949 } 3950 r := types.TSpendPolicyResult{ 3951 Hash: tspendHash.String(), 3952 Policy: policy, 3953 } 3954 if cmd.Ticket != nil { 3955 r.Ticket = *cmd.Ticket 3956 } 3957 res = append(res, r) 3958 } 3959 return res, nil 3960 } 3961 3962 // setTSpendPolicy saves the voting policy for a particular tspend transaction 3963 // hash, and optionally, setting the tspend policy used by a specific ticket. 3964 // 3965 // If a VSP host is configured in the application settings, the voting 3966 // preferences will also be set with the VSP. 3967 func (s *Server) setTSpendPolicy(ctx context.Context, icmd interface{}) (interface{}, error) { 3968 cmd := icmd.(*types.SetTSpendPolicyCmd) 3969 w, ok := s.walletLoader.LoadedWallet() 3970 if !ok { 3971 return nil, errUnloadedWallet 3972 } 3973 3974 if len(cmd.Hash) != chainhash.MaxHashStringSize { 3975 err := fmt.Errorf("invalid tspend hash length, expected %d got %d", 3976 chainhash.MaxHashStringSize, len(cmd.Hash)) 3977 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3978 } 3979 3980 hash, err := chainhash.NewHashFromStr(cmd.Hash) 3981 if err != nil { 3982 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3983 } 3984 3985 var ticketHash *chainhash.Hash 3986 if cmd.Ticket != nil && *cmd.Ticket != "" { 3987 if len(*cmd.Ticket) != chainhash.MaxHashStringSize { 3988 err := fmt.Errorf("invalid ticket hash length, expected %d got %d", 3989 chainhash.MaxHashStringSize, len(*cmd.Ticket)) 3990 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3991 } 3992 var err error 3993 ticketHash, err = chainhash.NewHashFromStr(*cmd.Ticket) 3994 if err != nil { 3995 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 3996 } 3997 } 3998 3999 var policy stake.TreasuryVoteT 4000 switch cmd.Policy { 4001 case "abstain", "invalid", "": 4002 policy = stake.TreasuryVoteInvalid 4003 case "yes": 4004 policy = stake.TreasuryVoteYes 4005 case "no": 4006 policy = stake.TreasuryVoteNo 4007 default: 4008 err := fmt.Errorf("unknown policy %q", cmd.Policy) 4009 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4010 } 4011 4012 err = w.SetTSpendPolicy(ctx, hash, policy, ticketHash) 4013 if err != nil { 4014 return nil, err 4015 } 4016 4017 // Update voting preferences on VSPs if required. 4018 policyMap := map[string]string{ 4019 cmd.Hash: cmd.Policy, 4020 } 4021 err = s.updateVSPVoteChoices(ctx, w, ticketHash, nil, policyMap, nil) 4022 return nil, err 4023 } 4024 4025 // redeemMultiSigOut receives a transaction hash/idx and fetches the first output 4026 // index or indices with known script hashes from the transaction. It then 4027 // construct a transaction with a single P2PKH paying to a specified address. 4028 // It signs any inputs that it can, then provides the raw transaction to 4029 // the user to export to others to sign. 4030 func (s *Server) redeemMultiSigOut(ctx context.Context, icmd interface{}) (interface{}, error) { 4031 cmd := icmd.(*types.RedeemMultiSigOutCmd) 4032 w, ok := s.walletLoader.LoadedWallet() 4033 if !ok { 4034 return nil, errUnloadedWallet 4035 } 4036 4037 // Convert the address to a useable format. If 4038 // we have no address, create a new address in 4039 // this wallet to send the output to. 4040 var addr stdaddr.Address 4041 var err error 4042 if cmd.Address != nil { 4043 addr, err = decodeAddress(*cmd.Address, w.ChainParams()) 4044 if err != nil { 4045 return nil, err 4046 } 4047 } else { 4048 account := uint32(udb.DefaultAccountNum) 4049 addr, err = w.NewInternalAddress(ctx, account, wallet.WithGapPolicyWrap()) 4050 if err != nil { 4051 return nil, err 4052 } 4053 } 4054 4055 // Lookup the multisignature output and get the amount 4056 // along with the script for that transaction. Then, 4057 // begin crafting a MsgTx. 4058 hash, err := chainhash.NewHashFromStr(cmd.Hash) 4059 if err != nil { 4060 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4061 } 4062 op := wire.OutPoint{ 4063 Hash: *hash, 4064 Index: cmd.Index, 4065 Tree: cmd.Tree, 4066 } 4067 p2shOutput, err := w.FetchP2SHMultiSigOutput(ctx, &op) 4068 if err != nil { 4069 return nil, err 4070 } 4071 sc := stdscript.DetermineScriptType(scriptVersionAssumed, p2shOutput.RedeemScript) 4072 if sc != stdscript.STMultiSig { 4073 return nil, errors.E("P2SH redeem script is not multisig") 4074 } 4075 msgTx := wire.NewMsgTx() 4076 txIn := wire.NewTxIn(&op, int64(p2shOutput.OutputAmount), nil) 4077 msgTx.AddTxIn(txIn) 4078 4079 _, pkScript := addr.PaymentScript() 4080 4081 err = w.PrepareRedeemMultiSigOutTxOutput(msgTx, p2shOutput, &pkScript) 4082 if err != nil { 4083 return nil, err 4084 } 4085 4086 // Start creating the SignRawTransactionCmd. 4087 _, outpointScript := p2shOutput.P2SHAddress.PaymentScript() 4088 outpointScriptStr := hex.EncodeToString(outpointScript) 4089 4090 rti := types.RawTxInput{ 4091 Txid: cmd.Hash, 4092 Vout: cmd.Index, 4093 Tree: cmd.Tree, 4094 ScriptPubKey: outpointScriptStr, 4095 RedeemScript: "", 4096 } 4097 rtis := []types.RawTxInput{rti} 4098 4099 var b strings.Builder 4100 b.Grow(2 * msgTx.SerializeSize()) 4101 err = msgTx.Serialize(hex.NewEncoder(&b)) 4102 if err != nil { 4103 return nil, err 4104 } 4105 sigHashAll := "ALL" 4106 4107 srtc := &types.SignRawTransactionCmd{ 4108 RawTx: b.String(), 4109 Inputs: &rtis, 4110 PrivKeys: &[]string{}, 4111 Flags: &sigHashAll, 4112 } 4113 4114 // Sign it and give the results to the user. 4115 signedTxResult, err := s.signRawTransaction(ctx, srtc) 4116 if signedTxResult == nil || err != nil { 4117 return nil, err 4118 } 4119 srtTyped := signedTxResult.(types.SignRawTransactionResult) 4120 return types.RedeemMultiSigOutResult(srtTyped), nil 4121 } 4122 4123 // redeemMultisigOuts receives a script hash (in the form of a 4124 // script hash address), looks up all the unspent outpoints associated 4125 // with that address, then generates a list of partially signed 4126 // transactions spending to either an address specified or internal 4127 // addresses in this wallet. 4128 func (s *Server) redeemMultiSigOuts(ctx context.Context, icmd interface{}) (interface{}, error) { 4129 cmd := icmd.(*types.RedeemMultiSigOutsCmd) 4130 w, ok := s.walletLoader.LoadedWallet() 4131 if !ok { 4132 return nil, errUnloadedWallet 4133 } 4134 4135 // Get all the multisignature outpoints that are unspent for this 4136 // address. 4137 addr, err := decodeAddress(cmd.FromScrAddress, w.ChainParams()) 4138 if err != nil { 4139 return nil, err 4140 } 4141 p2shAddr, ok := addr.(*stdaddr.AddressScriptHashV0) 4142 if !ok { 4143 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "address is not P2SH") 4144 } 4145 msos, err := wallet.UnstableAPI(w).UnspentMultisigCreditsForAddress(ctx, p2shAddr) 4146 if err != nil { 4147 return nil, err 4148 } 4149 max := uint32(0xffffffff) 4150 if cmd.Number != nil { 4151 max = uint32(*cmd.Number) 4152 } 4153 4154 itr := uint32(0) 4155 rmsoResults := make([]types.RedeemMultiSigOutResult, len(msos)) 4156 for i, mso := range msos { 4157 if itr > max { 4158 break 4159 } 4160 4161 rmsoRequest := &types.RedeemMultiSigOutCmd{ 4162 Hash: mso.OutPoint.Hash.String(), 4163 Index: mso.OutPoint.Index, 4164 Tree: mso.OutPoint.Tree, 4165 Address: cmd.ToAddress, 4166 } 4167 redeemResult, err := s.redeemMultiSigOut(ctx, rmsoRequest) 4168 if err != nil { 4169 return nil, err 4170 } 4171 redeemResultTyped := redeemResult.(types.RedeemMultiSigOutResult) 4172 rmsoResults[i] = redeemResultTyped 4173 4174 itr++ 4175 } 4176 4177 return types.RedeemMultiSigOutsResult{Results: rmsoResults}, nil 4178 } 4179 4180 // rescanWallet initiates a rescan of the block chain for wallet data, blocking 4181 // until the rescan completes or exits with an error. 4182 func (s *Server) rescanWallet(ctx context.Context, icmd interface{}) (interface{}, error) { 4183 cmd := icmd.(*types.RescanWalletCmd) 4184 w, ok := s.walletLoader.LoadedWallet() 4185 if !ok { 4186 return nil, errUnloadedWallet 4187 } 4188 4189 n, ok := s.walletLoader.NetworkBackend() 4190 if !ok { 4191 return nil, errNoNetwork 4192 } 4193 4194 err := w.RescanFromHeight(ctx, n, int32(*cmd.BeginHeight)) 4195 return nil, err 4196 } 4197 4198 // stakePoolUserInfo returns the ticket information for a given user from the 4199 // stake pool. 4200 func (s *Server) stakePoolUserInfo(ctx context.Context, icmd interface{}) (interface{}, error) { 4201 cmd := icmd.(*types.StakePoolUserInfoCmd) 4202 w, ok := s.walletLoader.LoadedWallet() 4203 if !ok { 4204 return nil, errUnloadedWallet 4205 } 4206 4207 userAddr, err := decodeStakeAddress(cmd.User, w.ChainParams()) 4208 if err != nil { 4209 return nil, err 4210 } 4211 spui, err := w.StakePoolUserInfo(ctx, userAddr) 4212 if err != nil { 4213 return nil, err 4214 } 4215 4216 resp := new(types.StakePoolUserInfoResult) 4217 resp.Tickets = make([]types.PoolUserTicket, 0, len(spui.Tickets)) 4218 resp.InvalidTickets = make([]string, 0, len(spui.InvalidTickets)) 4219 _, height := w.MainChainTip(ctx) 4220 for _, ticket := range spui.Tickets { 4221 var ticketRes types.PoolUserTicket 4222 4223 status := "" 4224 switch ticket.Status { 4225 case udb.TSImmatureOrLive: 4226 maturedHeight := int32(ticket.HeightTicket + uint32(w.ChainParams().TicketMaturity) + 1) 4227 4228 if height >= maturedHeight { 4229 status = "live" 4230 } else { 4231 status = "immature" 4232 } 4233 case udb.TSVoted: 4234 status = "voted" 4235 case udb.TSMissed: 4236 status = "missed" 4237 if ticket.HeightSpent-ticket.HeightTicket >= w.ChainParams().TicketExpiry { 4238 status = "expired" 4239 } 4240 } 4241 ticketRes.Status = status 4242 4243 ticketRes.Ticket = ticket.Ticket.String() 4244 ticketRes.TicketHeight = ticket.HeightTicket 4245 ticketRes.SpentBy = ticket.SpentBy.String() 4246 ticketRes.SpentByHeight = ticket.HeightSpent 4247 4248 resp.Tickets = append(resp.Tickets, ticketRes) 4249 } 4250 for _, invalid := range spui.InvalidTickets { 4251 invalidTicket := invalid.String() 4252 4253 resp.InvalidTickets = append(resp.InvalidTickets, invalidTicket) 4254 } 4255 4256 return resp, nil 4257 } 4258 4259 func (s *Server) ticketInfo(ctx context.Context, icmd interface{}) (interface{}, error) { 4260 cmd := icmd.(*types.TicketInfoCmd) 4261 w, ok := s.walletLoader.LoadedWallet() 4262 if !ok { 4263 return nil, errUnloadedWallet 4264 } 4265 4266 res := make([]types.TicketInfoResult, 0) 4267 4268 start := wallet.NewBlockIdentifierFromHeight(*cmd.StartHeight) 4269 end := wallet.NewBlockIdentifierFromHeight(-1) 4270 tmptx := new(wire.MsgTx) 4271 err := w.GetTickets(ctx, func(ts []*wallet.TicketSummary, h *wire.BlockHeader) (bool, error) { 4272 for _, t := range ts { 4273 status := t.Status 4274 if status == wallet.TicketStatusUnmined { 4275 // Standardize on immature. An unmined ticket 4276 // can be determined by the block height field 4277 // and the lack of a block hash. 4278 status = wallet.TicketStatusImmature 4279 } 4280 err := tmptx.Deserialize(bytes.NewReader(t.Ticket.Transaction)) 4281 if err != nil { 4282 return false, err 4283 } 4284 out := tmptx.TxOut[0] 4285 info := types.TicketInfoResult{ 4286 Hash: t.Ticket.Hash.String(), 4287 Cost: dcrutil.Amount(out.Value).ToCoin(), 4288 BlockHeight: -1, 4289 Status: status.String(), 4290 } 4291 4292 _, addrs := stdscript.ExtractAddrs(out.Version, out.PkScript, w.ChainParams()) 4293 if len(addrs) == 0 { 4294 return false, errors.New("unable to decode ticket pkScript") 4295 } 4296 info.VotingAddress = addrs[0].String() 4297 if h != nil { 4298 info.BlockHash = h.BlockHash().String() 4299 info.BlockHeight = int32(h.Height) 4300 } 4301 if t.Spender != nil { 4302 hash := t.Spender.Hash.String() 4303 if t.Spender.Type == wallet.TransactionTypeRevocation { 4304 info.Revocation = hash 4305 } else { 4306 info.Vote = hash 4307 } 4308 } 4309 4310 choices, _, err := w.AgendaChoices(ctx, t.Ticket.Hash) 4311 if err != nil { 4312 return false, err 4313 } 4314 info.Choices = make([]types.VoteChoice, len(choices)) 4315 for i := range choices { 4316 info.Choices[i].AgendaID = choices[i].AgendaID 4317 info.Choices[i].ChoiceID = choices[i].ChoiceID 4318 } 4319 4320 host, err := w.VSPHostForTicket(ctx, t.Ticket.Hash) 4321 if err != nil && !errors.Is(err, errors.NotExist) { 4322 return false, err 4323 } 4324 info.VSPHost = host 4325 4326 res = append(res, info) 4327 } 4328 return false, nil 4329 }, start, end) 4330 4331 return res, err 4332 } 4333 4334 // ticketsForAddress retrieves all ticket hashes that have the passed voting 4335 // address. It will only return tickets that are in the mempool or blockchain, 4336 // and should not return pruned tickets. 4337 func (s *Server) ticketsForAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 4338 cmd := icmd.(*types.TicketsForAddressCmd) 4339 w, ok := s.walletLoader.LoadedWallet() 4340 if !ok { 4341 return nil, errUnloadedWallet 4342 } 4343 4344 addr, err := stdaddr.DecodeAddress(cmd.Address, w.ChainParams()) 4345 if err != nil { 4346 return nil, err 4347 } 4348 4349 ticketHashes, err := w.TicketHashesForVotingAddress(ctx, addr) 4350 if err != nil { 4351 return nil, err 4352 } 4353 4354 ticketHashStrs := make([]string, 0, len(ticketHashes)) 4355 for _, hash := range ticketHashes { 4356 ticketHashStrs = append(ticketHashStrs, hash.String()) 4357 } 4358 4359 return dcrdtypes.TicketsForAddressResult{Tickets: ticketHashStrs}, nil 4360 } 4361 4362 func isNilOrEmpty(s *string) bool { 4363 return s == nil || *s == "" 4364 } 4365 4366 // sendFrom handles a sendfrom RPC request by creating a new transaction 4367 // spending unspent transaction outputs for a wallet to another payment 4368 // address. Leftover inputs not sent to the payment address or a fee for 4369 // the miner are sent back to a new address in the wallet. Upon success, 4370 // the TxID for the created transaction is returned. 4371 func (s *Server) sendFrom(ctx context.Context, icmd interface{}) (interface{}, error) { 4372 cmd := icmd.(*types.SendFromCmd) 4373 w, ok := s.walletLoader.LoadedWallet() 4374 if !ok { 4375 return nil, errUnloadedWallet 4376 } 4377 4378 // Transaction comments are not yet supported. Error instead of 4379 // pretending to save them. 4380 if !isNilOrEmpty(cmd.Comment) || !isNilOrEmpty(cmd.CommentTo) { 4381 return nil, rpcErrorf(dcrjson.ErrRPCUnimplemented, "transaction comments are unsupported") 4382 } 4383 4384 account, err := w.AccountNumber(ctx, cmd.FromAccount) 4385 if err != nil { 4386 return nil, err 4387 } 4388 4389 // Check that signed integer parameters are positive. 4390 if cmd.Amount < 0 { 4391 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative amount") 4392 } 4393 minConf := int32(*cmd.MinConf) 4394 if minConf < 0 { 4395 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative minconf") 4396 } 4397 // Create map of address and amount pairs. 4398 amt, err := dcrutil.NewAmount(cmd.Amount) 4399 if err != nil { 4400 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4401 } 4402 pairs := map[string]dcrutil.Amount{ 4403 cmd.ToAddress: amt, 4404 } 4405 4406 return s.sendPairs(ctx, w, pairs, account, minConf) 4407 } 4408 4409 // sendMany handles a sendmany RPC request by creating a new transaction 4410 // spending unspent transaction outputs for a wallet to any number of 4411 // payment addresses. Leftover inputs not sent to the payment address 4412 // or a fee for the miner are sent back to a new address in the wallet. 4413 // Upon success, the TxID for the created transaction is returned. 4414 func (s *Server) sendMany(ctx context.Context, icmd interface{}) (interface{}, error) { 4415 cmd := icmd.(*types.SendManyCmd) 4416 w, ok := s.walletLoader.LoadedWallet() 4417 if !ok { 4418 return nil, errUnloadedWallet 4419 } 4420 4421 // Transaction comments are not yet supported. Error instead of 4422 // pretending to save them. 4423 if !isNilOrEmpty(cmd.Comment) { 4424 return nil, rpcErrorf(dcrjson.ErrRPCUnimplemented, "transaction comments are unsupported") 4425 } 4426 4427 account, err := w.AccountNumber(ctx, cmd.FromAccount) 4428 if err != nil { 4429 return nil, err 4430 } 4431 4432 // Check that minconf is positive. 4433 minConf := int32(*cmd.MinConf) 4434 if minConf < 0 { 4435 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative minconf") 4436 } 4437 4438 // Recreate address/amount pairs, using dcrutil.Amount. 4439 pairs := make(map[string]dcrutil.Amount, len(cmd.Amounts)) 4440 for k, v := range cmd.Amounts { 4441 amt, err := dcrutil.NewAmount(v) 4442 if err != nil { 4443 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4444 } 4445 pairs[k] = amt 4446 } 4447 4448 return s.sendPairs(ctx, w, pairs, account, minConf) 4449 } 4450 4451 // sendToAddress handles a sendtoaddress RPC request by creating a new 4452 // transaction spending unspent transaction outputs for a wallet to another 4453 // payment address. Leftover inputs not sent to the payment address or a fee 4454 // for the miner are sent back to a new address in the wallet. Upon success, 4455 // the TxID for the created transaction is returned. 4456 func (s *Server) sendToAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 4457 cmd := icmd.(*types.SendToAddressCmd) 4458 w, ok := s.walletLoader.LoadedWallet() 4459 if !ok { 4460 return nil, errUnloadedWallet 4461 } 4462 4463 // Transaction comments are not yet supported. Error instead of 4464 // pretending to save them. 4465 if !isNilOrEmpty(cmd.Comment) || !isNilOrEmpty(cmd.CommentTo) { 4466 return nil, rpcErrorf(dcrjson.ErrRPCUnimplemented, "transaction comments are unsupported") 4467 } 4468 4469 amt, err := dcrutil.NewAmount(cmd.Amount) 4470 if err != nil { 4471 return nil, err 4472 } 4473 4474 // Check that signed integer parameters are positive. 4475 if amt < 0 { 4476 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative amount") 4477 } 4478 4479 // Mock up map of address and amount pairs. 4480 pairs := map[string]dcrutil.Amount{ 4481 cmd.Address: amt, 4482 } 4483 4484 // sendtoaddress always spends from the default account, this matches bitcoind 4485 return s.sendPairs(ctx, w, pairs, udb.DefaultAccountNum, 1) 4486 } 4487 4488 // sendToMultiSig handles a sendtomultisig RPC request by creating a new 4489 // transaction spending amount many funds to an output containing a multi- 4490 // signature script hash. The function will fail if there isn't at least one 4491 // public key in the public key list that corresponds to one that is owned 4492 // locally. 4493 // Upon successfully sending the transaction to the daemon, the script hash 4494 // is stored in the transaction manager and the corresponding address 4495 // specified to be watched by the daemon. 4496 // The function returns a tx hash, P2SH address, and a multisig script if 4497 // successful. 4498 // TODO Use with non-default accounts as well 4499 func (s *Server) sendToMultiSig(ctx context.Context, icmd interface{}) (interface{}, error) { 4500 cmd := icmd.(*types.SendToMultiSigCmd) 4501 w, ok := s.walletLoader.LoadedWallet() 4502 if !ok { 4503 return nil, errUnloadedWallet 4504 } 4505 4506 account := uint32(udb.DefaultAccountNum) 4507 amount, err := dcrutil.NewAmount(cmd.Amount) 4508 if err != nil { 4509 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4510 } 4511 nrequired := int8(*cmd.NRequired) 4512 minconf := int32(*cmd.MinConf) 4513 4514 pubKeys, err := walletPubKeys(ctx, w, cmd.Pubkeys) 4515 if err != nil { 4516 return nil, err 4517 } 4518 4519 tx, addr, script, err := 4520 w.CreateMultisigTx(ctx, account, amount, pubKeys, nrequired, minconf) 4521 if err != nil { 4522 return nil, err 4523 } 4524 4525 result := &types.SendToMultiSigResult{ 4526 TxHash: tx.MsgTx.TxHash().String(), 4527 Address: addr.String(), 4528 RedeemScript: hex.EncodeToString(script), 4529 } 4530 4531 log.Infof("Successfully sent funds to multisignature output in "+ 4532 "transaction %v", tx.MsgTx.TxHash().String()) 4533 4534 return result, nil 4535 } 4536 4537 // sendRawTransaction handles a sendrawtransaction RPC request by decoding hex 4538 // transaction and sending it to the network backend for propagation. 4539 func (s *Server) sendRawTransaction(ctx context.Context, icmd interface{}) (interface{}, error) { 4540 cmd := icmd.(*types.SendRawTransactionCmd) 4541 w, ok := s.walletLoader.LoadedWallet() 4542 if !ok { 4543 return nil, errUnloadedWallet 4544 } 4545 4546 n, err := w.NetworkBackend() 4547 if err != nil { 4548 return nil, err 4549 } 4550 4551 msgtx := wire.NewMsgTx() 4552 err = msgtx.Deserialize(hex.NewDecoder(strings.NewReader(cmd.HexTx))) 4553 if err != nil { 4554 return nil, rpcError(dcrjson.ErrRPCDeserialization, err) 4555 } 4556 4557 if !*cmd.AllowHighFees { 4558 highFees, err := txrules.TxPaysHighFees(msgtx) 4559 if err != nil { 4560 return nil, err 4561 } 4562 if highFees { 4563 return nil, errors.E(errors.Policy, "high fees") 4564 } 4565 } 4566 4567 txHash, err := w.PublishTransaction(ctx, msgtx, n) 4568 if err != nil { 4569 return nil, err 4570 } 4571 4572 return txHash.String(), nil 4573 } 4574 4575 // sendToTreasury handles a sendtotreasury RPC request by creating a new 4576 // transaction spending unspent transaction outputs for a wallet to the 4577 // treasury. Leftover inputs not sent to the payment address or a fee for the 4578 // miner are sent back to a new address in the wallet. Upon success, the TxID 4579 // for the created transaction is returned. 4580 func (s *Server) sendToTreasury(ctx context.Context, icmd interface{}) (interface{}, error) { 4581 cmd := icmd.(*types.SendToTreasuryCmd) 4582 w, ok := s.walletLoader.LoadedWallet() 4583 if !ok { 4584 return nil, errUnloadedWallet 4585 } 4586 4587 amt, err := dcrutil.NewAmount(cmd.Amount) 4588 if err != nil { 4589 return nil, err 4590 } 4591 4592 // Check that signed integer parameters are positive. 4593 if amt <= 0 { 4594 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative amount") 4595 } 4596 4597 // sendtotreasury always spends from the default account. 4598 return s.sendAmountToTreasury(ctx, w, amt, udb.DefaultAccountNum, 1) 4599 } 4600 4601 // transaction spending treasury balance. 4602 // Upon success, the TxID for the created transaction is returned. 4603 func (s *Server) sendFromTreasury(ctx context.Context, icmd interface{}) (interface{}, error) { 4604 cmd := icmd.(*types.SendFromTreasuryCmd) 4605 w, ok := s.walletLoader.LoadedWallet() 4606 if !ok { 4607 return nil, errUnloadedWallet 4608 } 4609 4610 return s.sendOutputsFromTreasury(ctx, w, *cmd) 4611 } 4612 4613 // setTxFee sets the transaction fee per kilobyte added to transactions. 4614 func (s *Server) setTxFee(ctx context.Context, icmd interface{}) (interface{}, error) { 4615 cmd := icmd.(*types.SetTxFeeCmd) 4616 w, ok := s.walletLoader.LoadedWallet() 4617 if !ok { 4618 return nil, errUnloadedWallet 4619 } 4620 4621 // Check that amount is not negative. 4622 if cmd.Amount < 0 { 4623 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "negative amount") 4624 } 4625 4626 relayFee, err := dcrutil.NewAmount(cmd.Amount) 4627 if err != nil { 4628 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4629 } 4630 w.SetRelayFee(relayFee) 4631 4632 // A boolean true result is returned upon success. 4633 return true, nil 4634 } 4635 4636 // setVoteChoice handles a setvotechoice request by modifying the preferred 4637 // choice for a voting agenda. 4638 // 4639 // If a VSP host is configured in the application settings, the voting 4640 // preferences will also be set with the VSP. 4641 func (s *Server) setVoteChoice(ctx context.Context, icmd interface{}) (interface{}, error) { 4642 cmd := icmd.(*types.SetVoteChoiceCmd) 4643 w, ok := s.walletLoader.LoadedWallet() 4644 if !ok { 4645 return nil, errUnloadedWallet 4646 } 4647 4648 var ticketHash *chainhash.Hash 4649 if cmd.TicketHash != nil { 4650 hash, err := chainhash.NewHashFromStr(*cmd.TicketHash) 4651 if err != nil { 4652 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4653 } 4654 ticketHash = hash 4655 } 4656 4657 choice := wallet.AgendaChoices{ 4658 { 4659 AgendaID: cmd.AgendaID, 4660 ChoiceID: cmd.ChoiceID, 4661 }, 4662 } 4663 _, err := w.SetAgendaChoices(ctx, ticketHash, choice...) 4664 if err != nil { 4665 return nil, err 4666 } 4667 4668 // Update voting preferences on VSPs if required. 4669 err = s.updateVSPVoteChoices(ctx, w, ticketHash, choice, nil, nil) 4670 return nil, err 4671 } 4672 4673 func (s *Server) updateVSPVoteChoices(ctx context.Context, w *wallet.Wallet, ticketHash *chainhash.Hash, 4674 choices wallet.AgendaChoices, tspendPolicy map[string]string, treasuryPolicy map[string]string) error { 4675 4676 if ticketHash != nil { 4677 vspHost, err := w.VSPHostForTicket(ctx, ticketHash) 4678 if err != nil { 4679 if errors.Is(err, errors.NotExist) { 4680 // Ticket is not registered with a VSP, nothing more to do here. 4681 return nil 4682 } 4683 return err 4684 } 4685 vspClient, err := loader.LookupVSP(vspHost) 4686 if err != nil { 4687 return err 4688 } 4689 err = vspClient.SetVoteChoice(ctx, ticketHash, choices.Map(), tspendPolicy, treasuryPolicy) 4690 return err 4691 } 4692 var firstErr error 4693 err := w.ForUnspentUnexpiredTickets(ctx, func(hash *chainhash.Hash) error { 4694 vspHost, err := w.VSPHostForTicket(ctx, hash) 4695 if err != nil && firstErr == nil { 4696 if errors.Is(err, errors.NotExist) { 4697 // Ticket is not registered with a VSP, nothing more to do here. 4698 return nil 4699 } 4700 firstErr = err 4701 return nil 4702 } 4703 vspClient, err := loader.LookupVSP(vspHost) 4704 if err != nil && firstErr == nil { 4705 firstErr = err 4706 return nil 4707 } 4708 // Never return errors here, so all tickets are tried. 4709 // The first error will be returned to the user. 4710 err = vspClient.SetVoteChoice(ctx, hash, choices.Map(), tspendPolicy, treasuryPolicy) 4711 if err != nil && firstErr == nil { 4712 firstErr = err 4713 } 4714 return nil 4715 }) 4716 if err != nil { 4717 return err 4718 } 4719 return firstErr 4720 } 4721 4722 // signMessage signs the given message with the private key for the given 4723 // address 4724 func (s *Server) signMessage(ctx context.Context, icmd interface{}) (interface{}, error) { 4725 cmd := icmd.(*types.SignMessageCmd) 4726 w, ok := s.walletLoader.LoadedWallet() 4727 if !ok { 4728 return nil, errUnloadedWallet 4729 } 4730 4731 addr, err := decodeAddress(cmd.Address, w.ChainParams()) 4732 if err != nil { 4733 return nil, err 4734 } 4735 sig, err := w.SignMessage(ctx, cmd.Message, addr) 4736 if err != nil { 4737 if errors.Is(err, errors.NotExist) { 4738 return nil, errAddressNotInWallet 4739 } 4740 if errors.Is(err, errors.Locked) { 4741 return nil, errWalletUnlockNeeded 4742 } 4743 return nil, err 4744 } 4745 return base64.StdEncoding.EncodeToString(sig), nil 4746 } 4747 4748 // signRawTransaction handles the signrawtransaction command. 4749 // 4750 // chainClient may be nil, in which case it was called by the NoChainRPC 4751 // variant. It must be checked before all usage. 4752 func (s *Server) signRawTransaction(ctx context.Context, icmd interface{}) (interface{}, error) { 4753 cmd := icmd.(*types.SignRawTransactionCmd) 4754 w, ok := s.walletLoader.LoadedWallet() 4755 if !ok { 4756 return nil, errUnloadedWallet 4757 } 4758 4759 tx := wire.NewMsgTx() 4760 err := tx.Deserialize(hex.NewDecoder(strings.NewReader(cmd.RawTx))) 4761 if err != nil { 4762 return nil, rpcError(dcrjson.ErrRPCDeserialization, err) 4763 } 4764 if len(tx.TxIn) == 0 { 4765 err := errors.New("transaction with no inputs cannot be signed") 4766 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4767 } 4768 4769 var hashType txscript.SigHashType 4770 switch *cmd.Flags { 4771 case "ALL": 4772 hashType = txscript.SigHashAll 4773 case "NONE": 4774 hashType = txscript.SigHashNone 4775 case "SINGLE": 4776 hashType = txscript.SigHashSingle 4777 case "ALL|ANYONECANPAY": 4778 hashType = txscript.SigHashAll | txscript.SigHashAnyOneCanPay 4779 case "NONE|ANYONECANPAY": 4780 hashType = txscript.SigHashNone | txscript.SigHashAnyOneCanPay 4781 case "SINGLE|ANYONECANPAY": 4782 hashType = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay 4783 case "ssgen": // Special case of SigHashAll 4784 hashType = txscript.SigHashAll 4785 case "ssrtx": // Special case of SigHashAll 4786 hashType = txscript.SigHashAll 4787 default: 4788 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, "invalid sighash flag") 4789 } 4790 4791 // TODO: really we probably should look these up with dcrd anyway to 4792 // make sure that they match the blockchain if present. 4793 inputs := make(map[wire.OutPoint][]byte) 4794 scripts := make(map[string][]byte) 4795 var cmdInputs []types.RawTxInput 4796 if cmd.Inputs != nil { 4797 cmdInputs = *cmd.Inputs 4798 } 4799 for _, rti := range cmdInputs { 4800 inputSha, err := chainhash.NewHashFromStr(rti.Txid) 4801 if err != nil { 4802 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 4803 } 4804 4805 script, err := decodeHexStr(rti.ScriptPubKey) 4806 if err != nil { 4807 return nil, err 4808 } 4809 4810 // redeemScript is only actually used iff the user provided 4811 // private keys. In which case, it is used to get the scripts 4812 // for signing. If the user did not provide keys then we always 4813 // get scripts from the wallet. 4814 // Empty strings are ok for this one and hex.DecodeString will 4815 // DTRT. 4816 // Note that redeemScript is NOT only the redeemscript 4817 // required to be appended to the end of a P2SH output 4818 // spend, but the entire signature script for spending 4819 // *any* outpoint with dummy values inserted into it 4820 // that can later be replacing by txscript's sign. 4821 if cmd.PrivKeys != nil && len(*cmd.PrivKeys) != 0 { 4822 redeemScript, err := decodeHexStr(rti.RedeemScript) 4823 if err != nil { 4824 return nil, err 4825 } 4826 4827 addr, err := stdaddr.NewAddressScriptHashV0(redeemScript, 4828 w.ChainParams()) 4829 if err != nil { 4830 return nil, err 4831 } 4832 scripts[addr.String()] = redeemScript 4833 } 4834 inputs[wire.OutPoint{ 4835 Hash: *inputSha, 4836 Tree: rti.Tree, 4837 Index: rti.Vout, 4838 }] = script 4839 } 4840 4841 // Now we go and look for any inputs that we were not provided by 4842 // querying dcrd with getrawtransaction. We queue up a bunch of async 4843 // requests and will wait for replies after we have checked the rest of 4844 // the arguments. 4845 requested := make(map[wire.OutPoint]*dcrdtypes.GetTxOutResult) 4846 var requestedMu sync.Mutex 4847 requestedGroup, gctx := errgroup.WithContext(ctx) 4848 n, _ := s.walletLoader.NetworkBackend() 4849 if rpc, ok := n.(*dcrd.RPC); ok { 4850 for i, txIn := range tx.TxIn { 4851 // We don't need the first input of a stakebase tx, as it's garbage 4852 // anyway. 4853 if i == 0 && *cmd.Flags == "ssgen" { 4854 continue 4855 } 4856 4857 // Did we get this outpoint from the arguments? 4858 if _, ok := inputs[txIn.PreviousOutPoint]; ok { 4859 continue 4860 } 4861 4862 // Asynchronously request the output script. 4863 txIn := txIn 4864 requestedGroup.Go(func() error { 4865 hash := txIn.PreviousOutPoint.Hash.String() 4866 index := txIn.PreviousOutPoint.Index 4867 tree := txIn.PreviousOutPoint.Tree 4868 // gettxout returns null without error if the output exists 4869 // but is spent. A double pointer is used to handle this case. 4870 var res *dcrdtypes.GetTxOutResult 4871 err := rpc.Call(gctx, "gettxout", &res, hash, index, tree, true) 4872 if err != nil { 4873 return errors.E(errors.Op("dcrd.jsonrpc.gettxout"), err) 4874 } 4875 requestedMu.Lock() 4876 requested[txIn.PreviousOutPoint] = res 4877 requestedMu.Unlock() 4878 return nil 4879 }) 4880 } 4881 } 4882 4883 // Parse list of private keys, if present. If there are any keys here 4884 // they are the keys that we may use for signing. If empty we will 4885 // use any keys known to us already. 4886 var keys map[string]*dcrutil.WIF 4887 if cmd.PrivKeys != nil { 4888 keys = make(map[string]*dcrutil.WIF) 4889 4890 for _, key := range *cmd.PrivKeys { 4891 wif, err := dcrutil.DecodeWIF(key, w.ChainParams().PrivateKeyID) 4892 if err != nil { 4893 return nil, rpcError(dcrjson.ErrRPCDeserialization, err) 4894 } 4895 4896 var addr stdaddr.Address 4897 switch wif.DSA() { 4898 case dcrec.STEcdsaSecp256k1: 4899 addr, err = stdaddr.NewAddressPubKeyEcdsaSecp256k1V0Raw( 4900 wif.PubKey(), w.ChainParams()) 4901 if err != nil { 4902 return nil, err 4903 } 4904 case dcrec.STEd25519: 4905 addr, err = stdaddr.NewAddressPubKeyEd25519V0Raw( 4906 wif.PubKey(), w.ChainParams()) 4907 if err != nil { 4908 return nil, err 4909 } 4910 case dcrec.STSchnorrSecp256k1: 4911 addr, err = stdaddr.NewAddressPubKeySchnorrSecp256k1V0Raw( 4912 wif.PubKey(), w.ChainParams()) 4913 if err != nil { 4914 return nil, err 4915 } 4916 } 4917 keys[addr.String()] = wif 4918 4919 // Add the pubkey hash variant for supported addresses as well. 4920 if pkH, ok := addr.(stdaddr.AddressPubKeyHasher); ok { 4921 keys[pkH.AddressPubKeyHash().String()] = wif 4922 } 4923 } 4924 } 4925 4926 // We have checked the rest of the args. now we can collect the async 4927 // txs. 4928 err = requestedGroup.Wait() 4929 if err != nil { 4930 return nil, err 4931 } 4932 for outPoint, result := range requested { 4933 // gettxout returns JSON null if the output is found, but is spent by 4934 // another transaction in the main chain. 4935 if result == nil { 4936 continue 4937 } 4938 script, err := hex.DecodeString(result.ScriptPubKey.Hex) 4939 if err != nil { 4940 return nil, rpcError(dcrjson.ErrRPCDecodeHexString, err) 4941 } 4942 inputs[outPoint] = script 4943 } 4944 4945 // All args collected. Now we can sign all the inputs that we can. 4946 // `complete' denotes that we successfully signed all outputs and that 4947 // all scripts will run to completion. This is returned as part of the 4948 // reply. 4949 signErrs, err := w.SignTransaction(ctx, tx, hashType, inputs, keys, scripts) 4950 if err != nil { 4951 return nil, err 4952 } 4953 4954 var b strings.Builder 4955 b.Grow(2 * tx.SerializeSize()) 4956 err = tx.Serialize(hex.NewEncoder(&b)) 4957 if err != nil { 4958 return nil, err 4959 } 4960 4961 signErrors := make([]types.SignRawTransactionError, 0, len(signErrs)) 4962 for _, e := range signErrs { 4963 input := tx.TxIn[e.InputIndex] 4964 signErrors = append(signErrors, types.SignRawTransactionError{ 4965 TxID: input.PreviousOutPoint.Hash.String(), 4966 Vout: input.PreviousOutPoint.Index, 4967 ScriptSig: hex.EncodeToString(input.SignatureScript), 4968 Sequence: input.Sequence, 4969 Error: e.Error.Error(), 4970 }) 4971 } 4972 4973 return types.SignRawTransactionResult{ 4974 Hex: b.String(), 4975 Complete: len(signErrors) == 0, 4976 Errors: signErrors, 4977 }, nil 4978 } 4979 4980 // signRawTransactions handles the signrawtransactions command. 4981 func (s *Server) signRawTransactions(ctx context.Context, icmd interface{}) (interface{}, error) { 4982 cmd := icmd.(*types.SignRawTransactionsCmd) 4983 4984 // Sign each transaction sequentially and record the results. 4985 // Error out if we meet some unexpected failure. 4986 results := make([]types.SignRawTransactionResult, len(cmd.RawTxs)) 4987 for i, etx := range cmd.RawTxs { 4988 flagAll := "ALL" 4989 srtc := &types.SignRawTransactionCmd{ 4990 RawTx: etx, 4991 Flags: &flagAll, 4992 } 4993 result, err := s.signRawTransaction(ctx, srtc) 4994 if err != nil { 4995 return nil, err 4996 } 4997 4998 tResult := result.(types.SignRawTransactionResult) 4999 results[i] = tResult 5000 } 5001 5002 // If the user wants completed transactions to be automatically send, 5003 // do that now. Otherwise, construct the slice and return it. 5004 toReturn := make([]types.SignedTransaction, len(cmd.RawTxs)) 5005 5006 if *cmd.Send { 5007 n, ok := s.walletLoader.NetworkBackend() 5008 if !ok { 5009 return nil, errNoNetwork 5010 } 5011 5012 for i, result := range results { 5013 if result.Complete { 5014 // Slow/mem hungry because of the deserializing. 5015 msgTx := wire.NewMsgTx() 5016 err := msgTx.Deserialize(hex.NewDecoder(strings.NewReader(result.Hex))) 5017 if err != nil { 5018 return nil, rpcError(dcrjson.ErrRPCDeserialization, err) 5019 } 5020 sent := false 5021 hashStr := "" 5022 err = n.PublishTransactions(ctx, msgTx) 5023 // If sendrawtransaction errors out (blockchain rule 5024 // issue, etc), continue onto the next transaction. 5025 if err == nil { 5026 sent = true 5027 hashStr = msgTx.TxHash().String() 5028 } 5029 5030 st := types.SignedTransaction{ 5031 SigningResult: result, 5032 Sent: sent, 5033 TxHash: &hashStr, 5034 } 5035 toReturn[i] = st 5036 } else { 5037 st := types.SignedTransaction{ 5038 SigningResult: result, 5039 Sent: false, 5040 TxHash: nil, 5041 } 5042 toReturn[i] = st 5043 } 5044 } 5045 } else { // Just return the results. 5046 for i, result := range results { 5047 st := types.SignedTransaction{ 5048 SigningResult: result, 5049 Sent: false, 5050 TxHash: nil, 5051 } 5052 toReturn[i] = st 5053 } 5054 } 5055 5056 return &types.SignRawTransactionsResult{Results: toReturn}, nil 5057 } 5058 5059 // scriptChangeSource is a ChangeSource which is used to 5060 // receive all correlated previous input value. 5061 type scriptChangeSource struct { 5062 version uint16 5063 script []byte 5064 } 5065 5066 func (src *scriptChangeSource) Script() ([]byte, uint16, error) { 5067 return src.script, src.version, nil 5068 } 5069 5070 func (src *scriptChangeSource) ScriptSize() int { 5071 return len(src.script) 5072 } 5073 5074 func makeScriptChangeSource(address string, params *chaincfg.Params) (*scriptChangeSource, error) { 5075 destinationAddress, err := stdaddr.DecodeAddress(address, params) 5076 if err != nil { 5077 return nil, err 5078 } 5079 version, script := destinationAddress.PaymentScript() 5080 source := &scriptChangeSource{ 5081 version: version, 5082 script: script, 5083 } 5084 return source, nil 5085 } 5086 5087 func sumOutputValues(outputs []*wire.TxOut) (totalOutput dcrutil.Amount) { 5088 for _, txOut := range outputs { 5089 totalOutput += dcrutil.Amount(txOut.Value) 5090 } 5091 return totalOutput 5092 } 5093 5094 // sweepAccount handles the sweepaccount command. 5095 func (s *Server) sweepAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 5096 cmd := icmd.(*types.SweepAccountCmd) 5097 w, ok := s.walletLoader.LoadedWallet() 5098 if !ok { 5099 return nil, errUnloadedWallet 5100 } 5101 5102 // use provided fee per Kb if specified 5103 feePerKb := w.RelayFee() 5104 if cmd.FeePerKb != nil { 5105 var err error 5106 feePerKb, err = dcrutil.NewAmount(*cmd.FeePerKb) 5107 if err != nil { 5108 return nil, rpcError(dcrjson.ErrRPCInvalidParameter, err) 5109 } 5110 } 5111 5112 // use provided required confirmations if specified 5113 requiredConfs := int32(1) 5114 if cmd.RequiredConfirmations != nil { 5115 requiredConfs = int32(*cmd.RequiredConfirmations) 5116 if requiredConfs < 0 { 5117 return nil, errNeedPositiveAmount 5118 } 5119 } 5120 5121 account, err := w.AccountNumber(ctx, cmd.SourceAccount) 5122 if err != nil { 5123 if errors.Is(err, errors.NotExist) { 5124 return nil, errAccountNotFound 5125 } 5126 return nil, err 5127 } 5128 5129 changeSource, err := makeScriptChangeSource(cmd.DestinationAddress, w.ChainParams()) 5130 if err != nil { 5131 return nil, err 5132 } 5133 tx, err := w.NewUnsignedTransaction(ctx, nil, feePerKb, account, 5134 requiredConfs, wallet.OutputSelectionAlgorithmAll, changeSource, nil) 5135 if err != nil { 5136 if errors.Is(err, errors.InsufficientBalance) { 5137 return nil, rpcError(dcrjson.ErrRPCWalletInsufficientFunds, err) 5138 } 5139 return nil, err 5140 } 5141 5142 var b strings.Builder 5143 b.Grow(2 * tx.Tx.SerializeSize()) 5144 err = tx.Tx.Serialize(hex.NewEncoder(&b)) 5145 if err != nil { 5146 return nil, err 5147 } 5148 5149 res := &types.SweepAccountResult{ 5150 UnsignedTransaction: b.String(), 5151 TotalPreviousOutputAmount: tx.TotalInput.ToCoin(), 5152 TotalOutputAmount: sumOutputValues(tx.Tx.TxOut).ToCoin(), 5153 EstimatedSignedSize: uint32(tx.EstimatedSignedSerializeSize), 5154 } 5155 5156 return res, nil 5157 } 5158 5159 // validateAddress handles the validateaddress command. 5160 func (s *Server) validateAddress(ctx context.Context, icmd interface{}) (interface{}, error) { 5161 cmd := icmd.(*types.ValidateAddressCmd) 5162 w, ok := s.walletLoader.LoadedWallet() 5163 if !ok { 5164 return nil, errUnloadedWallet 5165 } 5166 5167 result := types.ValidateAddressResult{} 5168 addr, err := decodeAddress(cmd.Address, w.ChainParams()) 5169 if err != nil { 5170 result.Script = stdscript.STNonStandard.String() 5171 // Use result zero value (IsValid=false). 5172 return result, nil 5173 } 5174 5175 result.Address = addr.String() 5176 result.IsValid = true 5177 ver, scr := addr.PaymentScript() 5178 class, _ := stdscript.ExtractAddrs(ver, scr, w.ChainParams()) 5179 result.Script = class.String() 5180 if pker, ok := addr.(stdaddr.SerializedPubKeyer); ok { 5181 result.PubKey = hex.EncodeToString(pker.SerializedPubKey()) 5182 result.PubKeyAddr = addr.String() 5183 } 5184 if class == stdscript.STScriptHash { 5185 result.IsScript = true 5186 } 5187 if _, ok := addr.(stdaddr.Hash160er); ok { 5188 result.IsCompressed = true 5189 } 5190 5191 ka, err := w.KnownAddress(ctx, addr) 5192 if err != nil { 5193 if errors.Is(err, errors.NotExist) { 5194 // No additional information available about the address. 5195 return result, nil 5196 } 5197 return nil, err 5198 } 5199 5200 // The address lookup was successful which means there is further 5201 // information about it available and it is "mine". 5202 result.IsMine = true 5203 result.Account = ka.AccountName() 5204 5205 switch ka := ka.(type) { 5206 case wallet.PubKeyHashAddress: 5207 pubKey := ka.PubKey() 5208 result.PubKey = hex.EncodeToString(pubKey) 5209 pubKeyAddr, err := stdaddr.NewAddressPubKeyEcdsaSecp256k1V0Raw(pubKey, w.ChainParams()) 5210 if err != nil { 5211 return nil, err 5212 } 5213 result.PubKeyAddr = pubKeyAddr.String() 5214 case wallet.P2SHAddress: 5215 version, script := ka.RedeemScript() 5216 result.Hex = hex.EncodeToString(script) 5217 5218 class, addrs := stdscript.ExtractAddrs(version, script, w.ChainParams()) 5219 addrStrings := make([]string, len(addrs)) 5220 for i, a := range addrs { 5221 addrStrings[i] = a.String() 5222 } 5223 result.Addresses = addrStrings 5224 result.Script = class.String() 5225 5226 // Multi-signature scripts also provide the number of required 5227 // signatures. 5228 if class == stdscript.STMultiSig { 5229 result.SigsRequired = int32(stdscript.DetermineRequiredSigs(version, script)) 5230 } 5231 } 5232 5233 if ka, ok := ka.(wallet.BIP0044Address); ok { 5234 acct, branch, child := ka.Path() 5235 if ka.AccountKind() != wallet.AccountKindImportedXpub { 5236 result.AccountN = &acct 5237 } 5238 result.Branch = &branch 5239 result.Index = &child 5240 } 5241 5242 return result, nil 5243 } 5244 5245 // validatePreDCP0005CF handles the validatepredcp0005cf command. 5246 func (s *Server) validatePreDCP0005CF(ctx context.Context, icmd interface{}) (interface{}, error) { 5247 w, ok := s.walletLoader.LoadedWallet() 5248 if !ok { 5249 return nil, errUnloadedWallet 5250 } 5251 5252 err := w.ValidatePreDCP0005CFilters(ctx) 5253 return err == nil, err 5254 } 5255 5256 // verifyMessage handles the verifymessage command by verifying the provided 5257 // compact signature for the given address and message. 5258 func (s *Server) verifyMessage(ctx context.Context, icmd interface{}) (interface{}, error) { 5259 cmd := icmd.(*types.VerifyMessageCmd) 5260 5261 var valid bool 5262 5263 // Decode address and base64 signature from the request. 5264 addr, err := stdaddr.DecodeAddress(cmd.Address, s.activeNet) 5265 if err != nil { 5266 return nil, err 5267 } 5268 sig, err := base64.StdEncoding.DecodeString(cmd.Signature) 5269 if err != nil { 5270 return nil, err 5271 } 5272 5273 // Addresses must have an associated secp256k1 private key and must be P2PKH 5274 // (P2PK and P2SH is not allowed). 5275 switch addr.(type) { 5276 case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0: 5277 default: 5278 return nil, rpcErrorf(dcrjson.ErrRPCInvalidParameter, 5279 "address must be secp256k1 pay-to-pubkey-hash") 5280 } 5281 5282 valid, err = wallet.VerifyMessage(cmd.Message, addr, sig, s.activeNet) 5283 // Mirror Bitcoin Core behavior, which treats all erorrs as an invalid 5284 // signature. 5285 return err == nil && valid, nil 5286 } 5287 5288 // version handles the version command by returning the RPC API versions of the 5289 // wallet and, optionally, the consensus RPC server as well if it is associated 5290 // with the server. The chainClient is optional, and this is simply a helper 5291 // function for the versionWithChainRPC and versionNoChainRPC handlers. 5292 func (s *Server) version(ctx context.Context, icmd interface{}) (interface{}, error) { 5293 resp := make(map[string]dcrdtypes.VersionResult) 5294 n, _ := s.walletLoader.NetworkBackend() 5295 if rpc, ok := n.(*dcrd.RPC); ok { 5296 err := rpc.Call(ctx, "version", &resp) 5297 if err != nil { 5298 return nil, err 5299 } 5300 } 5301 5302 resp["dcrwalletjsonrpcapi"] = dcrdtypes.VersionResult{ 5303 VersionString: jsonrpcSemverString, 5304 Major: jsonrpcSemverMajor, 5305 Minor: jsonrpcSemverMinor, 5306 Patch: jsonrpcSemverPatch, 5307 } 5308 return resp, nil 5309 } 5310 5311 // walletInfo gets the current information about the wallet. If the daemon 5312 // is connected and fails to ping, the function will still return that the 5313 // daemon is disconnected. 5314 func (s *Server) walletInfo(ctx context.Context, icmd interface{}) (interface{}, error) { 5315 w, ok := s.walletLoader.LoadedWallet() 5316 if !ok { 5317 return nil, errUnloadedWallet 5318 } 5319 5320 var connected, spvMode bool 5321 switch n, _ := w.NetworkBackend(); rpc := n.(type) { 5322 case *spv.Syncer: 5323 spvMode = true 5324 connected = len(rpc.GetRemotePeers()) > 0 5325 case *dcrd.RPC: 5326 err := rpc.Call(ctx, "ping", nil) 5327 if ctx.Err() != nil { 5328 return nil, ctx.Err() 5329 } 5330 if err != nil { 5331 log.Warnf("Ping failed on connected daemon client: %v", err) 5332 } else { 5333 connected = true 5334 } 5335 case nil: 5336 log.Warnf("walletInfo - no network backend") 5337 default: 5338 log.Errorf("walletInfo - invalid network backend (%T).", n) 5339 return nil, &dcrjson.RPCError{ 5340 Code: dcrjson.ErrRPCMisc, 5341 Message: "invalid network backend", 5342 } 5343 } 5344 5345 coinType, err := w.CoinType(ctx) 5346 if errors.Is(err, errors.WatchingOnly) { 5347 // This is a watching-only wallet, which does not store the active coin 5348 // type. Return CoinTypes default value (0), which will be omitted from 5349 // the JSON response, and log a debug message. 5350 log.Debug("Watching only wallets do not store the coin type keys.") 5351 } else if err != nil { 5352 log.Errorf("Failed to retrieve the active coin type: %v", err) 5353 coinType = 0 5354 } 5355 5356 unlocked := !(w.Locked()) 5357 fi := w.RelayFee() 5358 voteBits := w.VoteBits() 5359 var voteVersion uint32 5360 _ = binary.Read(bytes.NewBuffer(voteBits.ExtendedBits[0:4]), binary.LittleEndian, &voteVersion) 5361 voting := w.VotingEnabled() 5362 5363 return &types.WalletInfoResult{ 5364 DaemonConnected: connected, 5365 SPV: spvMode, 5366 Unlocked: unlocked, 5367 CoinType: coinType, 5368 TxFee: fi.ToCoin(), 5369 VoteBits: voteBits.Bits, 5370 VoteBitsExtended: hex.EncodeToString(voteBits.ExtendedBits), 5371 VoteVersion: voteVersion, 5372 Voting: voting, 5373 ManualTickets: w.ManualTickets(), 5374 }, nil 5375 } 5376 5377 // walletIsLocked handles the walletislocked extension request by 5378 // returning the current lock state (false for unlocked, true for locked) 5379 // of an account. 5380 func (s *Server) walletIsLocked(ctx context.Context, icmd interface{}) (interface{}, error) { 5381 w, ok := s.walletLoader.LoadedWallet() 5382 if !ok { 5383 return nil, errUnloadedWallet 5384 } 5385 5386 return w.Locked(), nil 5387 } 5388 5389 // walletLock handles a walletlock request by locking the all account 5390 // wallets, returning an error if any wallet is not encrypted (for example, 5391 // a watching-only wallet). 5392 func (s *Server) walletLock(ctx context.Context, icmd interface{}) (interface{}, error) { 5393 w, ok := s.walletLoader.LoadedWallet() 5394 if !ok { 5395 return nil, errUnloadedWallet 5396 } 5397 5398 w.Lock() 5399 return nil, nil 5400 } 5401 5402 // walletPassphrase responds to the walletpassphrase request by unlocking the 5403 // wallet. The decryption key is saved in the wallet until timeout seconds 5404 // expires, after which the wallet is locked. A timeout of 0 leaves the wallet 5405 // unlocked indefinitely. 5406 func (s *Server) walletPassphrase(ctx context.Context, icmd interface{}) (interface{}, error) { 5407 cmd := icmd.(*types.WalletPassphraseCmd) 5408 w, ok := s.walletLoader.LoadedWallet() 5409 if !ok { 5410 return nil, errUnloadedWallet 5411 } 5412 5413 timeout := time.Second * time.Duration(cmd.Timeout) 5414 var unlockAfter <-chan time.Time 5415 if timeout != 0 { 5416 unlockAfter = time.After(timeout) 5417 } 5418 err := w.Unlock(ctx, []byte(cmd.Passphrase), unlockAfter) 5419 return nil, err 5420 } 5421 5422 // walletPassphraseChange responds to the walletpassphrasechange request 5423 // by unlocking all accounts with the provided old passphrase, and 5424 // re-encrypting each private key with an AES key derived from the new 5425 // passphrase. 5426 // 5427 // If the old passphrase is correct and the passphrase is changed, all 5428 // wallets will be immediately locked. 5429 func (s *Server) walletPassphraseChange(ctx context.Context, icmd interface{}) (interface{}, error) { 5430 cmd := icmd.(*types.WalletPassphraseChangeCmd) 5431 w, ok := s.walletLoader.LoadedWallet() 5432 if !ok { 5433 return nil, errUnloadedWallet 5434 } 5435 5436 err := w.ChangePrivatePassphrase(ctx, []byte(cmd.OldPassphrase), 5437 []byte(cmd.NewPassphrase)) 5438 if err != nil { 5439 if errors.Is(err, errors.Passphrase) { 5440 return nil, rpcErrorf(dcrjson.ErrRPCWalletPassphraseIncorrect, "incorrect passphrase") 5441 } 5442 return nil, err 5443 } 5444 return nil, nil 5445 } 5446 5447 func (s *Server) mixOutput(ctx context.Context, icmd interface{}) (interface{}, error) { 5448 cmd := icmd.(*types.MixOutputCmd) 5449 if s.cfg.CSPPServer == "" { 5450 return nil, errors.E("CoinShuffle++ server is not configured") 5451 } 5452 w, ok := s.walletLoader.LoadedWallet() 5453 if !ok { 5454 return nil, errUnloadedWallet 5455 } 5456 5457 outpoint, err := parseOutpoint(cmd.Outpoint) 5458 if err != nil { 5459 return nil, err 5460 } 5461 5462 mixAccount, err := w.AccountNumber(ctx, s.cfg.MixAccount) 5463 if err != nil { 5464 if errors.Is(err, errors.NotExist) { 5465 return nil, errAccountNotFound 5466 } 5467 return nil, err 5468 } 5469 changeAccount, err := w.AccountNumber(ctx, s.cfg.MixChangeAccount) 5470 if err != nil { 5471 if errors.Is(err, errors.NotExist) { 5472 return nil, errAccountNotFound 5473 } 5474 return nil, err 5475 } 5476 5477 dial := s.cfg.DialCSPPServer 5478 server := s.cfg.CSPPServer 5479 mixBranch := s.cfg.MixBranch 5480 5481 err = w.MixOutput(ctx, dial, server, outpoint, changeAccount, mixAccount, mixBranch) 5482 return nil, err 5483 } 5484 5485 func (s *Server) mixAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 5486 if s.cfg.CSPPServer == "" { 5487 return nil, errors.E("CoinShuffle++ server is not configured") 5488 } 5489 w, ok := s.walletLoader.LoadedWallet() 5490 if !ok { 5491 return nil, errUnloadedWallet 5492 } 5493 5494 mixAccount, err := w.AccountNumber(ctx, s.cfg.MixAccount) 5495 if err != nil { 5496 if errors.Is(err, errors.NotExist) { 5497 return nil, errAccountNotFound 5498 } 5499 return nil, err 5500 } 5501 changeAccount, err := w.AccountNumber(ctx, s.cfg.MixChangeAccount) 5502 if err != nil { 5503 if errors.Is(err, errors.NotExist) { 5504 return nil, errAccountNotFound 5505 } 5506 return nil, err 5507 } 5508 5509 dial := s.cfg.DialCSPPServer 5510 server := s.cfg.CSPPServer 5511 mixBranch := s.cfg.MixBranch 5512 5513 err = w.MixAccount(ctx, dial, server, changeAccount, mixAccount, mixBranch) 5514 return nil, err 5515 } 5516 5517 func parseOutpoint(s string) (*wire.OutPoint, error) { 5518 const op errors.Op = "parseOutpoint" 5519 if len(s) < 66 { 5520 return nil, errors.E(op, "bad len") 5521 } 5522 if s[64] != ':' { // sep follows 32 bytes of hex 5523 return nil, errors.E(op, "bad separator") 5524 } 5525 hash, err := chainhash.NewHashFromStr(s[:64]) 5526 if err != nil { 5527 return nil, errors.E(op, err) 5528 } 5529 index, err := strconv.ParseUint(s[65:], 10, 32) 5530 if err != nil { 5531 return nil, errors.E(op, err) 5532 } 5533 return &wire.OutPoint{Hash: *hash, Index: uint32(index)}, nil 5534 } 5535 5536 // walletPubPassphraseChange responds to the walletpubpassphrasechange request 5537 // by modifying the public passphrase of the wallet. 5538 func (s *Server) walletPubPassphraseChange(ctx context.Context, icmd interface{}) (interface{}, error) { 5539 cmd := icmd.(*types.WalletPubPassphraseChangeCmd) 5540 w, ok := s.walletLoader.LoadedWallet() 5541 if !ok { 5542 return nil, errUnloadedWallet 5543 } 5544 5545 err := w.ChangePublicPassphrase(ctx, []byte(cmd.OldPassphrase), 5546 []byte(cmd.NewPassphrase)) 5547 if errors.Is(errors.Passphrase, err) { 5548 return nil, rpcErrorf(dcrjson.ErrRPCWalletPassphraseIncorrect, "incorrect passphrase") 5549 } 5550 return nil, err 5551 } 5552 5553 func (s *Server) setAccountPassphrase(ctx context.Context, icmd interface{}) (interface{}, error) { 5554 cmd := icmd.(*types.SetAccountPassphraseCmd) 5555 w, ok := s.walletLoader.LoadedWallet() 5556 if !ok { 5557 return nil, errUnloadedWallet 5558 } 5559 5560 account, err := w.AccountNumber(ctx, cmd.Account) 5561 if err != nil { 5562 if errors.Is(err, errors.NotExist) { 5563 return nil, errAccountNotFound 5564 } 5565 return nil, err 5566 } 5567 err = w.SetAccountPassphrase(ctx, account, []byte(cmd.Passphrase)) 5568 return nil, err 5569 } 5570 5571 func (s *Server) accountUnlocked(ctx context.Context, icmd interface{}) (interface{}, error) { 5572 cmd := icmd.(*types.AccountUnlockedCmd) 5573 w, ok := s.walletLoader.LoadedWallet() 5574 if !ok { 5575 return nil, errUnloadedWallet 5576 } 5577 5578 account, err := w.AccountNumber(ctx, cmd.Account) 5579 if err != nil { 5580 if errors.Is(err, errors.NotExist) { 5581 return nil, errAccountNotFound 5582 } 5583 return nil, err 5584 } 5585 5586 encrypted, err := w.AccountHasPassphrase(ctx, account) 5587 if err != nil { 5588 return nil, err 5589 } 5590 if !encrypted { 5591 return &types.AccountUnlockedResult{}, nil 5592 } 5593 5594 unlocked, err := w.AccountUnlocked(ctx, account) 5595 if err != nil { 5596 return nil, err 5597 } 5598 5599 return &types.AccountUnlockedResult{ 5600 Encrypted: true, 5601 Unlocked: &unlocked, 5602 }, nil 5603 } 5604 5605 func (s *Server) unlockAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 5606 cmd := icmd.(*types.UnlockAccountCmd) 5607 w, ok := s.walletLoader.LoadedWallet() 5608 if !ok { 5609 return nil, errUnloadedWallet 5610 } 5611 5612 account, err := w.AccountNumber(ctx, cmd.Account) 5613 if err != nil { 5614 if errors.Is(err, errors.NotExist) { 5615 return nil, errAccountNotFound 5616 } 5617 return nil, err 5618 } 5619 err = w.UnlockAccount(ctx, account, []byte(cmd.Passphrase)) 5620 return nil, err 5621 } 5622 5623 func (s *Server) lockAccount(ctx context.Context, icmd interface{}) (interface{}, error) { 5624 cmd := icmd.(*types.LockAccountCmd) 5625 w, ok := s.walletLoader.LoadedWallet() 5626 if !ok { 5627 return nil, errUnloadedWallet 5628 } 5629 5630 if cmd.Account == "*" { 5631 a, err := w.Accounts(ctx) 5632 if err != nil { 5633 return nil, err 5634 } 5635 for _, acct := range a.Accounts { 5636 if acct.AccountEncrypted && acct.AccountUnlocked { 5637 err = w.LockAccount(ctx, acct.AccountNumber) 5638 if err != nil { 5639 return nil, err 5640 } 5641 } 5642 } 5643 return nil, nil 5644 } 5645 5646 account, err := w.AccountNumber(ctx, cmd.Account) 5647 if err != nil { 5648 if errors.Is(err, errors.NotExist) { 5649 return nil, errAccountNotFound 5650 } 5651 return nil, err 5652 } 5653 err = w.LockAccount(ctx, account) 5654 return nil, err 5655 } 5656 5657 // decodeHexStr decodes the hex encoding of a string, possibly prepending a 5658 // leading '0' character if there is an odd number of bytes in the hex string. 5659 // This is to prevent an error for an invalid hex string when using an odd 5660 // number of bytes when calling hex.Decode. 5661 func decodeHexStr(hexStr string) ([]byte, error) { 5662 if len(hexStr)%2 != 0 { 5663 hexStr = "0" + hexStr 5664 } 5665 decoded, err := hex.DecodeString(hexStr) 5666 if err != nil { 5667 return nil, rpcErrorf(dcrjson.ErrRPCDecodeHexString, "hex string decode failed: %v", err) 5668 } 5669 return decoded, nil 5670 } 5671 5672 func (s *Server) getcoinjoinsbyacct(ctx context.Context, icmd interface{}) (interface{}, error) { 5673 _ = icmd.(*types.GetCoinjoinsByAcctCmd) 5674 w, ok := s.walletLoader.LoadedWallet() 5675 if !ok { 5676 return nil, errUnloadedWallet 5677 } 5678 5679 acctCoinjoinsSum, err := w.GetCoinjoinTxsSumbByAcct(ctx) 5680 if err != nil { 5681 if errors.Is(err, errors.Passphrase) { 5682 return nil, rpcErrorf(dcrjson.ErrRPCWalletPassphraseIncorrect, "incorrect passphrase") 5683 } 5684 return nil, err 5685 } 5686 5687 acctNameCoinjoinSum := map[string]int{} 5688 for acctIdx, coinjoinSum := range acctCoinjoinsSum { 5689 accountName, err := w.AccountName(ctx, acctIdx) 5690 if err != nil { 5691 // Expect account lookup to succeed 5692 if errors.Is(err, errors.NotExist) { 5693 return nil, rpcError(dcrjson.ErrRPCInternal.Code, err) 5694 } 5695 return nil, err 5696 } 5697 acctNameCoinjoinSum[accountName] = coinjoinSum 5698 } 5699 5700 return acctNameCoinjoinSum, nil 5701 }