decred.org/dcrwallet/v3@v3.1.0/internal/rpc/rpcserver/server.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Copyright (c) 2016-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 rpcserver implements the RPC API and is used by the main package to 7 // start gRPC services. 8 // 9 // Full documentation of the API implemented by this package is maintained in a 10 // language-agnostic document: 11 // 12 // https://github.com/decred/dcrwallet/blob/master/rpc/documentation/api.md 13 // 14 // Any API changes must be performed according to the steps listed here: 15 // 16 // https://github.com/decred/dcrwallet/blob/master/rpc/documentation/serverchanges.md 17 package rpcserver 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/rand" 23 "encoding/hex" 24 "fmt" 25 "net" 26 "sort" 27 "sync/atomic" 28 "time" 29 30 "google.golang.org/grpc" 31 "google.golang.org/grpc/codes" 32 "google.golang.org/grpc/status" 33 34 "decred.org/dcrwallet/v3/chain" 35 "decred.org/dcrwallet/v3/errors" 36 "decred.org/dcrwallet/v3/internal/cfgutil" 37 "decred.org/dcrwallet/v3/internal/loader" 38 "decred.org/dcrwallet/v3/internal/netparams" 39 "decred.org/dcrwallet/v3/internal/vsp" 40 "decred.org/dcrwallet/v3/p2p" 41 "decred.org/dcrwallet/v3/rpc/client/dcrd" 42 pb "decred.org/dcrwallet/v3/rpc/walletrpc" 43 "decred.org/dcrwallet/v3/spv" 44 "decred.org/dcrwallet/v3/ticketbuyer" 45 "decred.org/dcrwallet/v3/wallet" 46 "decred.org/dcrwallet/v3/wallet/txauthor" 47 "decred.org/dcrwallet/v3/wallet/txrules" 48 "decred.org/dcrwallet/v3/wallet/udb" 49 "decred.org/dcrwallet/v3/walletseed" 50 "github.com/decred/dcrd/addrmgr/v2" 51 "github.com/decred/dcrd/blockchain/stake/v5" 52 "github.com/decred/dcrd/chaincfg/chainhash" 53 "github.com/decred/dcrd/chaincfg/v3" 54 "github.com/decred/dcrd/dcrec/secp256k1/v4" 55 "github.com/decred/dcrd/dcrutil/v4" 56 "github.com/decred/dcrd/gcs/v4" 57 "github.com/decred/dcrd/hdkeychain/v3" 58 "github.com/decred/dcrd/txscript/v4" 59 "github.com/decred/dcrd/txscript/v4/stdaddr" 60 "github.com/decred/dcrd/txscript/v4/stdscript" 61 "github.com/decred/dcrd/wire" 62 ) 63 64 // Public API version constants 65 const ( 66 semverString = "7.17.0" 67 semverMajor = 7 68 semverMinor = 17 69 semverPatch = 0 70 ) 71 72 // The assumed output script version is defined to assist with refactoring to 73 // use actual script versions. 74 const scriptVersionAssumed = 0 75 76 // translateError creates a new gRPC error with an appropriate error code for 77 // recognized errors. 78 // 79 // This function is by no means complete and should be expanded based on other 80 // known errors. Any RPC handler not returning a gRPC error (with status.Errorf) 81 // should return this result instead. 82 func translateError(err error) error { 83 code := errorCode(err) 84 return status.Errorf(code, "%s", err.Error()) 85 } 86 87 func errorCode(err error) codes.Code { 88 var kind errors.Kind 89 if errors.As(err, &kind) { 90 switch kind { 91 case errors.Bug: 92 case errors.Invalid: 93 return codes.InvalidArgument 94 case errors.Permission: 95 return codes.PermissionDenied 96 case errors.IO: 97 case errors.Exist: 98 return codes.AlreadyExists 99 case errors.NotExist: 100 return codes.NotFound 101 case errors.Encoding: 102 case errors.Crypto: 103 return codes.DataLoss 104 case errors.Locked: 105 return codes.FailedPrecondition 106 case errors.Passphrase: 107 return codes.InvalidArgument 108 case errors.Seed: 109 return codes.InvalidArgument 110 case errors.WatchingOnly: 111 return codes.Unimplemented 112 case errors.InsufficientBalance: 113 return codes.ResourceExhausted 114 case errors.ScriptFailure: 115 case errors.Policy: 116 case errors.DoubleSpend: 117 case errors.Protocol: 118 case errors.NoPeers: 119 return codes.Unavailable 120 } 121 } 122 if errors.Is(err, hdkeychain.ErrInvalidSeedLen) { 123 return codes.InvalidArgument 124 } 125 if errors.Is(errors.Cause(err), context.Canceled) { 126 return codes.Canceled 127 } 128 if code := status.Code(errors.Cause(err)); code != codes.OK && code != codes.Unknown { 129 return code 130 } 131 return codes.Unknown 132 } 133 134 // decodeAddress decodes an address and verifies it is intended for the active 135 // network. This should be used preferred to direct usage of 136 // dcrutil.DecodeAddress, which does not perform the network check. 137 func decodeAddress(a string, params *chaincfg.Params) (stdaddr.Address, error) { 138 addr, err := stdaddr.DecodeAddress(a, params) 139 if err != nil { 140 return nil, status.Errorf(codes.InvalidArgument, "invalid address %v: %v", a, err) 141 } 142 return addr, nil 143 } 144 145 func decodeStakeAddress(s string, params *chaincfg.Params) (stdaddr.StakeAddress, error) { 146 a, err := decodeAddress(s, params) 147 if err != nil { 148 return nil, err 149 } 150 if sa, ok := a.(stdaddr.StakeAddress); ok { 151 return sa, nil 152 } 153 return nil, status.Errorf(codes.InvalidArgument, "invalid stake address %q", s) 154 } 155 156 func decodeHashes(in [][]byte) ([]*chainhash.Hash, error) { 157 out := make([]*chainhash.Hash, len(in)) 158 var err error 159 for i, h := range in { 160 out[i], err = chainhash.NewHash(h) 161 if err != nil { 162 return nil, status.Errorf(codes.InvalidArgument, "hash (hex %x): %v", h, err) 163 } 164 } 165 return out, nil 166 } 167 168 // versionServer provides RPC clients with the ability to query the RPC server 169 // version. 170 type versionServer struct { 171 pb.UnimplementedVersionServiceServer 172 } 173 174 // walletServer provides wallet services for RPC clients. 175 type walletServer struct { 176 ready uint32 // atomic 177 wallet *wallet.Wallet 178 dialCSPPServer func(ctx context.Context, network, addr string) (net.Conn, error) 179 pb.UnimplementedWalletServiceServer 180 } 181 182 // loaderServer provides RPC clients with the ability to load and close wallets, 183 // as well as establishing a RPC connection to a dcrd consensus server. 184 type loaderServer struct { 185 ready uint32 // atomic 186 loader *loader.Loader 187 activeNet *netparams.Params 188 pb.UnimplementedWalletLoaderServiceServer 189 } 190 191 // seedServer provides RPC clients with the ability to generate secure random 192 // seeds encoded in both binary and human-readable formats, and decode any 193 // human-readable input back to binary. 194 type seedServer struct { 195 pb.UnimplementedSeedServiceServer 196 } 197 198 // accountMixerServer provides RPC clients with the ability to start/stop the 199 // account mixing privacy service. 200 type accountMixerServer struct { 201 ready uint32 // atomic 202 loader *loader.Loader 203 pb.UnimplementedAccountMixerServiceServer 204 } 205 206 // ticketbuyerServer provides RPC clients with the ability to start/stop the 207 // automatic ticket buyer service. 208 type ticketbuyerV2Server struct { 209 ready uint32 // atomic 210 loader *loader.Loader 211 pb.UnimplementedTicketBuyerV2ServiceServer 212 } 213 214 type agendaServer struct { 215 ready uint32 // atomic 216 activeNet *chaincfg.Params 217 pb.UnimplementedAgendaServiceServer 218 } 219 220 type votingServer struct { 221 ready uint32 // atomic 222 wallet *wallet.Wallet 223 pb.UnimplementedVotingServiceServer 224 } 225 226 // messageVerificationServer provides RPC clients with the ability to verify 227 // that a message was signed using the private key of a particular address. 228 type messageVerificationServer struct { 229 chainParams *chaincfg.Params 230 pb.UnimplementedMessageVerificationServiceServer 231 } 232 233 type decodeMessageServer struct { 234 chainParams *chaincfg.Params 235 pb.UnimplementedDecodeMessageServiceServer 236 } 237 238 // networkServer provices RPC clients with the ability to perform network 239 // related calls that are not necessarily used or backed by the wallet itself. 240 type networkServer struct { 241 ready uint32 // atomic 242 wallet *wallet.Wallet 243 pb.UnimplementedNetworkServiceServer 244 } 245 246 // Singleton implementations of each service. Not all services are immediately 247 // usable. 248 var ( 249 versionService versionServer 250 walletService walletServer 251 loaderService loaderServer 252 seedService seedServer 253 accountMixerService accountMixerServer 254 ticketBuyerV2Service ticketbuyerV2Server 255 agendaService agendaServer 256 votingService votingServer 257 messageVerificationService messageVerificationServer 258 decodeMessageService decodeMessageServer 259 networkService networkServer 260 ) 261 262 // RegisterServices registers implementations of each gRPC service and registers 263 // it with the server. Not all service are ready to be used after registration. 264 func RegisterServices(server *grpc.Server) { 265 pb.RegisterVersionServiceServer(server, &versionService) 266 pb.RegisterWalletServiceServer(server, &walletService) 267 pb.RegisterWalletLoaderServiceServer(server, &loaderService) 268 pb.RegisterSeedServiceServer(server, &seedService) 269 pb.RegisterAccountMixerServiceServer(server, &accountMixerService) 270 pb.RegisterTicketBuyerV2ServiceServer(server, &ticketBuyerV2Service) 271 pb.RegisterAgendaServiceServer(server, &agendaService) 272 pb.RegisterVotingServiceServer(server, &votingService) 273 pb.RegisterMessageVerificationServiceServer(server, &messageVerificationService) 274 pb.RegisterDecodeMessageServiceServer(server, &decodeMessageService) 275 pb.RegisterNetworkServiceServer(server, &networkService) 276 } 277 278 var serviceMap = map[string]interface{}{ 279 "walletrpc.VersionService": &versionService, 280 "walletrpc.WalletService": &walletService, 281 "walletrpc.WalletLoaderService": &loaderService, 282 "walletrpc.SeedService": &seedService, 283 "walletrpc.AccountMixerService": &accountMixerService, 284 "walletrpc.TicketBuyerV2Service": &ticketBuyerV2Service, 285 "walletrpc.AgendaService": &agendaService, 286 "walletrpc.VotingService": &votingService, 287 "walletrpc.MessageVerificationService": &messageVerificationService, 288 "walletrpc.DecodeMessageService": &decodeMessageService, 289 "walletrpc.NetworkService": &networkService, 290 } 291 292 // ServiceReady returns nil when the service is ready and a gRPC error when not. 293 func ServiceReady(service string) error { 294 s, ok := serviceMap[service] 295 if !ok { 296 return status.Errorf(codes.Unimplemented, "service %s not found", service) 297 } 298 type readyChecker interface { 299 checkReady() bool 300 } 301 ready := true 302 r, ok := s.(readyChecker) 303 if ok { 304 ready = r.checkReady() 305 } 306 if !ready { 307 return status.Errorf(codes.FailedPrecondition, "service %v is not ready", service) 308 } 309 return nil 310 } 311 312 func (*versionServer) Version(ctx context.Context, req *pb.VersionRequest) (*pb.VersionResponse, error) { 313 return &pb.VersionResponse{ 314 VersionString: semverString, 315 Major: semverMajor, 316 Minor: semverMinor, 317 Patch: semverPatch, 318 }, nil 319 } 320 321 type dialFunc func(ctx context.Context, network, addr string) (net.Conn, error) 322 323 // StartWalletService starts the WalletService. 324 func StartWalletService(server *grpc.Server, wallet *wallet.Wallet, dialCSPP dialFunc) { 325 if atomic.SwapUint32(&walletService.ready, 1) != 0 { 326 panic("service already started") 327 } 328 walletService.wallet = wallet 329 walletService.dialCSPPServer = dialCSPP 330 } 331 332 func (s *walletServer) checkReady() bool { 333 return atomic.LoadUint32(&s.ready) != 0 334 } 335 336 // requireNetworkBackend checks whether the wallet has been associated with the 337 // consensus server RPC client, returning a gRPC error when it is not. 338 func (s *walletServer) requireNetworkBackend() (wallet.NetworkBackend, error) { 339 n, err := s.wallet.NetworkBackend() 340 if err != nil { 341 return nil, status.Errorf(codes.FailedPrecondition, 342 "wallet is not associated with a consensus server RPC client") 343 } 344 return n, nil 345 } 346 347 func (s *walletServer) Ping(ctx context.Context, req *pb.PingRequest) (*pb.PingResponse, error) { 348 return &pb.PingResponse{}, nil 349 } 350 351 func (s *walletServer) Network(ctx context.Context, req *pb.NetworkRequest) ( 352 *pb.NetworkResponse, error) { 353 354 return &pb.NetworkResponse{ActiveNetwork: uint32(s.wallet.ChainParams().Net)}, nil 355 } 356 357 func (s *walletServer) CoinType(ctx context.Context, req *pb.CoinTypeRequest) (*pb.CoinTypeResponse, error) { 358 coinType, err := s.wallet.CoinType(ctx) 359 if err != nil { 360 return nil, translateError(err) 361 } 362 return &pb.CoinTypeResponse{CoinType: coinType}, nil 363 } 364 365 func (s *walletServer) AccountNumber(ctx context.Context, req *pb.AccountNumberRequest) ( 366 *pb.AccountNumberResponse, error) { 367 368 accountNum, err := s.wallet.AccountNumber(ctx, req.AccountName) 369 if err != nil { 370 return nil, translateError(err) 371 } 372 373 return &pb.AccountNumberResponse{AccountNumber: accountNum}, nil 374 } 375 376 func (s *walletServer) Accounts(ctx context.Context, req *pb.AccountsRequest) (*pb.AccountsResponse, error) { 377 resp, err := s.wallet.Accounts(ctx) 378 if err != nil { 379 return nil, translateError(err) 380 } 381 accounts := make([]*pb.AccountsResponse_Account, len(resp.Accounts)) 382 for i := range resp.Accounts { 383 a := &resp.Accounts[i] 384 accounts[i] = &pb.AccountsResponse_Account{ 385 AccountNumber: a.AccountNumber, 386 AccountName: a.AccountName, 387 TotalBalance: int64(a.TotalBalance), 388 // indexes are zero based, add 1 for the count 389 ExternalKeyCount: a.LastReturnedExternalIndex + 1, 390 InternalKeyCount: a.LastReturnedInternalIndex + 1, 391 ImportedKeyCount: a.ImportedKeyCount, 392 AccountEncrypted: a.AccountEncrypted, 393 AccountUnlocked: a.AccountUnlocked, 394 } 395 } 396 return &pb.AccountsResponse{ 397 Accounts: accounts, 398 CurrentBlockHash: resp.CurrentBlockHash[:], 399 CurrentBlockHeight: resp.CurrentBlockHeight, 400 }, nil 401 } 402 403 func (s *walletServer) RenameAccount(ctx context.Context, req *pb.RenameAccountRequest) ( 404 *pb.RenameAccountResponse, error) { 405 406 err := s.wallet.RenameAccount(ctx, req.AccountNumber, req.NewName) 407 if err != nil { 408 return nil, translateError(err) 409 } 410 411 return &pb.RenameAccountResponse{}, nil 412 } 413 414 func (s *walletServer) PublishUnminedTransactions(ctx context.Context, req *pb.PublishUnminedTransactionsRequest) ( 415 *pb.PublishUnminedTransactionsResponse, error) { 416 n, err := s.requireNetworkBackend() 417 if err != nil { 418 return nil, err 419 } 420 err = s.wallet.PublishUnminedTransactions(ctx, n) 421 if err != nil { 422 return nil, translateError(err) 423 } 424 425 return &pb.PublishUnminedTransactionsResponse{}, nil 426 } 427 428 func (s *walletServer) Rescan(req *pb.RescanRequest, svr pb.WalletService_RescanServer) error { 429 n, err := s.requireNetworkBackend() 430 if err != nil { 431 return err 432 } 433 434 var blockID *wallet.BlockIdentifier 435 switch { 436 case req.BeginHash != nil && req.BeginHeight != 0: 437 return status.Errorf(codes.InvalidArgument, "begin hash and height must not be set together") 438 case req.BeginHeight < 0: 439 return status.Errorf(codes.InvalidArgument, "begin height must be non-negative") 440 case req.BeginHash != nil: 441 blockHash, err := chainhash.NewHash(req.BeginHash) 442 if err != nil { 443 return status.Errorf(codes.InvalidArgument, "block hash has invalid length") 444 } 445 blockID = wallet.NewBlockIdentifierFromHash(blockHash) 446 default: 447 blockID = wallet.NewBlockIdentifierFromHeight(req.BeginHeight) 448 } 449 450 b, err := s.wallet.BlockInfo(svr.Context(), blockID) 451 if err != nil { 452 return translateError(err) 453 } 454 455 progress := make(chan wallet.RescanProgress, 1) 456 go s.wallet.RescanProgressFromHeight(svr.Context(), n, b.Height, progress) 457 458 for p := range progress { 459 if p.Err != nil { 460 return translateError(p.Err) 461 } 462 resp := &pb.RescanResponse{RescannedThrough: p.ScannedThrough} 463 err := svr.Send(resp) 464 if err != nil { 465 return translateError(err) 466 } 467 } 468 // finished or cancelled rescan without error 469 select { 470 case <-svr.Context().Done(): 471 return status.Errorf(codes.Canceled, "rescan canceled") 472 default: 473 return nil 474 } 475 } 476 477 func zero(b []byte) { 478 for i := range b { 479 b[i] = 0 480 } 481 } 482 483 func (s *walletServer) NextAccount(ctx context.Context, req *pb.NextAccountRequest) ( 484 *pb.NextAccountResponse, error) { 485 486 defer zero(req.Passphrase) 487 488 if req.AccountName == "" { 489 return nil, status.Errorf(codes.InvalidArgument, "account name may not be empty") 490 } 491 492 if len(req.Passphrase) > 0 { 493 lock := make(chan time.Time, 1) 494 defer func() { 495 lock <- time.Time{} // send matters, not the value 496 }() 497 err := s.wallet.Unlock(ctx, req.Passphrase, lock) 498 if err != nil { 499 return nil, translateError(err) 500 } 501 } 502 503 account, err := s.wallet.NextAccount(ctx, req.AccountName) 504 if err != nil { 505 return nil, translateError(err) 506 } 507 508 return &pb.NextAccountResponse{AccountNumber: account}, nil 509 } 510 511 func (s *walletServer) NextAddress(ctx context.Context, req *pb.NextAddressRequest) ( 512 *pb.NextAddressResponse, error) { 513 514 var callOpts []wallet.NextAddressCallOption 515 switch req.GapPolicy { 516 case pb.NextAddressRequest_GAP_POLICY_UNSPECIFIED: 517 case pb.NextAddressRequest_GAP_POLICY_ERROR: 518 callOpts = append(callOpts, wallet.WithGapPolicyError()) 519 case pb.NextAddressRequest_GAP_POLICY_IGNORE: 520 callOpts = append(callOpts, wallet.WithGapPolicyIgnore()) 521 case pb.NextAddressRequest_GAP_POLICY_WRAP: 522 callOpts = append(callOpts, wallet.WithGapPolicyWrap()) 523 default: 524 return nil, status.Errorf(codes.InvalidArgument, "gap_policy=%v", req.GapPolicy) 525 } 526 527 var ( 528 addr stdaddr.Address 529 err error 530 ) 531 switch req.Kind { 532 case pb.NextAddressRequest_BIP0044_EXTERNAL: 533 addr, err = s.wallet.NewExternalAddress(ctx, req.Account, callOpts...) 534 if err != nil { 535 return nil, translateError(err) 536 } 537 case pb.NextAddressRequest_BIP0044_INTERNAL: 538 addr, err = s.wallet.NewInternalAddress(ctx, req.Account, callOpts...) 539 if err != nil { 540 return nil, translateError(err) 541 } 542 default: 543 return nil, status.Errorf(codes.InvalidArgument, "kind=%v", req.Kind) 544 } 545 if err != nil { 546 return nil, translateError(err) 547 } 548 549 var pubKeyAddrString string 550 switch addr := addr.(type) { 551 case wallet.PubKeyHashAddress: 552 pubKey := addr.PubKey() 553 pubKeyAddr, err := stdaddr.NewAddressPubKeyEcdsaSecp256k1V0Raw( 554 pubKey, s.wallet.ChainParams()) 555 if err != nil { 556 return nil, translateError(err) 557 } 558 pubKeyAddrString = pubKeyAddr.String() 559 } 560 561 return &pb.NextAddressResponse{ 562 Address: addr.String(), 563 PublicKey: pubKeyAddrString, 564 }, nil 565 } 566 567 func (s *walletServer) Address(ctx context.Context, req *pb.AddressRequest) ( 568 *pb.AddressResponse, error) { 569 var branch uint32 570 switch req.Kind { 571 case pb.AddressRequest_BIP0044_EXTERNAL: 572 case pb.AddressRequest_BIP0044_INTERNAL: 573 branch = 1 574 default: 575 return nil, status.Errorf(codes.InvalidArgument, "kind=%v", req.Kind) 576 } 577 addr, err := s.wallet.AddressAtIdx(ctx, req.Account, branch, req.Index) 578 if err != nil { 579 return nil, translateError(err) 580 } 581 var pubKeyAddrString string 582 switch addr := addr.(type) { 583 case wallet.PubKeyHashAddress: 584 pubKey := addr.PubKey() 585 pubKeyAddr, err := stdaddr.NewAddressPubKeyEcdsaSecp256k1V0Raw( 586 pubKey, s.wallet.ChainParams()) 587 if err != nil { 588 return nil, translateError(err) 589 } 590 pubKeyAddrString = pubKeyAddr.String() 591 } 592 593 return &pb.AddressResponse{ 594 Address: addr.String(), 595 PublicKey: pubKeyAddrString, 596 }, nil 597 } 598 599 func (s *walletServer) DumpPrivateKey(ctx context.Context, req *pb.DumpPrivateKeyRequest) ( 600 *pb.DumpPrivateKeyResponse, error) { 601 602 addr, err := decodeAddress(req.Address, s.wallet.ChainParams()) 603 if err != nil { 604 return nil, err 605 } 606 607 key, err := s.wallet.DumpWIFPrivateKey(ctx, addr) 608 if err != nil { 609 return nil, translateError(err) 610 } 611 return &pb.DumpPrivateKeyResponse{PrivateKeyWif: key}, nil 612 } 613 614 func (s *walletServer) ImportPrivateKey(ctx context.Context, req *pb.ImportPrivateKeyRequest) ( 615 *pb.ImportPrivateKeyResponse, error) { 616 617 defer zero(req.Passphrase) 618 619 wif, err := dcrutil.DecodeWIF(req.PrivateKeyWif, s.wallet.ChainParams().PrivateKeyID) 620 if err != nil { 621 return nil, status.Errorf(codes.InvalidArgument, 622 "Invalid WIF-encoded private key: %v", err) 623 } 624 625 if len(req.Passphrase) > 0 { 626 lock := make(chan time.Time, 1) 627 defer func() { 628 lock <- time.Time{} // send matters, not the value 629 }() 630 err = s.wallet.Unlock(ctx, req.Passphrase, lock) 631 if err != nil { 632 return nil, translateError(err) 633 } 634 } 635 636 // At the moment, only the special-cased import account can be used to 637 // import keys. 638 if req.Account != udb.ImportedAddrAccount { 639 return nil, status.Errorf(codes.InvalidArgument, 640 "Only the imported account accepts private key imports") 641 } 642 643 if req.ScanFrom < 0 { 644 return nil, status.Errorf(codes.InvalidArgument, 645 "Attempted to scan from a negative block height") 646 } 647 648 if req.ScanFrom > 0 && req.Rescan { 649 return nil, status.Errorf(codes.InvalidArgument, 650 "Passed a rescan height without rescan set") 651 } 652 653 n, err := s.requireNetworkBackend() 654 if err != nil { 655 return nil, err 656 } 657 658 _, err = s.wallet.ImportPrivateKey(ctx, wif) 659 if err != nil { 660 return nil, translateError(err) 661 } 662 663 if req.Rescan { 664 go s.wallet.RescanFromHeight(context.Background(), n, req.ScanFrom) 665 } 666 667 return &pb.ImportPrivateKeyResponse{}, nil 668 } 669 670 func (s *walletServer) ImportExtendedPublicKey(ctx context.Context, req *pb.ImportExtendedPublicKeyRequest) ( 671 *pb.ImportExtendedPublicKeyResponse, error) { 672 673 xpub, err := hdkeychain.NewKeyFromString(req.Xpub, s.wallet.ChainParams()) 674 if err != nil { 675 return nil, err 676 } 677 678 n, err := s.requireNetworkBackend() 679 if err != nil { 680 return nil, err 681 } 682 683 err = s.wallet.ImportXpubAccount(ctx, req.AccountName, xpub) 684 if err != nil { 685 return nil, translateError(err) 686 } 687 688 if req.Rescan { 689 go s.wallet.RescanFromHeight(context.Background(), n, req.ScanFrom) 690 } 691 692 return &pb.ImportExtendedPublicKeyResponse{}, nil 693 } 694 695 func (s *walletServer) ImportScript(ctx context.Context, 696 req *pb.ImportScriptRequest) (*pb.ImportScriptResponse, error) { 697 698 // TODO: Rather than assuming a script version, it must become a parameter 699 // to the request. 700 sc, addrs := stdscript.ExtractAddrs(scriptVersionAssumed, req.Script, s.wallet.ChainParams()) 701 requiredSigs := stdscript.DetermineRequiredSigs(scriptVersionAssumed, req.Script) 702 // NOTE: not explicitly disallowing len(addrs) == 0 && requiredSigs == 0 703 var ownAddrs uint16 704 for _, a := range addrs { 705 haveAddr, err := s.wallet.HaveAddress(ctx, a) 706 if err != nil { 707 return nil, translateError(err) 708 } 709 if haveAddr { 710 ownAddrs++ 711 } 712 } 713 redeemable := sc == stdscript.STMultiSig && ownAddrs >= requiredSigs 714 if !redeemable && req.RequireRedeemable { 715 return nil, status.Errorf(codes.FailedPrecondition, 716 "The script is not redeemable by the wallet") 717 } 718 719 if req.ScanFrom < 0 { 720 return nil, status.Errorf(codes.InvalidArgument, 721 "Attempted to scan from a negative block height") 722 } 723 724 if req.ScanFrom > 0 && req.Rescan { 725 return nil, status.Errorf(codes.InvalidArgument, 726 "Passed a rescan height without rescan set") 727 } 728 729 n, err := s.requireNetworkBackend() 730 if err != nil { 731 return nil, err 732 } 733 734 err = s.wallet.ImportScript(ctx, req.Script) 735 if err != nil && !errors.Is(err, errors.Exist) { 736 return nil, translateError(err) 737 } 738 if err == nil && req.Rescan { 739 go s.wallet.RescanFromHeight(context.Background(), n, req.ScanFrom) 740 } 741 742 p2sh, err := stdaddr.NewAddressScriptHashV0(req.Script, s.wallet.ChainParams()) 743 if err != nil { 744 return nil, translateError(err) 745 } 746 747 return &pb.ImportScriptResponse{P2ShAddress: p2sh.String(), Redeemable: redeemable}, nil 748 } 749 750 func (s *walletServer) ImportVotingAccountFromSeed(ctx context.Context, req *pb.ImportVotingAccountFromSeedRequest) ( 751 *pb.ImportVotingAccountFromSeedResponse, error) { 752 753 defer func() { 754 zero(req.Passphrase) 755 zero(req.Seed) 756 }() 757 758 seedSize := len(req.Seed) 759 if seedSize < hdkeychain.MinSeedBytes || seedSize > hdkeychain.MaxSeedBytes { 760 return nil, status.Errorf(codes.InvalidArgument, "invalid seed length") 761 } 762 763 if req.ScanFrom < 0 { 764 return nil, status.Errorf(codes.InvalidArgument, 765 "Attempted to scan from a negative block height") 766 } 767 768 if req.ScanFrom > 0 && req.Rescan { 769 return nil, status.Errorf(codes.InvalidArgument, 770 "Passed a rescan height without rescan set") 771 } 772 773 if req.DiscoverFrom < 0 { 774 return nil, status.Errorf(codes.InvalidArgument, 775 "Attempted to discover usage from a negative block height") 776 } 777 778 if req.DiscoverFrom > 0 && req.DiscoverUsage { 779 return nil, status.Errorf(codes.InvalidArgument, 780 "Passed a discover usage height without discover usage set") 781 } 782 783 var ( 784 n wallet.NetworkBackend 785 err error 786 ) 787 if req.DiscoverUsage || req.Rescan { 788 n, err = s.requireNetworkBackend() 789 if err != nil { 790 return nil, status.Errorf(codes.Unknown, "Unable to retrieve network backend. Error: %v", err) 791 } 792 } 793 794 acctKeyPriv, err := s.wallet.VotingXprivFromSeed(req.Seed) 795 if err != nil { 796 return nil, translateError(err) 797 } 798 799 accountN, err := s.wallet.ImportVotingAccount(ctx, acctKeyPriv, req.Passphrase, req.Name) 800 if err != nil { 801 return nil, translateError(err) 802 } 803 804 if req.DiscoverUsage { 805 startBlock := s.wallet.ChainParams().GenesisHash 806 if req.DiscoverFrom != 0 { 807 blockID := wallet.NewBlockIdentifierFromHeight(req.DiscoverFrom) 808 b, err := s.wallet.BlockInfo(ctx, blockID) 809 if err != nil { 810 return nil, translateError(err) 811 } 812 startBlock = b.Hash 813 } 814 815 gapLimit := s.wallet.GapLimit() 816 if req.DiscoverGapLimit != 0 { 817 gapLimit = uint32(req.DiscoverGapLimit) 818 } 819 820 err = s.wallet.DiscoverActiveAddresses(ctx, n, &startBlock, false, gapLimit) 821 if err != nil { 822 return nil, err 823 } 824 } 825 826 if req.Rescan { 827 go s.wallet.RescanFromHeight(context.Background(), n, req.ScanFrom) 828 } 829 830 return &pb.ImportVotingAccountFromSeedResponse{Account: accountN}, nil 831 } 832 833 func (s *walletServer) Balance(ctx context.Context, req *pb.BalanceRequest) ( 834 *pb.BalanceResponse, error) { 835 836 account := req.AccountNumber 837 reqConfs := req.RequiredConfirmations 838 bals, err := s.wallet.AccountBalance(ctx, account, reqConfs) 839 if err != nil { 840 return nil, translateError(err) 841 } 842 843 // TODO: Spendable currently includes multisig outputs that may not 844 // actually be spendable without additional keys. 845 resp := &pb.BalanceResponse{ 846 Total: int64(bals.Total), 847 Spendable: int64(bals.Spendable), 848 ImmatureReward: int64(bals.ImmatureCoinbaseRewards), 849 ImmatureStakeGeneration: int64(bals.ImmatureStakeGeneration), 850 LockedByTickets: int64(bals.LockedByTickets), 851 VotingAuthority: int64(bals.VotingAuthority), 852 Unconfirmed: int64(bals.Unconfirmed), 853 } 854 return resp, nil 855 } 856 857 func (s *walletServer) TicketPrice(ctx context.Context, req *pb.TicketPriceRequest) (*pb.TicketPriceResponse, error) { 858 sdiff, err := s.wallet.NextStakeDifficulty(ctx) 859 if err != nil { 860 return nil, translateError(err) 861 } 862 _, tipHeight := s.wallet.MainChainTip(ctx) 863 resp := &pb.TicketPriceResponse{ 864 TicketPrice: int64(sdiff), 865 Height: tipHeight, 866 } 867 return resp, nil 868 } 869 870 func (s *walletServer) StakeInfo(ctx context.Context, req *pb.StakeInfoRequest) (*pb.StakeInfoResponse, error) { 871 var rpc *dcrd.RPC 872 n, _ := s.wallet.NetworkBackend() 873 if client, ok := n.(*dcrd.RPC); ok { 874 rpc = client 875 } 876 var si *wallet.StakeInfoData 877 var err error 878 if rpc != nil { 879 si, err = s.wallet.StakeInfoPrecise(ctx, rpc) 880 } else { 881 si, err = s.wallet.StakeInfo(ctx) 882 } 883 if err != nil { 884 return nil, translateError(err) 885 } 886 887 return &pb.StakeInfoResponse{ 888 PoolSize: si.PoolSize, 889 AllMempoolTix: si.AllMempoolTix, 890 OwnMempoolTix: si.OwnMempoolTix, 891 Immature: si.Immature, 892 Live: si.Live, 893 Voted: si.Voted, 894 Missed: si.Missed, 895 Revoked: si.Revoked, 896 Expired: si.Expired, 897 TotalSubsidy: int64(si.TotalSubsidy), 898 Unspent: si.Unspent, 899 }, nil 900 } 901 902 // scriptChangeSource is a ChangeSource which is used to 903 // receive all correlated previous input value. 904 type scriptChangeSource struct { 905 version uint16 906 script []byte 907 } 908 909 func (src *scriptChangeSource) Script() ([]byte, uint16, error) { 910 return src.script, src.version, nil 911 } 912 913 func (src *scriptChangeSource) ScriptSize() int { 914 return len(src.script) 915 } 916 917 func makeScriptChangeSource(address string, params *chaincfg.Params) (*scriptChangeSource, error) { 918 destinationAddress, err := stdaddr.DecodeAddress(address, params) 919 if err != nil { 920 return nil, err 921 } 922 version, script := destinationAddress.PaymentScript() 923 source := &scriptChangeSource{ 924 version: version, 925 script: script, 926 } 927 return source, nil 928 } 929 930 func sumOutputValues(outputs []*wire.TxOut) (totalOutput dcrutil.Amount) { 931 for _, txOut := range outputs { 932 totalOutput += dcrutil.Amount(txOut.Value) 933 } 934 return totalOutput 935 } 936 937 func (s *walletServer) SweepAccount(ctx context.Context, req *pb.SweepAccountRequest) (*pb.SweepAccountResponse, error) { 938 feePerKb := s.wallet.RelayFee() 939 940 // Use provided fee per Kb if specified. 941 if req.FeePerKb < 0 { 942 return nil, status.Errorf(codes.InvalidArgument, "%s", 943 "fee per kb argument cannot be negative") 944 } 945 946 if req.FeePerKb > 0 { 947 var err error 948 feePerKb, err = dcrutil.NewAmount(req.FeePerKb) 949 if err != nil { 950 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 951 } 952 } 953 954 account, err := s.wallet.AccountNumber(ctx, req.SourceAccount) 955 if err != nil { 956 return nil, translateError(err) 957 } 958 959 changeSource, err := makeScriptChangeSource(req.DestinationAddress, s.wallet.ChainParams()) 960 if err != nil { 961 return nil, translateError(err) 962 } 963 964 tx, err := s.wallet.NewUnsignedTransaction(ctx, nil, feePerKb, account, 965 int32(req.RequiredConfirmations), wallet.OutputSelectionAlgorithmAll, 966 changeSource, nil) 967 if err != nil { 968 return nil, translateError(err) 969 } 970 971 var txBuf bytes.Buffer 972 txBuf.Grow(tx.Tx.SerializeSize()) 973 err = tx.Tx.Serialize(&txBuf) 974 if err != nil { 975 return nil, translateError(err) 976 } 977 978 res := &pb.SweepAccountResponse{ 979 UnsignedTransaction: txBuf.Bytes(), 980 TotalPreviousOutputAmount: int64(tx.TotalInput), 981 TotalOutputAmount: int64(sumOutputValues(tx.Tx.TxOut)), 982 EstimatedSignedSize: uint32(tx.EstimatedSignedSerializeSize), 983 } 984 985 return res, nil 986 } 987 988 func (s *walletServer) BlockInfo(ctx context.Context, req *pb.BlockInfoRequest) (*pb.BlockInfoResponse, error) { 989 var blockID *wallet.BlockIdentifier 990 switch { 991 case req.BlockHash != nil && req.BlockHeight != 0: 992 return nil, status.Errorf(codes.InvalidArgument, "block hash and height must not be set together") 993 case req.BlockHash != nil: 994 blockHash, err := chainhash.NewHash(req.BlockHash) 995 if err != nil { 996 return nil, status.Errorf(codes.InvalidArgument, "block hash has invalid length") 997 } 998 blockID = wallet.NewBlockIdentifierFromHash(blockHash) 999 default: 1000 blockID = wallet.NewBlockIdentifierFromHeight(req.BlockHeight) 1001 } 1002 1003 b, err := s.wallet.BlockInfo(ctx, blockID) 1004 if err != nil { 1005 return nil, translateError(err) 1006 } 1007 1008 header := new(wire.BlockHeader) 1009 err = header.Deserialize(bytes.NewReader(b.Header)) 1010 if err != nil { 1011 return nil, status.Errorf(codes.Internal, "failed to deserialize saved block header: %v", err) 1012 } 1013 1014 return &pb.BlockInfoResponse{ 1015 BlockHash: b.Hash[:], 1016 BlockHeight: b.Height, 1017 Confirmations: b.Confirmations, 1018 Timestamp: b.Timestamp, 1019 BlockHeader: b.Header, 1020 StakeInvalidated: b.StakeInvalidated, 1021 ApprovesParent: header.VoteBits&dcrutil.BlockValid != 0, 1022 }, nil 1023 } 1024 1025 func (s *walletServer) UnspentOutputs(req *pb.UnspentOutputsRequest, svr pb.WalletService_UnspentOutputsServer) error { 1026 policy := wallet.OutputSelectionPolicy{ 1027 Account: req.Account, 1028 RequiredConfirmations: req.RequiredConfirmations, 1029 } 1030 inputDetail, err := s.wallet.SelectInputs(svr.Context(), dcrutil.Amount(req.TargetAmount), policy) 1031 // Do not return errors to caller when there was insufficient spendable 1032 // outputs available for the target amount. 1033 if err != nil && !errors.Is(err, errors.InsufficientBalance) { 1034 return translateError(err) 1035 } 1036 1037 var sum int64 1038 for i, input := range inputDetail.Inputs { 1039 select { 1040 case <-svr.Context().Done(): 1041 return status.Errorf(codes.Canceled, "unspentoutputs cancelled") 1042 default: 1043 outputInfo, err := s.wallet.OutputInfo(svr.Context(), &input.PreviousOutPoint) 1044 if err != nil { 1045 return translateError(err) 1046 } 1047 unspentOutput := &pb.UnspentOutputResponse{ 1048 TransactionHash: input.PreviousOutPoint.Hash[:], 1049 OutputIndex: input.PreviousOutPoint.Index, 1050 Tree: int32(input.PreviousOutPoint.Tree), 1051 Amount: int64(outputInfo.Amount), 1052 PkScript: inputDetail.Scripts[i], 1053 ReceiveTime: outputInfo.Received.Unix(), 1054 FromCoinbase: outputInfo.FromCoinbase, 1055 } 1056 1057 sum += unspentOutput.Amount 1058 unspentOutput.AmountSum = sum 1059 1060 err = svr.Send(unspentOutput) 1061 if err != nil { 1062 return translateError(err) 1063 } 1064 } 1065 } 1066 return nil 1067 } 1068 1069 func (s *walletServer) FundTransaction(ctx context.Context, req *pb.FundTransactionRequest) ( 1070 *pb.FundTransactionResponse, error) { 1071 1072 policy := wallet.OutputSelectionPolicy{ 1073 Account: req.Account, 1074 RequiredConfirmations: req.RequiredConfirmations, 1075 } 1076 inputDetail, err := s.wallet.SelectInputs(ctx, dcrutil.Amount(req.TargetAmount), policy) 1077 // Do not return errors to caller when there was insufficient spendable 1078 // outputs available for the target amount. 1079 if err != nil && !errors.Is(err, errors.InsufficientBalance) { 1080 return nil, translateError(err) 1081 } 1082 1083 selectedOutputs := make([]*pb.FundTransactionResponse_PreviousOutput, len(inputDetail.Inputs)) 1084 for i, input := range inputDetail.Inputs { 1085 outputInfo, err := s.wallet.OutputInfo(ctx, &input.PreviousOutPoint) 1086 if err != nil { 1087 return nil, translateError(err) 1088 } 1089 selectedOutputs[i] = &pb.FundTransactionResponse_PreviousOutput{ 1090 TransactionHash: input.PreviousOutPoint.Hash[:], 1091 OutputIndex: input.PreviousOutPoint.Index, 1092 Tree: int32(input.PreviousOutPoint.Tree), 1093 Amount: int64(outputInfo.Amount), 1094 PkScript: inputDetail.Scripts[i], 1095 ReceiveTime: outputInfo.Received.Unix(), 1096 FromCoinbase: outputInfo.FromCoinbase, 1097 } 1098 } 1099 1100 var changeScript []byte 1101 if req.IncludeChangeScript && inputDetail.Amount > dcrutil.Amount(req.TargetAmount) { 1102 changeAddr, err := s.wallet.NewChangeAddress(ctx, req.Account) 1103 if err != nil { 1104 return nil, translateError(err) 1105 } 1106 _, changeScript = changeAddr.PaymentScript() 1107 } 1108 1109 return &pb.FundTransactionResponse{ 1110 SelectedOutputs: selectedOutputs, 1111 TotalAmount: int64(inputDetail.Amount), 1112 ChangePkScript: changeScript, 1113 }, nil 1114 } 1115 1116 func decodeDestination(dest *pb.ConstructTransactionRequest_OutputDestination, 1117 chainParams *chaincfg.Params) (pkScript []byte, version uint16, err error) { 1118 1119 switch { 1120 case dest == nil: 1121 fallthrough 1122 default: 1123 return nil, 0, status.Errorf(codes.InvalidArgument, "unknown or missing output destination") 1124 1125 case dest.Address != "": 1126 addr, err := decodeAddress(dest.Address, chainParams) 1127 if err != nil { 1128 return nil, 0, err 1129 } 1130 version, pkScript = addr.PaymentScript() 1131 return pkScript, version, nil 1132 case dest.Script != nil: 1133 if dest.ScriptVersion > uint32(^uint16(0)) { 1134 return nil, 0, status.Errorf(codes.InvalidArgument, "script_version overflows uint16") 1135 } 1136 return dest.Script, uint16(dest.ScriptVersion), nil 1137 } 1138 } 1139 1140 type txChangeSource struct { 1141 version uint16 1142 script []byte 1143 } 1144 1145 func (src *txChangeSource) Script() ([]byte, uint16, error) { 1146 return src.script, src.version, nil 1147 } 1148 1149 func (src *txChangeSource) ScriptSize() int { 1150 return len(src.script) 1151 } 1152 1153 func makeTxChangeSource(destination *pb.ConstructTransactionRequest_OutputDestination, 1154 chainParams *chaincfg.Params) (*txChangeSource, error) { 1155 script, version, err := decodeDestination(destination, chainParams) 1156 if err != nil { 1157 return nil, err 1158 } 1159 changeSource := &txChangeSource{ 1160 script: script, 1161 version: version, 1162 } 1163 return changeSource, nil 1164 } 1165 1166 func (s *walletServer) ConstructTransaction(ctx context.Context, req *pb.ConstructTransactionRequest) ( 1167 *pb.ConstructTransactionResponse, error) { 1168 1169 chainParams := s.wallet.ChainParams() 1170 1171 if len(req.NonChangeOutputs) == 0 && req.ChangeDestination == nil { 1172 return nil, status.Errorf(codes.InvalidArgument, 1173 "non_change_outputs and change_destination may not both be empty or null") 1174 } 1175 1176 outputs := make([]*wire.TxOut, 0, len(req.NonChangeOutputs)) 1177 for _, o := range req.NonChangeOutputs { 1178 script, version, err := decodeDestination(o.Destination, chainParams) 1179 if err != nil { 1180 return nil, err 1181 } 1182 output := &wire.TxOut{ 1183 Value: o.Amount, 1184 Version: version, 1185 PkScript: script, 1186 } 1187 outputs = append(outputs, output) 1188 } 1189 1190 var algo wallet.OutputSelectionAlgorithm 1191 switch req.OutputSelectionAlgorithm { 1192 case pb.ConstructTransactionRequest_UNSPECIFIED: 1193 algo = wallet.OutputSelectionAlgorithmDefault 1194 case pb.ConstructTransactionRequest_ALL: 1195 algo = wallet.OutputSelectionAlgorithmAll 1196 default: 1197 return nil, status.Errorf(codes.InvalidArgument, "unknown output selection algorithm") 1198 } 1199 1200 feePerKb := txrules.DefaultRelayFeePerKb 1201 if req.FeePerKb != 0 { 1202 feePerKb = dcrutil.Amount(req.FeePerKb) 1203 } 1204 1205 var changeSource txauthor.ChangeSource 1206 var err error 1207 if req.ChangeDestination != nil { 1208 changeSource, err = makeTxChangeSource(req.ChangeDestination, chainParams) 1209 if err != nil { 1210 return nil, translateError(err) 1211 } 1212 } 1213 1214 tx, err := s.wallet.NewUnsignedTransaction(ctx, outputs, feePerKb, req.SourceAccount, 1215 req.RequiredConfirmations, algo, changeSource, nil) 1216 if err != nil { 1217 return nil, translateError(err) 1218 } 1219 1220 if tx.ChangeIndex >= 0 { 1221 tx.RandomizeChangePosition() 1222 } 1223 1224 var txBuf bytes.Buffer 1225 txBuf.Grow(tx.Tx.SerializeSize()) 1226 err = tx.Tx.Serialize(&txBuf) 1227 if err != nil { 1228 return nil, translateError(err) 1229 } 1230 1231 res := &pb.ConstructTransactionResponse{ 1232 UnsignedTransaction: txBuf.Bytes(), 1233 TotalPreviousOutputAmount: int64(tx.TotalInput), 1234 TotalOutputAmount: int64(sumOutputValues(tx.Tx.TxOut)), 1235 EstimatedSignedSize: uint32(tx.EstimatedSignedSerializeSize), 1236 ChangeIndex: int32(tx.ChangeIndex), 1237 } 1238 return res, nil 1239 } 1240 1241 func (s *walletServer) GetAccountExtendedPubKey(ctx context.Context, req *pb.GetAccountExtendedPubKeyRequest) (*pb.GetAccountExtendedPubKeyResponse, error) { 1242 accExtendedPubKey, err := s.wallet.AccountXpub(ctx, req.AccountNumber) 1243 if err != nil { 1244 return nil, err 1245 } 1246 res := &pb.GetAccountExtendedPubKeyResponse{ 1247 AccExtendedPubKey: accExtendedPubKey.String(), 1248 } 1249 return res, nil 1250 } 1251 1252 func (s *walletServer) GetAccountExtendedPrivKey(ctx context.Context, req *pb.GetAccountExtendedPrivKeyRequest) (*pb.GetAccountExtendedPrivKeyResponse, error) { 1253 if len(req.Passphrase) > 0 { 1254 lock := make(chan time.Time, 1) 1255 lockWallet := func() { 1256 lock <- time.Time{} 1257 zero(req.Passphrase) 1258 } 1259 1260 err := s.wallet.Unlock(ctx, req.Passphrase, lock) 1261 if err != nil { 1262 return nil, translateError(err) 1263 } 1264 defer lockWallet() 1265 } 1266 1267 accExtendedPrivKey, err := s.wallet.AccountXpriv(ctx, req.AccountNumber) 1268 if err != nil { 1269 return nil, translateError(err) 1270 } 1271 res := &pb.GetAccountExtendedPrivKeyResponse{ 1272 AccExtendedPrivKey: accExtendedPrivKey.String(), 1273 } 1274 return res, nil 1275 } 1276 1277 func (s *walletServer) GetTransaction(ctx context.Context, req *pb.GetTransactionRequest) (*pb.GetTransactionResponse, error) { 1278 txHash, err := chainhash.NewHash(req.TransactionHash) 1279 if err != nil { 1280 return nil, status.Errorf(codes.InvalidArgument, "transaction_hash has invalid length") 1281 } 1282 1283 txSummary, confs, blockHash, err := s.wallet.TransactionSummary(ctx, txHash) 1284 if err != nil { 1285 return nil, translateError(err) 1286 } 1287 resp := &pb.GetTransactionResponse{ 1288 Transaction: marshalTransactionDetails(txSummary), 1289 Confirmations: confs, 1290 } 1291 if blockHash != nil { 1292 resp.BlockHash = blockHash[:] 1293 } 1294 return resp, nil 1295 } 1296 1297 type blockRangeReq interface { 1298 GetStartingBlockHash() []byte 1299 GetStartingBlockHeight() int32 1300 GetEndingBlockHash() []byte 1301 GetEndingBlockHeight() int32 1302 } 1303 1304 // decodeBlockRange converts a request that has the standard set of 1305 // StartingBlockHash/StartingBlockHeight, EndingBlockHash/EndingBlockHeight 1306 // parameters into corresponding *wallet.BlockIdentifier for use in functions 1307 // that range over blocks. 1308 // 1309 // For each (hash,height) pair of each (start,end) set, the following 1310 // conversion is made: 1311 // 1312 // - Specifying both hash and height is an error 1313 // - Non-nil hash is used 1314 // - Non-zero height is used 1315 // - Otherwise a nil identifier is returned 1316 // 1317 // This behavior makes the wallet's internal `blockRange` function match blocks 1318 // over the entire range of mainchain blocks, including unmined transactions 1319 // (when applicable). 1320 func decodeBlockRange(req blockRangeReq) (*wallet.BlockIdentifier, *wallet.BlockIdentifier, error) { 1321 var startBlock, endBlock *wallet.BlockIdentifier 1322 sbh := req.GetStartingBlockHash() 1323 sh := req.GetStartingBlockHeight() 1324 ebh := req.GetEndingBlockHash() 1325 eh := req.GetEndingBlockHeight() 1326 1327 // Determine start block identifier. 1328 switch { 1329 case sbh != nil && sh != 0: 1330 return nil, nil, status.Errorf(codes.InvalidArgument, 1331 "starting block hash and height may not be specified simultaneously") 1332 case sbh != nil: 1333 hash, err := chainhash.NewHash(sbh) 1334 if err != nil { 1335 return nil, nil, status.Errorf(codes.InvalidArgument, 1336 "invalid starting block hash: %s", err.Error()) 1337 } 1338 startBlock = wallet.NewBlockIdentifierFromHash(hash) 1339 case sh != 0: 1340 startBlock = wallet.NewBlockIdentifierFromHeight(sh) 1341 } 1342 1343 // Determine end block identifier. 1344 switch { 1345 case ebh != nil && eh != 0: 1346 return nil, nil, status.Errorf(codes.InvalidArgument, 1347 "ending block hash and height may not be specified simultaneously") 1348 case ebh != nil: 1349 hash, err := chainhash.NewHash(ebh) 1350 if err != nil { 1351 return nil, nil, status.Errorf(codes.InvalidArgument, 1352 "invalid ending block hash: %s", err.Error()) 1353 } 1354 endBlock = wallet.NewBlockIdentifierFromHash(hash) 1355 case eh != 0: 1356 endBlock = wallet.NewBlockIdentifierFromHeight(eh) 1357 } 1358 1359 return startBlock, endBlock, nil 1360 } 1361 1362 // BUGS: 1363 // - MinimumRecentTransactions is ignored. 1364 // - Wrong error codes when a block height or hash is not recognized 1365 func (s *walletServer) GetTransactions(req *pb.GetTransactionsRequest, 1366 server pb.WalletService_GetTransactionsServer) error { 1367 1368 startBlock, endBlock, err := decodeBlockRange(req) 1369 if err != nil { 1370 return err 1371 } 1372 1373 var minRecentTxs int 1374 if req.MinimumRecentTransactions != 0 { 1375 if endBlock != nil { 1376 return status.Errorf(codes.InvalidArgument, 1377 "ending block and minimum number of recent transactions "+ 1378 "may not be specified simultaneously") 1379 } 1380 minRecentTxs = int(req.MinimumRecentTransactions) 1381 if minRecentTxs < 0 { 1382 return status.Errorf(codes.InvalidArgument, 1383 "minimum number of recent transactions may not be negative") 1384 } 1385 } 1386 _ = minRecentTxs 1387 1388 targetTxCount := int(req.TargetTransactionCount) 1389 if targetTxCount < 0 { 1390 return status.Errorf(codes.InvalidArgument, 1391 "maximum transaction count may not be negative") 1392 } 1393 1394 ctx := server.Context() 1395 txCount := 0 1396 1397 rangeFn := func(block *wallet.Block) (bool, error) { 1398 var resp *pb.GetTransactionsResponse 1399 if block.Header != nil { 1400 resp = &pb.GetTransactionsResponse{ 1401 MinedTransactions: marshalBlock(block), 1402 } 1403 } else { 1404 resp = &pb.GetTransactionsResponse{ 1405 UnminedTransactions: marshalTransactionDetailsSlice(block.Transactions), 1406 } 1407 } 1408 txCount += len(block.Transactions) 1409 1410 select { 1411 case <-ctx.Done(): 1412 return true, ctx.Err() 1413 default: 1414 err := server.Send(resp) 1415 return (err != nil) || ((targetTxCount > 0) && (txCount >= targetTxCount)), err 1416 } 1417 } 1418 1419 err = s.wallet.GetTransactions(ctx, rangeFn, startBlock, endBlock) 1420 if err != nil { 1421 return translateError(err) 1422 } 1423 1424 return nil 1425 } 1426 1427 func (s *walletServer) GetTicket(ctx context.Context, req *pb.GetTicketRequest) (*pb.GetTicketsResponse, error) { 1428 ticketHash, err := chainhash.NewHash(req.TicketHash) 1429 if err != nil { 1430 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 1431 } 1432 1433 // The dcrd client could be nil here if the network backend is not 1434 // the consensus rpc client. This is fine since the chain client is 1435 // optional. 1436 n, _ := s.wallet.NetworkBackend() 1437 rpc, _ := n.(*dcrd.RPC) 1438 1439 var ticketSummary *wallet.TicketSummary 1440 var blockHeader *wire.BlockHeader 1441 if rpc == nil { 1442 ticketSummary, blockHeader, err = s.wallet.GetTicketInfo(ctx, ticketHash) 1443 } else { 1444 ticketSummary, blockHeader, err = 1445 s.wallet.GetTicketInfoPrecise(ctx, rpc, ticketHash) 1446 } 1447 if err != nil { 1448 return nil, translateError(err) 1449 } 1450 1451 host, err := s.wallet.VSPHostForTicket(ctx, ticketHash) 1452 if err != nil && !errors.Is(err, errors.NotExist) { 1453 return nil, err 1454 } 1455 1456 resp := &pb.GetTicketsResponse{ 1457 Ticket: marshalTicketDetails(ticketSummary), 1458 Block: marshalGetTicketBlockDetails(blockHeader), 1459 VspHost: host, 1460 } 1461 return resp, nil 1462 } 1463 1464 func (s *walletServer) GetTickets(req *pb.GetTicketsRequest, 1465 server pb.WalletService_GetTicketsServer) error { 1466 1467 startBlock, endBlock, err := decodeBlockRange(req) 1468 if err != nil { 1469 return err 1470 } 1471 1472 targetTicketCount := int(req.TargetTicketCount) 1473 if targetTicketCount < 0 { 1474 return status.Errorf(codes.InvalidArgument, 1475 "target ticket count may not be negative") 1476 } 1477 1478 ticketCount := 0 1479 ctx := server.Context() 1480 1481 rangeFn := func(tickets []*wallet.TicketSummary, block *wire.BlockHeader) (bool, error) { 1482 resp := &pb.GetTicketsResponse{ 1483 Block: marshalGetTicketBlockDetails(block), 1484 } 1485 1486 // current contract for grpc GetTickets is for one ticket per response. 1487 // To make sure we don't miss any while paginating, we only check for 1488 // the targetTicketCount after sending all from this block. 1489 for _, t := range tickets { 1490 resp.Ticket = marshalTicketDetails(t) 1491 1492 host, err := s.wallet.VSPHostForTicket(ctx, t.Ticket.Hash) 1493 if err != nil && !errors.Is(err, errors.NotExist) { 1494 return true, err 1495 } 1496 resp.VspHost = host 1497 1498 err = server.Send(resp) 1499 if err != nil { 1500 return true, err 1501 } 1502 } 1503 ticketCount += len(tickets) 1504 1505 select { 1506 case <-ctx.Done(): 1507 return true, ctx.Err() 1508 default: 1509 return ((targetTicketCount > 0) && (ticketCount >= targetTicketCount)), nil 1510 } 1511 } 1512 n, _ := s.wallet.NetworkBackend() 1513 if rpc, ok := n.(*dcrd.RPC); ok { 1514 err = s.wallet.GetTicketsPrecise(ctx, rpc, rangeFn, startBlock, endBlock) 1515 } else { 1516 err = s.wallet.GetTickets(ctx, rangeFn, startBlock, endBlock) 1517 } 1518 if err != nil { 1519 return translateError(err) 1520 } 1521 1522 return nil 1523 } 1524 1525 func (s *walletServer) ChangePassphrase(ctx context.Context, req *pb.ChangePassphraseRequest) ( 1526 *pb.ChangePassphraseResponse, error) { 1527 1528 defer func() { 1529 zero(req.OldPassphrase) 1530 zero(req.NewPassphrase) 1531 }() 1532 1533 var ( 1534 oldPass = req.OldPassphrase 1535 newPass = req.NewPassphrase 1536 ) 1537 1538 var err error 1539 switch req.Key { 1540 case pb.ChangePassphraseRequest_PRIVATE: 1541 err = s.wallet.ChangePrivatePassphrase(ctx, oldPass, newPass) 1542 case pb.ChangePassphraseRequest_PUBLIC: 1543 if len(oldPass) == 0 { 1544 oldPass = []byte(wallet.InsecurePubPassphrase) 1545 } 1546 if len(newPass) == 0 { 1547 newPass = []byte(wallet.InsecurePubPassphrase) 1548 } 1549 err = s.wallet.ChangePublicPassphrase(ctx, oldPass, newPass) 1550 default: 1551 return nil, status.Errorf(codes.InvalidArgument, "Unknown key type (%d)", req.Key) 1552 } 1553 if err != nil { 1554 return nil, translateError(err) 1555 } 1556 return &pb.ChangePassphraseResponse{}, nil 1557 } 1558 1559 func (s *walletServer) SignTransaction(ctx context.Context, req *pb.SignTransactionRequest) ( 1560 *pb.SignTransactionResponse, error) { 1561 1562 var tx wire.MsgTx 1563 err := tx.Deserialize(bytes.NewReader(req.SerializedTransaction)) 1564 if err != nil { 1565 return nil, status.Errorf(codes.InvalidArgument, 1566 "Bytes do not represent a valid raw transaction: %v", err) 1567 } 1568 1569 if len(req.Passphrase) > 0 { 1570 lock := make(chan time.Time, 1) 1571 defer func() { 1572 lock <- time.Time{} // send matters, not the value 1573 zero(req.Passphrase) 1574 }() 1575 err = s.wallet.Unlock(ctx, req.Passphrase, lock) 1576 if err != nil { 1577 return nil, translateError(err) 1578 } 1579 } 1580 1581 var additionalPkScripts map[wire.OutPoint][]byte 1582 if len(req.AdditionalScripts) > 0 { 1583 additionalPkScripts = make(map[wire.OutPoint][]byte, len(req.AdditionalScripts)) 1584 for _, script := range req.AdditionalScripts { 1585 op := wire.OutPoint{Index: script.OutputIndex, Tree: int8(script.Tree)} 1586 if len(script.TransactionHash) != chainhash.HashSize { 1587 return nil, status.Errorf(codes.InvalidArgument, 1588 "Invalid transaction hash length for script %v, expected %v got %v", 1589 script, chainhash.HashSize, len(script.TransactionHash)) 1590 } 1591 1592 copy(op.Hash[:], script.TransactionHash) 1593 additionalPkScripts[op] = script.PkScript 1594 } 1595 } 1596 1597 invalidSigs, err := s.wallet.SignTransaction(ctx, &tx, txscript.SigHashAll, additionalPkScripts, nil, nil) 1598 if err != nil { 1599 return nil, translateError(err) 1600 } 1601 1602 invalidInputIndexes := make([]uint32, len(invalidSigs)) 1603 for i, e := range invalidSigs { 1604 invalidInputIndexes[i] = e.InputIndex 1605 } 1606 1607 var serializedTransaction bytes.Buffer 1608 serializedTransaction.Grow(tx.SerializeSize()) 1609 err = tx.Serialize(&serializedTransaction) 1610 if err != nil { 1611 return nil, translateError(err) 1612 } 1613 1614 resp := &pb.SignTransactionResponse{ 1615 Transaction: serializedTransaction.Bytes(), 1616 UnsignedInputIndexes: invalidInputIndexes, 1617 } 1618 return resp, nil 1619 } 1620 1621 func (s *walletServer) SignTransactions(ctx context.Context, req *pb.SignTransactionsRequest) ( 1622 *pb.SignTransactionsResponse, error) { 1623 defer zero(req.Passphrase) 1624 1625 if len(req.Passphrase) > 0 { 1626 lock := make(chan time.Time, 1) 1627 defer func() { 1628 lock <- time.Time{} // send matters, not the value 1629 }() 1630 err := s.wallet.Unlock(ctx, req.Passphrase, lock) 1631 if err != nil { 1632 return nil, translateError(err) 1633 } 1634 } 1635 1636 var additionalPkScripts map[wire.OutPoint][]byte 1637 if len(req.AdditionalScripts) > 0 { 1638 additionalPkScripts = make(map[wire.OutPoint][]byte, len(req.AdditionalScripts)) 1639 for _, script := range req.AdditionalScripts { 1640 op := wire.OutPoint{Index: script.OutputIndex, Tree: int8(script.Tree)} 1641 if len(script.TransactionHash) != chainhash.HashSize { 1642 return nil, status.Errorf(codes.InvalidArgument, 1643 "Invalid transaction hash length for script %v, expected %v got %v", 1644 script, chainhash.HashSize, len(script.TransactionHash)) 1645 } 1646 1647 copy(op.Hash[:], script.TransactionHash) 1648 additionalPkScripts[op] = script.PkScript 1649 } 1650 } 1651 1652 resp := pb.SignTransactionsResponse{} 1653 resp.Transactions = make([]*pb.SignTransactionsResponse_SignedTransaction, 0, len(req.Transactions)) 1654 for _, unsignedTx := range req.Transactions { 1655 var tx wire.MsgTx 1656 err := tx.Deserialize(bytes.NewReader(unsignedTx.SerializedTransaction)) 1657 if err != nil { 1658 return nil, status.Errorf(codes.InvalidArgument, 1659 "Bytes do not represent a valid raw transaction: %v", err) 1660 } 1661 1662 invalidSigs, err := s.wallet.SignTransaction(ctx, &tx, txscript.SigHashAll, additionalPkScripts, nil, nil) 1663 if err != nil { 1664 return nil, translateError(err) 1665 } 1666 1667 invalidInputIndexes := make([]uint32, len(invalidSigs)) 1668 for i, e := range invalidSigs { 1669 invalidInputIndexes[i] = e.InputIndex 1670 } 1671 1672 var serializedTransaction bytes.Buffer 1673 serializedTransaction.Grow(tx.SerializeSize()) 1674 err = tx.Serialize(&serializedTransaction) 1675 if err != nil { 1676 return nil, translateError(err) 1677 } 1678 1679 resp.Transactions = append(resp.Transactions, &pb.SignTransactionsResponse_SignedTransaction{ 1680 Transaction: serializedTransaction.Bytes(), 1681 UnsignedInputIndexes: invalidInputIndexes, 1682 }) 1683 } 1684 1685 return &resp, nil 1686 } 1687 1688 func (s *walletServer) CreateSignature(ctx context.Context, req *pb.CreateSignatureRequest) ( 1689 *pb.CreateSignatureResponse, error) { 1690 1691 defer zero(req.Passphrase) 1692 1693 var tx wire.MsgTx 1694 err := tx.Deserialize(bytes.NewReader(req.SerializedTransaction)) 1695 if err != nil { 1696 return nil, status.Errorf(codes.InvalidArgument, 1697 "Bytes do not represent a valid raw transaction: %v", err) 1698 } 1699 1700 if req.InputIndex >= uint32(len(tx.TxIn)) { 1701 return nil, status.Errorf(codes.InvalidArgument, 1702 "transaction input %d does not exist", req.InputIndex) 1703 } 1704 1705 if len(req.Passphrase) > 0 { 1706 lock := make(chan time.Time, 1) 1707 defer func() { 1708 lock <- time.Time{} // send matters, not the value 1709 }() 1710 err = s.wallet.Unlock(ctx, req.Passphrase, lock) 1711 if err != nil { 1712 return nil, translateError(err) 1713 } 1714 } 1715 1716 addr, err := decodeAddress(req.Address, s.wallet.ChainParams()) 1717 if err != nil { 1718 return nil, err 1719 } 1720 1721 hashType := txscript.SigHashType(req.HashType) 1722 sig, pubkey, err := s.wallet.CreateSignature(ctx, &tx, req.InputIndex, addr, hashType, req.PreviousPkScript) 1723 if err != nil { 1724 return nil, translateError(err) 1725 } 1726 1727 return &pb.CreateSignatureResponse{Signature: sig, PublicKey: pubkey}, nil 1728 } 1729 1730 func (s *walletServer) PublishTransaction(ctx context.Context, req *pb.PublishTransactionRequest) ( 1731 *pb.PublishTransactionResponse, error) { 1732 1733 n, err := s.requireNetworkBackend() 1734 if err != nil { 1735 return nil, err 1736 } 1737 1738 msgTx := new(wire.MsgTx) 1739 err = msgTx.Deserialize(bytes.NewReader(req.SignedTransaction)) 1740 if err != nil { 1741 return nil, status.Errorf(codes.InvalidArgument, 1742 "Bytes do not represent a valid raw transaction: %v", err) 1743 } 1744 1745 if !s.wallet.AllowsHighFees() { 1746 highFees, err := txrules.TxPaysHighFees(msgTx) 1747 if err != nil { 1748 return nil, translateError(err) 1749 } 1750 if highFees { 1751 err := errors.E(errors.Policy, "high fees") 1752 return nil, translateError(err) 1753 } 1754 } 1755 1756 txHash, err := s.wallet.PublishTransaction(ctx, msgTx, n) 1757 if err != nil { 1758 return nil, translateError(err) 1759 } 1760 1761 return &pb.PublishTransactionResponse{TransactionHash: txHash[:]}, nil 1762 } 1763 1764 // PurchaseTickets purchases tickets from the wallet. 1765 func (s *walletServer) PurchaseTickets(ctx context.Context, 1766 req *pb.PurchaseTicketsRequest) (*pb.PurchaseTicketsResponse, error) { 1767 // Unmarshall the received data and prepare it as input for the ticket 1768 // purchase request. 1769 spendLimit := dcrutil.Amount(req.SpendLimit) 1770 if spendLimit < 0 { 1771 return nil, status.Errorf(codes.InvalidArgument, 1772 "Negative spend limit given") 1773 } 1774 1775 minConf := int32(req.RequiredConfirmations) 1776 params := s.wallet.ChainParams() 1777 1778 var ticketAddr stdaddr.StakeAddress 1779 var err error 1780 1781 n, err := s.wallet.NetworkBackend() 1782 if err != nil { 1783 return nil, err 1784 } 1785 1786 if req.TicketAddress != "" { 1787 ticketAddr, err = decodeStakeAddress(req.TicketAddress, params) 1788 if err != nil { 1789 return nil, err 1790 } 1791 } 1792 1793 var poolAddr stdaddr.StakeAddress 1794 var poolFees float64 1795 if req.PoolAddress != "" { 1796 if req.VspHost != "" || req.VspPubkey != "" { 1797 return nil, status.Errorf(codes.InvalidArgument, 1798 "request contains both legacy stakepoold and vspd options.") 1799 } 1800 poolAddr, err = decodeStakeAddress(req.PoolAddress, params) 1801 if err != nil { 1802 return nil, err 1803 } 1804 } 1805 1806 // new vspd request 1807 var vspHost string 1808 var vspPubKey string 1809 var vspClient *vsp.Client 1810 if req.VspHost != "" || req.VspPubkey != "" { 1811 vspHost = req.VspHost 1812 vspPubKey = req.VspPubkey 1813 if vspPubKey == "" { 1814 return nil, status.Errorf(codes.InvalidArgument, "vsp pubkey can not be null") 1815 } 1816 if vspHost == "" { 1817 return nil, status.Errorf(codes.InvalidArgument, "vsp host can not be null") 1818 } 1819 cfg := vsp.Config{ 1820 URL: vspHost, 1821 PubKey: vspPubKey, 1822 Dialer: nil, 1823 Wallet: s.wallet, 1824 Policy: vsp.Policy{ 1825 MaxFee: 0.1e8, 1826 FeeAcct: req.Account, 1827 ChangeAcct: req.ChangeAccount, 1828 }, 1829 } 1830 vspClient, err = loader.VSP(cfg) 1831 if err != nil { 1832 return nil, status.Errorf(codes.Unknown, "VSP Server instance failed to start: %v", err) 1833 } 1834 } 1835 1836 if req.PoolFees > 0 { 1837 poolFees = req.PoolFees 1838 if !txrules.ValidPoolFeeRate(req.PoolFees) { 1839 return nil, status.Errorf(codes.InvalidArgument, "Invalid pool fees percentage") 1840 } 1841 } 1842 1843 if poolFees > 0 && poolAddr == nil { 1844 return nil, status.Errorf(codes.InvalidArgument, 1845 "Pool fees set but no pool address given") 1846 } 1847 1848 if poolFees <= 0 && poolAddr != nil { 1849 return nil, status.Errorf(codes.InvalidArgument, 1850 "Pool fees negative or unset but pool address given") 1851 } 1852 1853 numTickets := int(req.NumTickets) 1854 if numTickets < 1 { 1855 return nil, status.Errorf(codes.InvalidArgument, 1856 "Zero or negative number of tickets given") 1857 } 1858 1859 expiry := int32(req.Expiry) 1860 txFee := dcrutil.Amount(req.TxFee) 1861 1862 if txFee < 0 { 1863 return nil, status.Errorf(codes.InvalidArgument, 1864 "Negative fees per KB given") 1865 } 1866 1867 dontSignTx := req.DontSignTx 1868 1869 var csppServer string 1870 var mixedAccount uint32 1871 var mixedAccountBranch uint32 1872 var mixedSplitAccount uint32 1873 var changeAccount = req.ChangeAccount 1874 1875 if req.CsppServer != "" { 1876 csppServer = req.CsppServer 1877 mixedAccount = req.MixedAccount 1878 _, err = s.wallet.AccountName(ctx, mixedAccount) 1879 if err != nil { 1880 return nil, status.Errorf(codes.InvalidArgument, 1881 "CSPP Server set, but error on mixed account: %v", err) 1882 } 1883 mixedAccountBranch = req.MixedAccountBranch 1884 if mixedAccountBranch != 0 && mixedAccountBranch != 1 { 1885 return nil, status.Errorf(codes.InvalidArgument, 1886 "MixedAccountBranch should be 0 or 1.") 1887 } 1888 mixedSplitAccount = req.MixedSplitAccount 1889 _, err = s.wallet.AccountName(ctx, mixedSplitAccount) 1890 if err != nil { 1891 return nil, status.Errorf(codes.InvalidArgument, 1892 "CSPP Server set, but error on mixedSplitAccount: %v", err) 1893 } 1894 _, err = s.wallet.AccountName(ctx, changeAccount) 1895 if err != nil { 1896 return nil, status.Errorf(codes.InvalidArgument, 1897 "CSPP Server set, but error on changeAccount: %v", err) 1898 } 1899 } 1900 1901 request := &wallet.PurchaseTicketsRequest{ 1902 Count: numTickets, 1903 SourceAccount: req.Account, 1904 VotingAddress: ticketAddr, 1905 MinConf: minConf, 1906 Expiry: expiry, 1907 DontSignTx: dontSignTx, 1908 VSPAddress: poolAddr, 1909 VSPFees: poolFees, 1910 UseVotingAccount: req.UseVotingAccount, 1911 VotingAccount: req.VotingAccount, 1912 1913 // CSPP 1914 CSPPServer: csppServer, 1915 DialCSPPServer: s.dialCSPPServer, 1916 MixedAccount: mixedAccount, 1917 MixedAccountBranch: mixedAccountBranch, 1918 MixedSplitAccount: mixedSplitAccount, 1919 ChangeAccount: changeAccount, 1920 } 1921 1922 if vspClient != nil { 1923 request.VSPFeePaymentProcess = vspClient.Process 1924 request.VSPFeeProcess = vspClient.FeePercentage 1925 } 1926 1927 // If dontSignTx is false we unlock the wallet so we can sign the tx. 1928 if !dontSignTx && len(req.Passphrase) > 0 { 1929 lock := make(chan time.Time, 1) 1930 defer func() { 1931 lock <- time.Time{} // send matters, not the value 1932 }() 1933 err = s.wallet.Unlock(ctx, req.Passphrase, lock) 1934 if err != nil { 1935 return nil, translateError(err) 1936 } 1937 } 1938 1939 ticketsResponse, err := s.wallet.PurchaseTickets(ctx, n, request) 1940 if err != nil { 1941 return nil, err 1942 } 1943 ticketsTx := ticketsResponse.Tickets 1944 splitTx := ticketsResponse.SplitTx 1945 1946 unsignedTickets := make([][]byte, len(ticketsTx)) 1947 for i, mtx := range ticketsTx { 1948 var buf bytes.Buffer 1949 1950 err = mtx.Serialize(&buf) 1951 if err != nil { 1952 return nil, err 1953 } 1954 unsignedTickets[i] = buf.Bytes() 1955 } 1956 1957 var splitTxBuf bytes.Buffer 1958 splitTxBuf.Grow(splitTx.SerializeSize()) 1959 err = splitTx.Serialize(&splitTxBuf) 1960 if err != nil { 1961 return nil, status.Errorf(codes.Unknown, "Error Serializing split tx: %v", err) 1962 } 1963 splitTxBytes := splitTxBuf.Bytes() 1964 hashesBytes := marshalHashes(ticketsResponse.TicketHashes) 1965 1966 return &pb.PurchaseTicketsResponse{ 1967 TicketHashes: hashesBytes, 1968 Tickets: unsignedTickets, 1969 SplitTx: splitTxBytes, 1970 }, nil 1971 } 1972 1973 // deprecated 1974 func (s *walletServer) RevokeTickets(ctx context.Context, req *pb.RevokeTicketsRequest) (*pb.RevokeTicketsResponse, error) { 1975 return &pb.RevokeTicketsResponse{}, nil 1976 } 1977 1978 // deprecated 1979 func (s *walletServer) RevokeTicket(ctx context.Context, req *pb.RevokeTicketRequest) (*pb.RevokeTicketResponse, error) { 1980 return &pb.RevokeTicketResponse{}, nil 1981 } 1982 1983 func (s *walletServer) LoadActiveDataFilters(ctx context.Context, req *pb.LoadActiveDataFiltersRequest) ( 1984 *pb.LoadActiveDataFiltersResponse, error) { 1985 1986 n, err := s.requireNetworkBackend() 1987 if err != nil { 1988 return nil, err 1989 } 1990 1991 err = s.wallet.LoadActiveDataFilters(ctx, n, false) 1992 if err != nil { 1993 return nil, translateError(err) 1994 } 1995 1996 return &pb.LoadActiveDataFiltersResponse{}, nil 1997 } 1998 1999 func (s *walletServer) CommittedTickets(ctx context.Context, req *pb.CommittedTicketsRequest) ( 2000 *pb.CommittedTicketsResponse, error) { 2001 2002 // Translate [][]byte to []*chainhash.Hash 2003 in := make([]*chainhash.Hash, 0, len(req.Tickets)) 2004 for _, v := range req.Tickets { 2005 hash, err := chainhash.NewHash(v) 2006 if err != nil { 2007 return &pb.CommittedTicketsResponse{}, 2008 status.Error(codes.InvalidArgument, 2009 "invalid hash "+hex.EncodeToString(v)) 2010 } 2011 in = append(in, hash) 2012 } 2013 2014 // Figure out which tickets we own 2015 out, outAddr, err := s.wallet.CommittedTickets(ctx, in) 2016 if err != nil { 2017 return nil, translateError(err) 2018 } 2019 if len(out) != len(outAddr) { 2020 // Sanity check 2021 return nil, status.Error(codes.Internal, 2022 "impossible condition: ticket and address count unequal") 2023 } 2024 2025 // Translate []*chainhash.Hash to [][]byte 2026 ctr := &pb.CommittedTicketsResponse{ 2027 TicketAddresses: make([]*pb.CommittedTicketsResponse_TicketAddress, 2028 0, len(out)), 2029 } 2030 for k, v := range out { 2031 ctr.TicketAddresses = append(ctr.TicketAddresses, 2032 &pb.CommittedTicketsResponse_TicketAddress{ 2033 Ticket: v[:], 2034 Address: outAddr[k].String(), 2035 }) 2036 } 2037 2038 return ctr, nil 2039 } 2040 2041 func (s *walletServer) signMessage(ctx context.Context, address, message string) ([]byte, error) { 2042 addr, err := decodeAddress(address, s.wallet.ChainParams()) 2043 if err != nil { 2044 return nil, err 2045 } 2046 2047 // Addresses must have an associated secp256k1 private key and therefore 2048 // must be P2PK or P2PKH (P2SH is not allowed). 2049 var sig []byte 2050 switch addr.(type) { 2051 case *stdaddr.AddressPubKeyEcdsaSecp256k1V0: 2052 case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0: 2053 default: 2054 return nil, status.Error(codes.InvalidArgument, 2055 "address must be secp256k1 P2PK or P2PKH") 2056 } 2057 2058 sig, err = s.wallet.SignMessage(ctx, message, addr) 2059 if err != nil { 2060 return nil, translateError(err) 2061 } 2062 return sig, nil 2063 } 2064 2065 func (s *walletServer) SignMessage(ctx context.Context, req *pb.SignMessageRequest) (*pb.SignMessageResponse, error) { 2066 if len(req.Passphrase) > 0 { 2067 lock := make(chan time.Time, 1) 2068 defer func() { 2069 lock <- time.Time{} // send matters, not the value 2070 }() 2071 err := s.wallet.Unlock(ctx, req.Passphrase, lock) 2072 if err != nil { 2073 return nil, translateError(err) 2074 } 2075 } 2076 2077 sig, err := s.signMessage(ctx, req.Address, req.Message) 2078 if err != nil { 2079 return nil, err 2080 } 2081 2082 return &pb.SignMessageResponse{Signature: sig}, nil 2083 } 2084 2085 func (s *walletServer) SignMessages(ctx context.Context, req *pb.SignMessagesRequest) (*pb.SignMessagesResponse, error) { 2086 if len(req.Passphrase) > 0 { 2087 lock := make(chan time.Time, 1) 2088 defer func() { 2089 lock <- time.Time{} // send matters, not the value 2090 }() 2091 err := s.wallet.Unlock(ctx, req.Passphrase, lock) 2092 if err != nil { 2093 return nil, translateError(err) 2094 } 2095 } 2096 2097 smr := pb.SignMessagesResponse{ 2098 Replies: make([]*pb.SignMessagesResponse_SignReply, 0, 2099 len(req.Messages)), 2100 } 2101 for _, v := range req.Messages { 2102 e := "" 2103 sig, err := s.signMessage(ctx, v.Address, v.Message) 2104 if err != nil { 2105 e = err.Error() 2106 } 2107 smr.Replies = append(smr.Replies, 2108 &pb.SignMessagesResponse_SignReply{ 2109 Signature: sig, 2110 Error: e, 2111 }) 2112 } 2113 2114 return &smr, nil 2115 } 2116 2117 func (s *walletServer) ValidateAddress(ctx context.Context, req *pb.ValidateAddressRequest) (*pb.ValidateAddressResponse, error) { 2118 result := &pb.ValidateAddressResponse{} 2119 addr, err := decodeAddress(req.GetAddress(), s.wallet.ChainParams()) 2120 if err != nil { 2121 return result, nil 2122 } 2123 2124 result.IsValid = true 2125 2126 ver, scr := addr.PaymentScript() 2127 class, _ := stdscript.ExtractAddrs(ver, scr, s.wallet.ChainParams()) 2128 result.ScriptType = pb.ValidateAddressResponse_ScriptType(scProto(class)) 2129 result.PayToAddrScript = scr 2130 if pker, ok := addr.(stdaddr.SerializedPubKeyer); ok { 2131 result.PubKey = pker.SerializedPubKey() 2132 result.PubKeyAddr = addr.String() 2133 } 2134 if class == stdscript.STScriptHash { 2135 result.IsScript = true 2136 } 2137 2138 ka, err := s.wallet.KnownAddress(ctx, addr) 2139 if err != nil { 2140 if errors.Is(err, errors.NotExist) { 2141 // No additional information available about the address. 2142 return result, nil 2143 } 2144 return nil, err 2145 } 2146 acct, err := s.wallet.AccountNumber(ctx, ka.AccountName()) 2147 if err != nil { 2148 return nil, err 2149 } 2150 2151 // The address lookup was successful which means there is further 2152 // information about it available and it is "mine". 2153 result.IsMine = true 2154 result.AccountNumber = acct 2155 2156 switch ka := ka.(type) { 2157 case wallet.PubKeyHashAddress: 2158 result.PubKey = ka.PubKey() 2159 pubKeyAddr, err := stdaddr.NewAddressPubKeyEcdsaSecp256k1V0Raw(result.PubKey, 2160 s.wallet.ChainParams()) 2161 if err != nil { 2162 return nil, err 2163 } 2164 result.PubKeyAddr = pubKeyAddr.String() 2165 case wallet.P2SHAddress: 2166 version, redeem := ka.RedeemScript() 2167 class, addrs := stdscript.ExtractAddrs(version, redeem, s.wallet.ChainParams()) 2168 reqSigs := stdscript.DetermineRequiredSigs(version, redeem) 2169 addrStrings := make([]string, len(addrs)) 2170 for i, a := range addrs { 2171 addrStrings[i] = a.String() 2172 } 2173 result.PkScriptAddrs = addrStrings 2174 2175 // Multi-signature scripts also provide the number of required 2176 // signatures. 2177 result.ScriptType = pb.ValidateAddressResponse_ScriptType(scProto(class)) 2178 if class == stdscript.STMultiSig { 2179 result.SigsRequired = uint32(reqSigs) 2180 } 2181 } 2182 2183 switch ka := ka.(type) { 2184 case wallet.BIP0044Address: 2185 _, branch, child := ka.Path() 2186 result.IsInternal = branch == udb.InternalBranch 2187 result.Index = child 2188 } 2189 2190 return result, nil 2191 } 2192 2193 func (s *walletServer) Spender(ctx context.Context, req *pb.SpenderRequest) (*pb.SpenderResponse, error) { 2194 txHash, err := chainhash.NewHash(req.TransactionHash) 2195 if err != nil { 2196 return nil, status.Errorf(codes.InvalidArgument, "invalid transaction hash: %v", err) 2197 } 2198 out := wire.OutPoint{Hash: *txHash, Index: req.Index} 2199 2200 spender, spenderIndex, err := s.wallet.Spender(ctx, &out) 2201 if err != nil { 2202 if errors.Is(errors.NotExist, err) { 2203 return nil, status.Errorf(codes.NotFound, "output is unspent") 2204 } 2205 if errors.Is(errors.Invalid, err) { 2206 return nil, status.Errorf(codes.InvalidArgument, "output is not relevant to the wallet") 2207 } 2208 return nil, translateError(err) 2209 } 2210 2211 var buf bytes.Buffer 2212 buf.Grow(spender.SerializeSize()) 2213 err = spender.Serialize(&buf) 2214 if err != nil { 2215 return nil, translateError(err) 2216 } 2217 2218 resp := &pb.SpenderResponse{ 2219 SpenderTransaction: buf.Bytes(), 2220 InputIndex: spenderIndex, 2221 } 2222 return resp, nil 2223 } 2224 2225 func (s *walletServer) GetCFilters(req *pb.GetCFiltersRequest, server pb.WalletService_GetCFiltersServer) error { 2226 startBlock, endBlock, err := decodeBlockRange(req) 2227 if err != nil { 2228 return err 2229 } 2230 2231 ctx := server.Context() 2232 rangeFn := func(bh chainhash.Hash, key [gcs.KeySize]byte, cf *gcs.FilterV2) (bool, error) { 2233 resp := &pb.GetCFiltersResponse{ 2234 Key: key[:], 2235 Filter: cf.Bytes(), 2236 BlockHash: bh[:], 2237 } 2238 err := server.Send(resp) 2239 if err != nil { 2240 return true, err 2241 } 2242 2243 select { 2244 case <-ctx.Done(): 2245 return true, ctx.Err() 2246 default: 2247 return false, nil 2248 } 2249 } 2250 err = s.wallet.RangeCFiltersV2(ctx, startBlock, endBlock, rangeFn) 2251 if err != nil { 2252 return translateError(err) 2253 } 2254 2255 return nil 2256 } 2257 2258 func marshalTransactionInputs(v []wallet.TransactionSummaryInput) []*pb.TransactionDetails_Input { 2259 inputs := make([]*pb.TransactionDetails_Input, len(v)) 2260 for i := range v { 2261 input := &v[i] 2262 inputs[i] = &pb.TransactionDetails_Input{ 2263 Index: input.Index, 2264 PreviousAccount: input.PreviousAccount, 2265 PreviousAmount: int64(input.PreviousAmount), 2266 } 2267 } 2268 return inputs 2269 } 2270 2271 func marshalTransactionOutputs(v []wallet.TransactionSummaryOutput) []*pb.TransactionDetails_Output { 2272 outputs := make([]*pb.TransactionDetails_Output, len(v)) 2273 for i := range v { 2274 output := &v[i] 2275 address := "" 2276 if output.Address != nil { 2277 address = output.Address.String() 2278 } 2279 outputs[i] = &pb.TransactionDetails_Output{ 2280 Index: output.Index, 2281 Account: output.Account, 2282 Internal: output.Internal, 2283 Amount: int64(output.Amount), 2284 Address: address, 2285 OutputScript: output.OutputScript, 2286 } 2287 } 2288 return outputs 2289 } 2290 2291 func marshalTxType(walletTxType wallet.TransactionType) pb.TransactionDetails_TransactionType { 2292 switch walletTxType { 2293 case wallet.TransactionTypeCoinbase: 2294 return pb.TransactionDetails_COINBASE 2295 case wallet.TransactionTypeTicketPurchase: 2296 return pb.TransactionDetails_TICKET_PURCHASE 2297 case wallet.TransactionTypeVote: 2298 return pb.TransactionDetails_VOTE 2299 case wallet.TransactionTypeRevocation: 2300 return pb.TransactionDetails_REVOCATION 2301 default: 2302 return pb.TransactionDetails_REGULAR 2303 } 2304 } 2305 2306 func marshalTransactionDetails(tx *wallet.TransactionSummary) *pb.TransactionDetails { 2307 2308 return &pb.TransactionDetails{ 2309 Hash: tx.Hash[:], 2310 Transaction: tx.Transaction, 2311 Debits: marshalTransactionInputs(tx.MyInputs), 2312 Credits: marshalTransactionOutputs(tx.MyOutputs), 2313 Fee: int64(tx.Fee), 2314 Timestamp: tx.Timestamp, 2315 TransactionType: marshalTxType(tx.Type), 2316 } 2317 } 2318 2319 func marshalTransactionDetailsSlice(v []wallet.TransactionSummary) []*pb.TransactionDetails { 2320 txs := make([]*pb.TransactionDetails, len(v)) 2321 for i := range v { 2322 txs[i] = marshalTransactionDetails(&v[i]) 2323 } 2324 return txs 2325 } 2326 2327 func marshalTicketDetails(ticket *wallet.TicketSummary) *pb.GetTicketsResponse_TicketDetails { 2328 var ticketStatus = pb.GetTicketsResponse_TicketDetails_LIVE 2329 switch ticket.Status { 2330 case wallet.TicketStatusExpired: 2331 ticketStatus = pb.GetTicketsResponse_TicketDetails_EXPIRED 2332 case wallet.TicketStatusImmature: 2333 ticketStatus = pb.GetTicketsResponse_TicketDetails_IMMATURE 2334 case wallet.TicketStatusVoted: 2335 ticketStatus = pb.GetTicketsResponse_TicketDetails_VOTED 2336 case wallet.TicketStatusRevoked: 2337 ticketStatus = pb.GetTicketsResponse_TicketDetails_REVOKED 2338 case wallet.TicketStatusUnmined: 2339 ticketStatus = pb.GetTicketsResponse_TicketDetails_UNMINED 2340 case wallet.TicketStatusMissed: 2341 ticketStatus = pb.GetTicketsResponse_TicketDetails_MISSED 2342 case wallet.TicketStatusUnknown: 2343 ticketStatus = pb.GetTicketsResponse_TicketDetails_UNKNOWN 2344 } 2345 spender := &pb.TransactionDetails{} 2346 if ticket.Spender != nil { 2347 spender = marshalTransactionDetails(ticket.Spender) 2348 } 2349 return &pb.GetTicketsResponse_TicketDetails{ 2350 Ticket: marshalTransactionDetails(ticket.Ticket), 2351 Spender: spender, 2352 TicketStatus: ticketStatus, 2353 } 2354 } 2355 2356 func marshalGetTicketBlockDetails(v *wire.BlockHeader) *pb.GetTicketsResponse_BlockDetails { 2357 if v == nil { 2358 return nil 2359 } 2360 2361 blockHash := v.BlockHash() 2362 return &pb.GetTicketsResponse_BlockDetails{ 2363 Hash: blockHash[:], 2364 Height: int32(v.Height), 2365 Timestamp: v.Timestamp.Unix(), 2366 } 2367 } 2368 2369 func marshalBlock(v *wallet.Block) *pb.BlockDetails { 2370 txs := marshalTransactionDetailsSlice(v.Transactions) 2371 2372 if v.Header == nil { 2373 return &pb.BlockDetails{ 2374 Hash: nil, 2375 Height: -1, 2376 Transactions: txs, 2377 } 2378 } 2379 2380 hash := v.Header.BlockHash() 2381 return &pb.BlockDetails{ 2382 Hash: hash[:], 2383 Height: int32(v.Header.Height), 2384 Timestamp: v.Header.Timestamp.Unix(), 2385 ApprovesParent: v.Header.VoteBits&dcrutil.BlockValid != 0, 2386 Transactions: txs, 2387 PrevBlock: v.Header.PrevBlock[:], 2388 } 2389 } 2390 2391 func marshalBlocks(v []wallet.Block) []*pb.BlockDetails { 2392 blocks := make([]*pb.BlockDetails, len(v)) 2393 for i := range v { 2394 blocks[i] = marshalBlock(&v[i]) 2395 } 2396 return blocks 2397 } 2398 2399 func marshalDetachedBlock(v *wire.BlockHeader) *pb.DetachedBlockDetails { 2400 hash := v.BlockHash() 2401 return &pb.DetachedBlockDetails{ 2402 Hash: hash[:], 2403 Height: int32(v.Height), 2404 Timestamp: v.Timestamp.Unix(), 2405 PrevBlock: v.PrevBlock[:], 2406 } 2407 } 2408 2409 func marshalDetachedBlocks(v []*wire.BlockHeader) []*pb.DetachedBlockDetails { 2410 blocks := make([]*pb.DetachedBlockDetails, len(v)) 2411 for i := range v { 2412 blocks[i] = marshalDetachedBlock(v[i]) 2413 } 2414 return blocks 2415 } 2416 2417 func marshalHeaderHashes(v []*wire.BlockHeader) [][]byte { 2418 hashes := make([][]byte, len(v)) 2419 for i := range v { 2420 hash := v[i].BlockHash() 2421 hashes[i] = hash[:] 2422 } 2423 return hashes 2424 } 2425 2426 func marshalHashes(v []*chainhash.Hash) [][]byte { 2427 hashes := make([][]byte, len(v)) 2428 for i, hash := range v { 2429 hashes[i] = hash[:] 2430 } 2431 return hashes 2432 } 2433 2434 func (s *walletServer) TransactionNotifications(req *pb.TransactionNotificationsRequest, 2435 svr pb.WalletService_TransactionNotificationsServer) error { 2436 2437 n := s.wallet.NtfnServer.TransactionNotifications() 2438 defer n.Done() 2439 2440 ctxDone := svr.Context().Done() 2441 for { 2442 select { 2443 case v := <-n.C: 2444 resp := pb.TransactionNotificationsResponse{ 2445 AttachedBlocks: marshalBlocks(v.AttachedBlocks), 2446 DetachedBlocks: marshalHeaderHashes(v.DetachedBlocks), 2447 UnminedTransactions: marshalTransactionDetailsSlice(v.UnminedTransactions), 2448 UnminedTransactionHashes: marshalHashes(v.UnminedTransactionHashes), 2449 DetachedBlockHeaders: marshalDetachedBlocks(v.DetachedBlocks), 2450 } 2451 err := svr.Send(&resp) 2452 if err != nil { 2453 return translateError(err) 2454 } 2455 2456 case <-ctxDone: 2457 return nil 2458 } 2459 } 2460 } 2461 2462 func (s *walletServer) AccountNotifications(req *pb.AccountNotificationsRequest, 2463 svr pb.WalletService_AccountNotificationsServer) error { 2464 2465 n := s.wallet.NtfnServer.AccountNotifications() 2466 defer n.Done() 2467 2468 ctxDone := svr.Context().Done() 2469 for { 2470 select { 2471 case v := <-n.C: 2472 resp := pb.AccountNotificationsResponse{ 2473 AccountNumber: v.AccountNumber, 2474 AccountName: v.AccountName, 2475 ExternalKeyCount: v.ExternalKeyCount, 2476 InternalKeyCount: v.InternalKeyCount, 2477 ImportedKeyCount: v.ImportedKeyCount, 2478 } 2479 err := svr.Send(&resp) 2480 if err != nil { 2481 return translateError(err) 2482 } 2483 2484 case <-ctxDone: 2485 return nil 2486 } 2487 } 2488 } 2489 2490 func (s *walletServer) ConfirmationNotifications(svr pb.WalletService_ConfirmationNotificationsServer) error { 2491 c := s.wallet.NtfnServer.ConfirmationNotifications(svr.Context()) 2492 errOut := make(chan error, 2) 2493 go func() { 2494 for { 2495 req, err := svr.Recv() 2496 if err != nil { 2497 errOut <- err 2498 return 2499 } 2500 txHashes, err := decodeHashes(req.TxHashes) 2501 if err != nil { 2502 errOut <- err 2503 return 2504 } 2505 if req.StopAfter < 0 { 2506 errOut <- status.Errorf(codes.InvalidArgument, "stop_after must be non-negative") 2507 return 2508 } 2509 c.Watch(txHashes, req.StopAfter) 2510 } 2511 }() 2512 go func() { 2513 for { 2514 n, err := c.Recv() 2515 if err != nil { 2516 errOut <- err 2517 return 2518 } 2519 results := make([]*pb.ConfirmationNotificationsResponse_TransactionConfirmations, len(n)) 2520 for i, r := range n { 2521 var blockHash []byte 2522 if r.BlockHash != nil { 2523 blockHash = r.BlockHash[:] 2524 } 2525 results[i] = &pb.ConfirmationNotificationsResponse_TransactionConfirmations{ 2526 TxHash: r.TxHash[:], 2527 Confirmations: r.Confirmations, 2528 BlockHash: blockHash, 2529 BlockHeight: r.BlockHeight, 2530 } 2531 } 2532 r := &pb.ConfirmationNotificationsResponse{ 2533 Confirmations: results, 2534 } 2535 err = svr.Send(r) 2536 if err != nil { 2537 errOut <- err 2538 return 2539 } 2540 } 2541 }() 2542 2543 select { 2544 case <-svr.Context().Done(): 2545 return nil 2546 case err := <-errOut: 2547 if errors.Is(err, context.Canceled) { 2548 return nil 2549 } 2550 if _, ok := status.FromError(err); ok { 2551 return err 2552 } 2553 return translateError(err) 2554 } 2555 } 2556 2557 // StartWalletLoaderService starts the WalletLoaderService. 2558 func StartWalletLoaderService(server *grpc.Server, loader *loader.Loader, activeNet *netparams.Params) { 2559 loaderService.loader = loader 2560 loaderService.activeNet = activeNet 2561 if atomic.SwapUint32(&loaderService.ready, 1) != 0 { 2562 panic("service already started") 2563 } 2564 } 2565 2566 func (s *loaderServer) checkReady() bool { 2567 return atomic.LoadUint32(&s.ready) != 0 2568 } 2569 2570 // StartAccountMixerService starts the AccountMixerService. 2571 func StartAccountMixerService(server *grpc.Server, loader *loader.Loader) { 2572 accountMixerService.loader = loader 2573 if atomic.SwapUint32(&accountMixerService.ready, 1) != 0 { 2574 panic("service already started") 2575 } 2576 } 2577 2578 // RunAccountMixer starts the automatic account mixer for the service. 2579 func (t *accountMixerServer) RunAccountMixer(req *pb.RunAccountMixerRequest, svr pb.AccountMixerService_RunAccountMixerServer) error { 2580 wallet, ok := t.loader.LoadedWallet() 2581 if !ok { 2582 return status.Errorf(codes.FailedPrecondition, "Wallet has not been loaded") 2583 } 2584 2585 tb := ticketbuyer.New(wallet) 2586 2587 // Set ticketbuyerV2 config 2588 tb.AccessConfig(func(c *ticketbuyer.Config) { 2589 c.MixedAccountBranch = req.MixedAccountBranch 2590 c.MixedAccount = req.MixedAccount 2591 c.ChangeAccount = req.ChangeAccount 2592 c.CSPPServer = req.CsppServer 2593 c.DialCSPPServer = t.loader.DialCSPPServer 2594 c.BuyTickets = false 2595 c.MixChange = true 2596 }) 2597 2598 if len(req.Passphrase) > 0 { 2599 lock := make(chan time.Time, 1) 2600 2601 lockWallet := func() { 2602 lock <- time.Time{} 2603 zero(req.Passphrase) 2604 } 2605 2606 err := wallet.Unlock(svr.Context(), req.Passphrase, lock) 2607 if err != nil { 2608 return translateError(err) 2609 } 2610 defer lockWallet() 2611 } 2612 2613 err := tb.Run(svr.Context(), req.Passphrase) 2614 if err != nil { 2615 if svr.Context().Err() != nil { 2616 return status.Errorf(codes.Canceled, "AccountMixer instance canceled, account number: %v", req.MixedAccount) 2617 } 2618 return status.Errorf(codes.Unknown, "AccountMixer instance errored: %v", err) 2619 } 2620 2621 return nil 2622 } 2623 2624 func (t *accountMixerServer) checkReady() bool { 2625 return atomic.LoadUint32(&t.ready) != 0 2626 } 2627 2628 // StartTicketBuyerV2Service starts the TicketBuyerV2Service. 2629 func StartTicketBuyerV2Service(server *grpc.Server, loader *loader.Loader) { 2630 ticketBuyerV2Service.loader = loader 2631 if atomic.SwapUint32(&ticketBuyerV2Service.ready, 1) != 0 { 2632 panic("service already started") 2633 } 2634 } 2635 2636 // StartTicketBuyer starts the automatic ticket buyer for the v2 service. 2637 func (t *ticketbuyerV2Server) RunTicketBuyer(req *pb.RunTicketBuyerRequest, svr pb.TicketBuyerV2Service_RunTicketBuyerServer) error { 2638 wallet, ok := t.loader.LoadedWallet() 2639 if !ok { 2640 return status.Errorf(codes.FailedPrecondition, "Wallet has not been loaded") 2641 } 2642 params := wallet.ChainParams() 2643 2644 ctx := svr.Context() 2645 // Legacy vsp request. After stopping supporting the old vsp version, this 2646 // code can be removed. 2647 // Confirm validity of provided voting addresses and pool addresses. 2648 var votingAddress stdaddr.StakeAddress 2649 var err error 2650 if req.VotingAddress != "" { 2651 votingAddress, err = decodeStakeAddress(req.VotingAddress, params) 2652 if err != nil { 2653 return err 2654 } 2655 } 2656 var poolAddress stdaddr.StakeAddress 2657 if req.PoolAddress != "" { 2658 if req.VspHost != "" || req.VspPubkey != "" { 2659 return status.Errorf(codes.InvalidArgument, 2660 "request contains both legacy stakepoold and vspd options.") 2661 } 2662 poolAddress, err = decodeStakeAddress(req.PoolAddress, params) 2663 if err != nil { 2664 return err 2665 } 2666 } 2667 2668 // new vspd request 2669 var vspHost string 2670 var vspPubKey string 2671 var vspClient *vsp.Client 2672 if req.VspHost != "" || req.VspPubkey != "" { 2673 vspHost = req.VspHost 2674 vspPubKey = req.VspPubkey 2675 if vspPubKey == "" { 2676 return status.Errorf(codes.InvalidArgument, "vsp pubkey can not be null") 2677 } 2678 if vspHost == "" { 2679 return status.Errorf(codes.InvalidArgument, "vsp host can not be null") 2680 } 2681 cfg := vsp.Config{ 2682 URL: vspHost, 2683 PubKey: vspPubKey, 2684 Dialer: nil, 2685 Wallet: wallet, 2686 Policy: vsp.Policy{ 2687 MaxFee: 0.1e8, 2688 FeeAcct: req.Account, 2689 ChangeAcct: req.Account, 2690 }, 2691 } 2692 vspClient, err = loader.VSP(cfg) 2693 if err != nil { 2694 return status.Errorf(codes.Unknown, "TicketBuyerV3 instance failed to start. Error: %v", err) 2695 } 2696 } 2697 if req.BalanceToMaintain < 0 { 2698 return status.Errorf(codes.InvalidArgument, "Negative balance to maintain given") 2699 } 2700 2701 var csppServer string 2702 var mixedAccount uint32 2703 var mixedAccountBranch uint32 2704 var mixedSplitAccount uint32 2705 var changeAccount = req.ChangeAccount 2706 var mixedChange = false 2707 2708 if req.CsppServer != "" { 2709 mixedChange = true 2710 csppServer = req.CsppServer 2711 mixedAccount = req.MixedAccount 2712 _, err = wallet.AccountName(ctx, mixedAccount) 2713 if err != nil { 2714 return status.Errorf(codes.InvalidArgument, 2715 "CSPP Server set, but error on mixed account: %v", err) 2716 } 2717 mixedAccountBranch = req.MixedAccountBranch 2718 if mixedAccountBranch != 0 && mixedAccountBranch != 1 { 2719 return status.Errorf(codes.InvalidArgument, 2720 "MixedAccountBranch should be 0 or 1.") 2721 } 2722 mixedSplitAccount = req.MixedSplitAccount 2723 _, err = wallet.AccountName(ctx, mixedSplitAccount) 2724 if err != nil { 2725 return status.Errorf(codes.InvalidArgument, 2726 "CSPP Server set, but error on mixedSplitAccount: %v", err) 2727 } 2728 _, err = wallet.AccountName(ctx, changeAccount) 2729 if err != nil { 2730 return status.Errorf(codes.InvalidArgument, 2731 "CSPP Server set, but error on changeAccount: %v", err) 2732 } 2733 } 2734 2735 // set limit. If it is not informed by the request it is used 0, which 2736 // is defaulted to 20. 2737 limit := int(req.Limit) 2738 2739 tb := ticketbuyer.New(wallet) 2740 // Set ticketbuyerV2 config 2741 tb.AccessConfig(func(c *ticketbuyer.Config) { 2742 c.BuyTickets = true 2743 c.Account = req.Account 2744 c.VotingAccount = req.VotingAccount 2745 c.Maintain = dcrutil.Amount(req.BalanceToMaintain) 2746 c.VotingAddr = votingAddress 2747 c.PoolFeeAddr = poolAddress 2748 c.PoolFees = req.PoolFees 2749 c.VSP = vspClient 2750 c.MixedAccount = mixedAccount 2751 c.MixChange = mixedChange 2752 c.CSPPServer = csppServer 2753 c.DialCSPPServer = t.loader.DialCSPPServer 2754 c.ChangeAccount = changeAccount 2755 c.MixedAccountBranch = mixedAccountBranch 2756 c.TicketSplitAccount = mixedSplitAccount 2757 c.Limit = limit 2758 }) 2759 2760 if len(req.Passphrase) > 0 { 2761 lock := make(chan time.Time, 1) 2762 2763 lockWallet := func() { 2764 lock <- time.Time{} 2765 zero(req.Passphrase) 2766 } 2767 2768 err = wallet.Unlock(svr.Context(), req.Passphrase, lock) 2769 if err != nil { 2770 return translateError(err) 2771 } 2772 defer lockWallet() 2773 } 2774 2775 err = tb.Run(svr.Context(), req.Passphrase) 2776 if err != nil { 2777 if svr.Context().Err() != nil { 2778 return status.Errorf(codes.Canceled, "TicketBuyerV2 instance canceled, account number: %v", req.Account) 2779 } 2780 return status.Errorf(codes.Unknown, "TicketBuyerV2 instance errored: %v", err) 2781 } 2782 2783 return nil 2784 } 2785 2786 func (t *ticketbuyerV2Server) checkReady() bool { 2787 return atomic.LoadUint32(&t.ready) != 0 2788 } 2789 2790 func (s *loaderServer) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest) ( 2791 *pb.CreateWalletResponse, error) { 2792 2793 defer func() { 2794 zero(req.PrivatePassphrase) 2795 zero(req.Seed) 2796 }() 2797 2798 // Use an insecure public passphrase when the request's is empty. 2799 pubPassphrase := req.PublicPassphrase 2800 if len(pubPassphrase) == 0 { 2801 pubPassphrase = []byte(wallet.InsecurePubPassphrase) 2802 } 2803 2804 // Seed is required. 2805 if len(req.Seed) == 0 { 2806 return nil, status.Errorf(codes.InvalidArgument, "seed is a required parameter") 2807 } 2808 2809 _, err := s.loader.CreateNewWallet(ctx, pubPassphrase, req.PrivatePassphrase, req.Seed) 2810 if err != nil { 2811 return nil, translateError(err) 2812 } 2813 2814 return &pb.CreateWalletResponse{}, nil 2815 } 2816 2817 func (s *loaderServer) CreateWatchingOnlyWallet(ctx context.Context, req *pb.CreateWatchingOnlyWalletRequest) ( 2818 *pb.CreateWatchingOnlyWalletResponse, error) { 2819 2820 // Use an insecure public passphrase when the request's is empty. 2821 pubPassphrase := req.PublicPassphrase 2822 if len(pubPassphrase) == 0 { 2823 pubPassphrase = []byte(wallet.InsecurePubPassphrase) 2824 } 2825 2826 _, err := s.loader.CreateWatchingOnlyWallet(ctx, req.ExtendedPubKey, pubPassphrase) 2827 if err != nil { 2828 return nil, translateError(err) 2829 } 2830 2831 return &pb.CreateWatchingOnlyWalletResponse{}, nil 2832 } 2833 2834 func (s *loaderServer) OpenWallet(ctx context.Context, req *pb.OpenWalletRequest) ( 2835 *pb.OpenWalletResponse, error) { 2836 2837 // Use an insecure public passphrase when the request's is empty. 2838 pubPassphrase := req.PublicPassphrase 2839 if len(pubPassphrase) == 0 { 2840 pubPassphrase = []byte(wallet.InsecurePubPassphrase) 2841 } 2842 2843 w, err := s.loader.OpenExistingWallet(ctx, pubPassphrase) 2844 if err != nil { 2845 return nil, translateError(err) 2846 } 2847 2848 return &pb.OpenWalletResponse{ 2849 WatchingOnly: w.WatchingOnly(), 2850 }, nil 2851 } 2852 2853 func (s *loaderServer) WalletExists(ctx context.Context, req *pb.WalletExistsRequest) ( 2854 *pb.WalletExistsResponse, error) { 2855 2856 exists, err := s.loader.WalletExists() 2857 if err != nil { 2858 return nil, translateError(err) 2859 } 2860 return &pb.WalletExistsResponse{Exists: exists}, nil 2861 } 2862 2863 func (s *loaderServer) CloseWallet(ctx context.Context, req *pb.CloseWalletRequest) ( 2864 *pb.CloseWalletResponse, error) { 2865 2866 err := s.loader.UnloadWallet() 2867 if errors.Is(err, errors.Invalid) { 2868 return nil, status.Errorf(codes.FailedPrecondition, "Wallet is not loaded") 2869 } 2870 if err != nil { 2871 return nil, translateError(err) 2872 } 2873 2874 return &pb.CloseWalletResponse{}, nil 2875 } 2876 2877 func isLoopback(addr string) bool { 2878 host, _, err := net.SplitHostPort(addr) 2879 if err == nil { 2880 addr = host 2881 } 2882 if addr == "localhost" { 2883 return true 2884 } 2885 ip := net.ParseIP(addr) 2886 if ip == nil { 2887 return false 2888 } 2889 return ip.IsLoopback() 2890 } 2891 2892 func (s *loaderServer) RpcSync(req *pb.RpcSyncRequest, svr pb.WalletLoaderService_RpcSyncServer) error { 2893 defer zero(req.Password) 2894 2895 // Error if the wallet is already syncing with the network. 2896 wallet, walletLoaded := s.loader.LoadedWallet() 2897 if walletLoaded { 2898 _, err := wallet.NetworkBackend() 2899 if err == nil { 2900 return status.Errorf(codes.FailedPrecondition, "wallet is loaded and already synchronizing") 2901 } 2902 } 2903 2904 if req.DiscoverAccounts && len(req.PrivatePassphrase) == 0 { 2905 return status.Errorf(codes.InvalidArgument, "private passphrase is required for discovering accounts") 2906 } 2907 var lockWallet func() 2908 if req.DiscoverAccounts { 2909 lock := make(chan time.Time, 1) 2910 lockWallet = func() { 2911 lock <- time.Time{} 2912 zero(req.PrivatePassphrase) 2913 } 2914 defer lockWallet() 2915 err := wallet.Unlock(svr.Context(), req.PrivatePassphrase, lock) 2916 if err != nil { 2917 return translateError(err) 2918 } 2919 } 2920 2921 syncer := chain.NewSyncer(wallet, &chain.RPCOptions{ 2922 Address: req.NetworkAddress, 2923 DefaultPort: s.activeNet.JSONRPCClientPort, 2924 User: req.Username, 2925 Pass: string(req.Password), 2926 CA: req.Certificate, 2927 Insecure: isLoopback(req.NetworkAddress) && len(req.Certificate) == 0, 2928 }) 2929 2930 cbs := &chain.Callbacks{ 2931 Synced: func(sync bool) { 2932 resp := &pb.RpcSyncResponse{} 2933 resp.Synced = sync 2934 if sync { 2935 resp.NotificationType = pb.SyncNotificationType_SYNCED 2936 } else { 2937 resp.NotificationType = pb.SyncNotificationType_UNSYNCED 2938 } 2939 _ = svr.Send(resp) 2940 }, 2941 FetchMissingCFiltersStarted: func() { 2942 resp := &pb.RpcSyncResponse{ 2943 NotificationType: pb.SyncNotificationType_FETCHED_MISSING_CFILTERS_STARTED, 2944 } 2945 _ = svr.Send(resp) 2946 }, 2947 FetchMissingCFiltersProgress: func(missingCFitlersStart, missingCFitlersEnd int32) { 2948 resp := &pb.RpcSyncResponse{ 2949 NotificationType: pb.SyncNotificationType_FETCHED_MISSING_CFILTERS_PROGRESS, 2950 FetchMissingCfilters: &pb.FetchMissingCFiltersNotification{ 2951 FetchedCfiltersStartHeight: missingCFitlersStart, 2952 FetchedCfiltersEndHeight: missingCFitlersEnd, 2953 }, 2954 } 2955 _ = svr.Send(resp) 2956 }, 2957 FetchMissingCFiltersFinished: func() { 2958 resp := &pb.RpcSyncResponse{ 2959 NotificationType: pb.SyncNotificationType_FETCHED_MISSING_CFILTERS_FINISHED, 2960 } 2961 _ = svr.Send(resp) 2962 }, 2963 FetchHeadersStarted: func() { 2964 resp := &pb.RpcSyncResponse{ 2965 NotificationType: pb.SyncNotificationType_FETCHED_HEADERS_STARTED, 2966 } 2967 _ = svr.Send(resp) 2968 }, 2969 FetchHeadersProgress: func(fetchedHeadersCount int32, lastHeaderTime int64) { 2970 resp := &pb.RpcSyncResponse{ 2971 NotificationType: pb.SyncNotificationType_FETCHED_HEADERS_PROGRESS, 2972 FetchHeaders: &pb.FetchHeadersNotification{ 2973 FetchedHeadersCount: fetchedHeadersCount, 2974 LastHeaderTime: lastHeaderTime, 2975 }, 2976 } 2977 _ = svr.Send(resp) 2978 }, 2979 FetchHeadersFinished: func() { 2980 resp := &pb.RpcSyncResponse{ 2981 NotificationType: pb.SyncNotificationType_FETCHED_HEADERS_FINISHED, 2982 } 2983 _ = svr.Send(resp) 2984 }, 2985 DiscoverAddressesStarted: func() { 2986 resp := &pb.RpcSyncResponse{ 2987 NotificationType: pb.SyncNotificationType_DISCOVER_ADDRESSES_STARTED, 2988 } 2989 _ = svr.Send(resp) 2990 }, 2991 DiscoverAddressesFinished: func() { 2992 resp := &pb.RpcSyncResponse{ 2993 NotificationType: pb.SyncNotificationType_DISCOVER_ADDRESSES_FINISHED, 2994 } 2995 2996 // Lock the wallet after the first time discovered while also 2997 // discovering accounts. 2998 if lockWallet != nil { 2999 lockWallet() 3000 lockWallet = nil 3001 } 3002 _ = svr.Send(resp) 3003 }, 3004 RescanStarted: func() { 3005 resp := &pb.RpcSyncResponse{ 3006 NotificationType: pb.SyncNotificationType_RESCAN_STARTED, 3007 } 3008 _ = svr.Send(resp) 3009 }, 3010 RescanProgress: func(rescannedThrough int32) { 3011 resp := &pb.RpcSyncResponse{ 3012 NotificationType: pb.SyncNotificationType_RESCAN_PROGRESS, 3013 RescanProgress: &pb.RescanProgressNotification{ 3014 RescannedThrough: rescannedThrough, 3015 }, 3016 } 3017 _ = svr.Send(resp) 3018 }, 3019 RescanFinished: func() { 3020 resp := &pb.RpcSyncResponse{ 3021 NotificationType: pb.SyncNotificationType_RESCAN_FINISHED, 3022 } 3023 _ = svr.Send(resp) 3024 }, 3025 } 3026 syncer.SetCallbacks(cbs) 3027 3028 // Synchronize until error or RPC cancelation. 3029 err := syncer.Run(svr.Context()) 3030 if err != nil { 3031 if svr.Context().Err() != nil { 3032 return status.Errorf(codes.Canceled, "Wallet synchronization canceled: %v", err) 3033 } 3034 return status.Errorf(codes.Unknown, "Wallet synchronization stopped: %v", err) 3035 } 3036 3037 return nil 3038 } 3039 3040 func (s *loaderServer) SpvSync(req *pb.SpvSyncRequest, svr pb.WalletLoaderService_SpvSyncServer) error { 3041 wallet, ok := s.loader.LoadedWallet() 3042 if !ok { 3043 return status.Errorf(codes.FailedPrecondition, "Wallet has not been loaded") 3044 } 3045 3046 if req.DiscoverAccounts && len(req.PrivatePassphrase) == 0 { 3047 return status.Errorf(codes.InvalidArgument, "private passphrase is required for discovering accounts") 3048 } 3049 var lockWallet func() 3050 if req.DiscoverAccounts { 3051 lock := make(chan time.Time, 1) 3052 lockWallet = func() { 3053 lock <- time.Time{} 3054 zero(req.PrivatePassphrase) 3055 } 3056 defer lockWallet() 3057 err := wallet.Unlock(svr.Context(), req.PrivatePassphrase, lock) 3058 if err != nil { 3059 return translateError(err) 3060 } 3061 } 3062 addr := &net.TCPAddr{IP: net.ParseIP("::1"), Port: 0} 3063 amgr := addrmgr.New(s.loader.DbDirPath(), net.LookupIP) // TODO: be mindful of tor 3064 lp := p2p.NewLocalPeer(wallet.ChainParams(), addr, amgr) 3065 3066 ntfns := &spv.Notifications{ 3067 Synced: func(sync bool) { 3068 resp := &pb.SpvSyncResponse{} 3069 resp.Synced = sync 3070 if sync { 3071 resp.NotificationType = pb.SyncNotificationType_SYNCED 3072 } else { 3073 resp.NotificationType = pb.SyncNotificationType_UNSYNCED 3074 } 3075 _ = svr.Send(resp) 3076 }, 3077 PeerConnected: func(peerCount int32, addr string) { 3078 resp := &pb.SpvSyncResponse{ 3079 NotificationType: pb.SyncNotificationType_PEER_CONNECTED, 3080 PeerInformation: &pb.PeerNotification{ 3081 PeerCount: peerCount, 3082 Address: addr, 3083 }, 3084 } 3085 _ = svr.Send(resp) 3086 }, 3087 PeerDisconnected: func(peerCount int32, addr string) { 3088 resp := &pb.SpvSyncResponse{ 3089 NotificationType: pb.SyncNotificationType_PEER_DISCONNECTED, 3090 PeerInformation: &pb.PeerNotification{ 3091 PeerCount: peerCount, 3092 Address: addr, 3093 }, 3094 } 3095 _ = svr.Send(resp) 3096 }, 3097 FetchMissingCFiltersStarted: func() { 3098 resp := &pb.SpvSyncResponse{ 3099 NotificationType: pb.SyncNotificationType_FETCHED_MISSING_CFILTERS_STARTED, 3100 } 3101 _ = svr.Send(resp) 3102 }, 3103 FetchMissingCFiltersProgress: func(missingCFitlersStart, missingCFitlersEnd int32) { 3104 resp := &pb.SpvSyncResponse{ 3105 NotificationType: pb.SyncNotificationType_FETCHED_MISSING_CFILTERS_PROGRESS, 3106 FetchMissingCfilters: &pb.FetchMissingCFiltersNotification{ 3107 FetchedCfiltersStartHeight: missingCFitlersStart, 3108 FetchedCfiltersEndHeight: missingCFitlersEnd, 3109 }, 3110 } 3111 _ = svr.Send(resp) 3112 }, 3113 FetchMissingCFiltersFinished: func() { 3114 resp := &pb.SpvSyncResponse{ 3115 NotificationType: pb.SyncNotificationType_FETCHED_MISSING_CFILTERS_FINISHED, 3116 } 3117 _ = svr.Send(resp) 3118 }, 3119 FetchHeadersStarted: func() { 3120 resp := &pb.SpvSyncResponse{ 3121 NotificationType: pb.SyncNotificationType_FETCHED_HEADERS_STARTED, 3122 } 3123 _ = svr.Send(resp) 3124 }, 3125 FetchHeadersProgress: func(fetchedHeadersCount int32, lastHeaderTime int64) { 3126 resp := &pb.SpvSyncResponse{ 3127 NotificationType: pb.SyncNotificationType_FETCHED_HEADERS_PROGRESS, 3128 FetchHeaders: &pb.FetchHeadersNotification{ 3129 FetchedHeadersCount: fetchedHeadersCount, 3130 LastHeaderTime: lastHeaderTime, 3131 }, 3132 } 3133 _ = svr.Send(resp) 3134 }, 3135 FetchHeadersFinished: func() { 3136 resp := &pb.SpvSyncResponse{ 3137 NotificationType: pb.SyncNotificationType_FETCHED_HEADERS_FINISHED, 3138 } 3139 _ = svr.Send(resp) 3140 }, 3141 DiscoverAddressesStarted: func() { 3142 resp := &pb.SpvSyncResponse{ 3143 NotificationType: pb.SyncNotificationType_DISCOVER_ADDRESSES_STARTED, 3144 } 3145 _ = svr.Send(resp) 3146 }, 3147 DiscoverAddressesFinished: func() { 3148 resp := &pb.SpvSyncResponse{ 3149 NotificationType: pb.SyncNotificationType_DISCOVER_ADDRESSES_FINISHED, 3150 } 3151 3152 // Lock the wallet after the first time discovered while also 3153 // discovering accounts. 3154 if lockWallet != nil { 3155 lockWallet() 3156 lockWallet = nil 3157 } 3158 _ = svr.Send(resp) 3159 }, 3160 RescanStarted: func() { 3161 resp := &pb.SpvSyncResponse{ 3162 NotificationType: pb.SyncNotificationType_RESCAN_STARTED, 3163 } 3164 _ = svr.Send(resp) 3165 }, 3166 RescanProgress: func(rescannedThrough int32) { 3167 resp := &pb.SpvSyncResponse{ 3168 NotificationType: pb.SyncNotificationType_RESCAN_PROGRESS, 3169 RescanProgress: &pb.RescanProgressNotification{ 3170 RescannedThrough: rescannedThrough, 3171 }, 3172 } 3173 _ = svr.Send(resp) 3174 }, 3175 RescanFinished: func() { 3176 resp := &pb.SpvSyncResponse{ 3177 NotificationType: pb.SyncNotificationType_RESCAN_FINISHED, 3178 } 3179 _ = svr.Send(resp) 3180 }, 3181 } 3182 syncer := spv.NewSyncer(wallet, lp) 3183 syncer.SetNotifications(ntfns) 3184 if len(req.SpvConnect) > 0 { 3185 spvConnects := make([]string, len(req.SpvConnect)) 3186 for i := 0; i < len(req.SpvConnect); i++ { 3187 spvConnect, err := cfgutil.NormalizeAddress(req.SpvConnect[i], s.activeNet.Params.DefaultPort) 3188 if err != nil { 3189 return status.Errorf(codes.FailedPrecondition, "SPV Connect address invalid: %v", err) 3190 } 3191 spvConnects[i] = spvConnect 3192 } 3193 syncer.SetPersistentPeers(spvConnects) 3194 } 3195 3196 err := syncer.Run(svr.Context()) 3197 if err != nil { 3198 if errors.Is(err, context.Canceled) { 3199 return status.Errorf(codes.Canceled, "SPV synchronization canceled: %v", err) 3200 } else if errors.Is(err, context.DeadlineExceeded) { 3201 return status.Errorf(codes.DeadlineExceeded, "SPV synchronization deadline exceeded: %v", err) 3202 } 3203 return translateError(err) 3204 } 3205 return nil 3206 } 3207 3208 func (s *loaderServer) RescanPoint(ctx context.Context, req *pb.RescanPointRequest) (*pb.RescanPointResponse, error) { 3209 wallet, ok := s.loader.LoadedWallet() 3210 if !ok { 3211 return nil, status.Errorf(codes.FailedPrecondition, "Wallet has not been loaded") 3212 } 3213 rescanPoint, err := wallet.RescanPoint(ctx) 3214 if err != nil { 3215 return nil, status.Errorf(codes.FailedPrecondition, "Rescan point failed to be requested %v", err) 3216 } 3217 if rescanPoint != nil { 3218 return &pb.RescanPointResponse{ 3219 RescanPointHash: rescanPoint[:], 3220 }, nil 3221 } 3222 return &pb.RescanPointResponse{RescanPointHash: nil}, nil 3223 } 3224 3225 func (s *seedServer) GenerateRandomSeed(ctx context.Context, req *pb.GenerateRandomSeedRequest) ( 3226 *pb.GenerateRandomSeedResponse, error) { 3227 3228 seedSize := req.SeedLength 3229 if seedSize == 0 { 3230 seedSize = hdkeychain.RecommendedSeedLen 3231 } 3232 if seedSize < hdkeychain.MinSeedBytes || seedSize > hdkeychain.MaxSeedBytes { 3233 return nil, status.Errorf(codes.InvalidArgument, "invalid seed length") 3234 } 3235 3236 seed := make([]byte, seedSize) 3237 _, err := rand.Read(seed) 3238 if err != nil { 3239 return nil, status.Errorf(codes.Unavailable, "failed to read cryptographically-random data for seed: %v", err) 3240 } 3241 3242 res := &pb.GenerateRandomSeedResponse{ 3243 SeedBytes: seed, 3244 SeedHex: hex.EncodeToString(seed), 3245 SeedMnemonic: walletseed.EncodeMnemonic(seed), 3246 } 3247 return res, nil 3248 } 3249 3250 func (s *seedServer) DecodeSeed(ctx context.Context, req *pb.DecodeSeedRequest) (*pb.DecodeSeedResponse, error) { 3251 seed, err := walletseed.DecodeUserInput(req.UserInput) 3252 if err != nil { 3253 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3254 } 3255 return &pb.DecodeSeedResponse{DecodedSeed: seed}, nil 3256 } 3257 3258 func StartAgendaService(server *grpc.Server, activeNet *chaincfg.Params) { 3259 agendaService.activeNet = activeNet 3260 if atomic.SwapUint32(&agendaService.ready, 1) != 0 { 3261 panic("service already started") 3262 } 3263 } 3264 3265 func (s *agendaServer) checkReady() bool { 3266 return atomic.LoadUint32(&s.ready) != 0 3267 } 3268 3269 func (s *agendaServer) Agendas(ctx context.Context, req *pb.AgendasRequest) (*pb.AgendasResponse, error) { 3270 version, deployments := wallet.CurrentAgendas(s.activeNet) 3271 resp := &pb.AgendasResponse{ 3272 Version: version, 3273 Agendas: make([]*pb.AgendasResponse_Agenda, len(deployments)), 3274 } 3275 for i := range deployments { 3276 d := &deployments[i] 3277 resp.Agendas[i] = &pb.AgendasResponse_Agenda{ 3278 Id: d.Vote.Id, 3279 Description: d.Vote.Description, 3280 Mask: uint32(d.Vote.Mask), 3281 Choices: make([]*pb.AgendasResponse_Choice, len(d.Vote.Choices)), 3282 StartTime: int64(d.StartTime), 3283 ExpireTime: int64(d.ExpireTime), 3284 } 3285 for j := range d.Vote.Choices { 3286 choice := &d.Vote.Choices[j] 3287 resp.Agendas[i].Choices[j] = &pb.AgendasResponse_Choice{ 3288 Id: choice.Id, 3289 Description: choice.Description, 3290 Bits: uint32(choice.Bits), 3291 IsAbstain: choice.IsAbstain, 3292 IsNo: choice.IsNo, 3293 } 3294 } 3295 } 3296 return resp, nil 3297 } 3298 3299 // StartVotingService starts the VotingService. 3300 func StartVotingService(server *grpc.Server, wallet *wallet.Wallet) { 3301 votingService.wallet = wallet 3302 if atomic.SwapUint32(&votingService.ready, 1) != 0 { 3303 panic("service already started") 3304 } 3305 } 3306 3307 func (s *votingServer) checkReady() bool { 3308 return atomic.LoadUint32(&s.ready) != 0 3309 } 3310 3311 func (s *votingServer) VoteChoices(ctx context.Context, req *pb.VoteChoicesRequest) (*pb.VoteChoicesResponse, error) { 3312 var ticketHash *chainhash.Hash 3313 var err error 3314 if len(req.TicketHash) != 0 { 3315 ticketHash, err = chainhash.NewHash(req.TicketHash) 3316 if err != nil { 3317 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3318 } 3319 } 3320 version, agendas := wallet.CurrentAgendas(s.wallet.ChainParams()) 3321 choices, voteBits, err := s.wallet.AgendaChoices(ctx, ticketHash) 3322 if err != nil { 3323 return nil, translateError(err) 3324 } 3325 resp := &pb.VoteChoicesResponse{ 3326 Version: version, 3327 Choices: make([]*pb.VoteChoicesResponse_Choice, len(agendas)), 3328 Votebits: uint32(voteBits), 3329 } 3330 3331 for i := range choices { 3332 resp.Choices[i] = &pb.VoteChoicesResponse_Choice{ 3333 AgendaId: choices[i].AgendaID, 3334 AgendaDescription: agendas[i].Vote.Description, 3335 ChoiceId: choices[i].ChoiceID, 3336 ChoiceDescription: "", // Set below 3337 } 3338 for j := range agendas[i].Vote.Choices { 3339 if choices[i].ChoiceID == agendas[i].Vote.Choices[j].Id { 3340 resp.Choices[i].ChoiceDescription = agendas[i].Vote.Choices[j].Description 3341 break 3342 } 3343 } 3344 } 3345 return resp, nil 3346 } 3347 3348 func (s *votingServer) SetVoteChoices(ctx context.Context, req *pb.SetVoteChoicesRequest) (*pb.SetVoteChoicesResponse, error) { 3349 var ticketHash *chainhash.Hash 3350 var err error 3351 if len(req.TicketHash) != 0 { 3352 ticketHash, err = chainhash.NewHash(req.TicketHash) 3353 if err != nil { 3354 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3355 } 3356 } 3357 choices := make(wallet.AgendaChoices, len(req.Choices)) 3358 for i, c := range req.Choices { 3359 choices[i] = wallet.AgendaChoice{ 3360 AgendaID: c.AgendaId, 3361 ChoiceID: c.ChoiceId, 3362 } 3363 } 3364 voteBits, err := s.wallet.SetAgendaChoices(ctx, ticketHash, choices...) 3365 if err != nil { 3366 return nil, translateError(err) 3367 } 3368 resp := &pb.SetVoteChoicesResponse{ 3369 Votebits: uint32(voteBits), 3370 } 3371 return resp, nil 3372 } 3373 3374 // TSpendPolicies returns voting policies for particular treasury spends 3375 // transactions. If a tspend transaction hash is specified, that policy is 3376 // returned; otherwise the policies for all known tspends are returned in an 3377 // array. If both a tspend transaction hash and a ticket hash are provided, 3378 // the per-ticket tspend policy is returned. 3379 func (s *votingServer) TSpendPolicies(ctx context.Context, req *pb.TSpendPoliciesRequest) (*pb.TSpendPoliciesResponse, error) { 3380 var ticketHash *chainhash.Hash 3381 var hash *chainhash.Hash 3382 var err error 3383 if len(req.TicketHash) != 0 { 3384 ticketHash, err = chainhash.NewHash(req.TicketHash) 3385 if err != nil { 3386 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3387 } 3388 } 3389 3390 if len(req.Hash) != 0 { 3391 hash, err = chainhash.NewHash(req.Hash) 3392 if err != nil { 3393 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3394 } 3395 var policy string 3396 switch s.wallet.TSpendPolicy(hash, ticketHash) { 3397 case stake.TreasuryVoteYes: 3398 policy = "yes" 3399 case stake.TreasuryVoteNo: 3400 policy = "no" 3401 default: 3402 policy = "abstain" 3403 } 3404 resp := &pb.TSpendPoliciesResponse{ 3405 Policies: make([]*pb.TSpendPoliciesResponse_Policy, 0, 1), 3406 } 3407 r := &pb.TSpendPoliciesResponse_Policy{ 3408 Hash: req.Hash, 3409 Policy: policy, 3410 TicketHash: []byte{}, 3411 } 3412 if req.TicketHash != nil { 3413 r.TicketHash = req.TicketHash 3414 } 3415 resp.Policies = append(resp.Policies, r) 3416 return resp, nil 3417 } 3418 3419 tspends := s.wallet.GetAllTSpends(ctx) 3420 resp := &pb.TSpendPoliciesResponse{ 3421 Policies: make([]*pb.TSpendPoliciesResponse_Policy, 0, len(tspends)), 3422 } 3423 for i := range tspends { 3424 tspendHash := tspends[i].TxHash() 3425 p := s.wallet.TSpendPolicy(&tspendHash, ticketHash) 3426 3427 var policy string 3428 switch p { 3429 case stake.TreasuryVoteYes: 3430 policy = "yes" 3431 case stake.TreasuryVoteNo: 3432 policy = "no" 3433 } 3434 r := &pb.TSpendPoliciesResponse_Policy{ 3435 Hash: tspendHash[:], 3436 Policy: policy, 3437 TicketHash: []byte{}, 3438 } 3439 if req.TicketHash != nil { 3440 r.TicketHash = req.TicketHash 3441 } 3442 resp.Policies = append(resp.Policies, r) 3443 } 3444 return resp, nil 3445 } 3446 3447 // SetTSpendPolicy saves the voting policy for a particular tspend transaction 3448 // hash, and optionally, setting the tspend policy used by a specific ticket. 3449 func (s *votingServer) SetTSpendPolicy(ctx context.Context, req *pb.SetTSpendPolicyRequest) (*pb.SetTSpendPolicyResponse, error) { 3450 if len(req.Hash) != chainhash.HashSize { 3451 err := fmt.Errorf("invalid tspend hash length, expected %d got %d", 3452 chainhash.HashSize, len(req.Hash)) 3453 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3454 } 3455 3456 hash, err := chainhash.NewHash(req.Hash) 3457 if err != nil { 3458 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3459 } 3460 3461 var ticketHash *chainhash.Hash 3462 if req.TicketHash != nil { 3463 if len(req.TicketHash) != chainhash.HashSize { 3464 err := fmt.Errorf("invalid ticket hash length, expected %d got %d", 3465 chainhash.HashSize, len(req.TicketHash)) 3466 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3467 } 3468 var err error 3469 ticketHash, err = chainhash.NewHash(req.TicketHash) 3470 if err != nil { 3471 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3472 } 3473 } 3474 3475 var policy stake.TreasuryVoteT 3476 switch req.Policy { 3477 case "abstain", "invalid", "": 3478 policy = stake.TreasuryVoteInvalid 3479 case "yes": 3480 policy = stake.TreasuryVoteYes 3481 case "no": 3482 policy = stake.TreasuryVoteNo 3483 default: 3484 err = fmt.Errorf("unknown policy %q", req.Policy) 3485 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3486 } 3487 3488 err = s.wallet.SetTSpendPolicy(ctx, hash, policy, ticketHash) 3489 if err != nil { 3490 return nil, err 3491 } 3492 3493 resp := &pb.SetTSpendPolicyResponse{} 3494 return resp, err 3495 } 3496 3497 // treasuryPolicies returns voting policies for treasury spends for all keys in an array 3498 func (s *votingServer) TreasuryPolicies(ctx context.Context, req *pb.TreasuryPoliciesRequest) (*pb.TreasuryPoliciesResponse, error) { 3499 policies := s.wallet.TreasuryKeyPolicies() 3500 resp := &pb.TreasuryPoliciesResponse{ 3501 Policies: make([]*pb.TreasuryPoliciesResponse_Policy, 0, len(policies)), 3502 } 3503 for i := range policies { 3504 var policy string 3505 switch policies[i].Policy { 3506 case stake.TreasuryVoteYes: 3507 policy = "yes" 3508 case stake.TreasuryVoteNo: 3509 policy = "no" 3510 } 3511 r := &pb.TreasuryPoliciesResponse_Policy{ 3512 Key: policies[i].PiKey, 3513 Policy: policy, 3514 TicketHash: []byte{}, 3515 } 3516 3517 if policies[i].Ticket != nil { 3518 r.TicketHash = policies[i].Ticket[:] 3519 } 3520 3521 resp.Policies = append(resp.Policies, r) 3522 } 3523 return resp, nil 3524 } 3525 3526 // setTreasuryPolicy saves the voting policy for treasury spends by a particular 3527 // key, and optionally, setting the key policy used by a specific ticket. 3528 // 3529 // If a VSP host is configured in the application settings, the voting 3530 // preferences will also be set with the VSP. 3531 func (s *votingServer) SetTreasuryPolicy(ctx context.Context, req *pb.SetTreasuryPolicyRequest) (*pb.SetTreasuryPolicyResponse, error) { 3532 var ticketHash *chainhash.Hash 3533 var err error 3534 if len(req.TicketHash) != 0 { 3535 ticketHash, err = chainhash.NewHash(req.TicketHash) 3536 if err != nil { 3537 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3538 } 3539 } 3540 3541 if len(req.Key) != secp256k1.PubKeyBytesLenCompressed { 3542 err = errors.New("treasury key must be 33 bytes") 3543 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3544 } 3545 var policy stake.TreasuryVoteT 3546 switch req.Policy { 3547 case "abstain", "invalid", "": 3548 policy = stake.TreasuryVoteInvalid 3549 case "yes": 3550 policy = stake.TreasuryVoteYes 3551 case "no": 3552 policy = stake.TreasuryVoteNo 3553 default: 3554 err = fmt.Errorf("unknown policy %q", req.Policy) 3555 return nil, status.Errorf(codes.InvalidArgument, "%v", err) 3556 } 3557 3558 err = s.wallet.SetTreasuryKeyPolicy(ctx, req.Key, policy, ticketHash) 3559 if err != nil { 3560 return nil, translateError(err) 3561 } 3562 3563 resp := &pb.SetTreasuryPolicyResponse{} 3564 return resp, err 3565 } 3566 3567 // StartMessageVerificationService starts the MessageVerification service 3568 func StartMessageVerificationService(server *grpc.Server, chainParams *chaincfg.Params) { 3569 messageVerificationService.chainParams = chainParams 3570 } 3571 3572 func (s *messageVerificationServer) VerifyMessage(ctx context.Context, req *pb.VerifyMessageRequest) ( 3573 *pb.VerifyMessageResponse, error) { 3574 3575 var valid bool 3576 3577 addr, err := stdaddr.DecodeAddress(req.Address, s.chainParams) 3578 if err != nil { 3579 return nil, translateError(err) 3580 } 3581 3582 // Addresses must have an associated secp256k1 private key and must be P2PKH 3583 // (P2PK and P2SH is not allowed). 3584 switch addr.(type) { 3585 case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0: 3586 default: 3587 return nil, status.Error(codes.InvalidArgument, 3588 "address must be secp256k1 pay-to-pubkey-hash") 3589 } 3590 3591 valid, err = wallet.VerifyMessage(req.Message, addr, req.Signature, s.chainParams) 3592 if err != nil { 3593 return nil, translateError(err) 3594 } 3595 return &pb.VerifyMessageResponse{Valid: valid}, nil 3596 } 3597 3598 // StartDecodeMessageService starts the MessageDecode service 3599 func StartDecodeMessageService(server *grpc.Server, chainParams *chaincfg.Params) { 3600 decodeMessageService.chainParams = chainParams 3601 } 3602 3603 func marshalDecodedTxInputs(mtx *wire.MsgTx) []*pb.DecodedTransaction_Input { 3604 inputs := make([]*pb.DecodedTransaction_Input, len(mtx.TxIn)) 3605 3606 for i, txIn := range mtx.TxIn { 3607 // The disassembled string will contain [error] inline 3608 // if the script doesn't fully parse, so ignore the 3609 // error here. 3610 disbuf, _ := txscript.DisasmString(txIn.SignatureScript) 3611 3612 inputs[i] = &pb.DecodedTransaction_Input{ 3613 PreviousTransactionHash: txIn.PreviousOutPoint.Hash[:], 3614 PreviousTransactionIndex: txIn.PreviousOutPoint.Index, 3615 Tree: pb.DecodedTransaction_Input_TreeType(txIn.PreviousOutPoint.Tree), 3616 Sequence: txIn.Sequence, 3617 AmountIn: txIn.ValueIn, 3618 BlockHeight: txIn.BlockHeight, 3619 BlockIndex: txIn.BlockIndex, 3620 SignatureScript: txIn.SignatureScript, 3621 SignatureScriptAsm: disbuf, 3622 } 3623 } 3624 3625 return inputs 3626 } 3627 3628 func marshalDecodedTxOutputs(mtx *wire.MsgTx, chainParams *chaincfg.Params) []*pb.DecodedTransaction_Output { 3629 outputs := make([]*pb.DecodedTransaction_Output, len(mtx.TxOut)) 3630 txType := stake.DetermineTxType(mtx) 3631 3632 for i, v := range mtx.TxOut { 3633 // The disassembled string will contain [error] inline if the 3634 // script doesn't fully parse, so ignore the error here. 3635 disbuf, _ := txscript.DisasmString(v.PkScript) 3636 3637 // Attempt to extract addresses from the public key script. In 3638 // the case of stake submission transactions, the odd outputs 3639 // contain a commitment address, so detect that case 3640 // accordingly. 3641 var addrs []stdaddr.Address 3642 var encodedAddrs []string 3643 var scriptClass stdscript.ScriptType 3644 var reqSigs uint16 3645 var commitAmt *dcrutil.Amount 3646 if (txType == stake.TxTypeSStx) && (stake.IsStakeCommitmentTxOut(i)) { 3647 // Questionable! This is either "nulldata" or the pseudo-type 3648 // "sstxcommitment", not "stakesubmission", which is only the 0th 3649 // output of a ticket. 3650 scriptClass = stdscript.STStakeSubmissionPubKeyHash 3651 addr, err := stake.AddrFromSStxPkScrCommitment(v.PkScript, 3652 chainParams) 3653 if err != nil { 3654 encodedAddrs = []string{fmt.Sprintf( 3655 "[error] failed to decode ticket "+ 3656 "commitment addr output for tx hash "+ 3657 "%v, output idx %v", mtx.TxHash(), i)} 3658 } else { 3659 encodedAddrs = []string{addr.String()} 3660 } 3661 amt, err := stake.AmountFromSStxPkScrCommitment(v.PkScript) 3662 if err != nil { 3663 commitAmt = &amt 3664 } 3665 } else { 3666 // Ignore the error here since an error means the script 3667 // couldn't parse and there is no additional information 3668 // about it anyways. 3669 scriptClass, addrs = stdscript.ExtractAddrs(v.Version, v.PkScript, chainParams) 3670 reqSigs = stdscript.DetermineRequiredSigs(v.Version, v.PkScript) 3671 encodedAddrs = make([]string, len(addrs)) 3672 for j, addr := range addrs { 3673 encodedAddrs[j] = addr.String() 3674 } 3675 } 3676 3677 outputs[i] = &pb.DecodedTransaction_Output{ 3678 Index: uint32(i), 3679 Value: v.Value, 3680 Version: int32(v.Version), 3681 Addresses: encodedAddrs, 3682 Script: v.PkScript, 3683 ScriptAsm: disbuf, 3684 ScriptClass: pb.DecodedTransaction_Output_ScriptClass(scProto(scriptClass)), 3685 RequiredSignatures: int32(reqSigs), 3686 } 3687 if commitAmt != nil { 3688 outputs[i].CommitmentAmount = int64(*commitAmt) 3689 } 3690 } 3691 3692 return outputs 3693 } 3694 3695 // api.proto: 3696 // 3697 // enum ScriptClass { 3698 // NON_STANDARD = 0; 3699 // PUB_KEY = 1; 3700 // PUB_KEY_HASH = 2; 3701 // SCRIPT_HASH = 3; 3702 // MULTI_SIG = 4; 3703 // NULL_DATA = 5; 3704 // STAKE_SUBMISSION = 6; 3705 // STAKE_GEN = 7; 3706 // STAKE_REVOCATION = 8; 3707 // STAKE_SUB_CHANGE = 9; 3708 // PUB_KEY_ALT = 10; 3709 // PUB_KEY_HASH_ALT = 11; 3710 // TGEN = 12; 3711 // TADD = 13; 3712 3713 func scProto(class stdscript.ScriptType) int32 { 3714 switch class { 3715 case stdscript.STNonStandard: 3716 return 0 3717 case stdscript.STPubKeyEcdsaSecp256k1: 3718 return 1 3719 case stdscript.STPubKeyHashEcdsaSecp256k1: 3720 return 2 3721 case stdscript.STScriptHash: 3722 return 3 3723 case stdscript.STMultiSig: 3724 return 4 3725 case stdscript.STNullData: 3726 return 5 3727 case stdscript.STStakeSubmissionPubKeyHash, stdscript.STStakeSubmissionScriptHash: 3728 return 6 3729 case stdscript.STStakeGenPubKeyHash, stdscript.STStakeGenScriptHash: 3730 return 7 3731 case stdscript.STStakeRevocationPubKeyHash, stdscript.STStakeRevocationScriptHash: 3732 return 8 3733 case stdscript.STStakeChangePubKeyHash, stdscript.STStakeChangeScriptHash: 3734 return 9 3735 case stdscript.STPubKeyEd25519, stdscript.STPubKeySchnorrSecp256k1: 3736 return 10 3737 case stdscript.STPubKeyHashEd25519, stdscript.STPubKeyHashSchnorrSecp256k1: 3738 return 11 3739 case stdscript.STTreasuryGenPubKeyHash, stdscript.STTreasuryGenScriptHash: 3740 return 12 3741 case stdscript.STTreasuryAdd: 3742 return 13 3743 } 3744 3745 return 0 3746 } 3747 3748 func (s *decodeMessageServer) DecodeRawTransaction(ctx context.Context, req *pb.DecodeRawTransactionRequest) ( 3749 *pb.DecodeRawTransactionResponse, error) { 3750 3751 serializedTx := req.SerializedTransaction 3752 3753 var mtx wire.MsgTx 3754 err := mtx.Deserialize(bytes.NewReader(serializedTx)) 3755 if err != nil { 3756 return nil, status.Errorf(codes.InvalidArgument, "Could not decode Tx: %v", 3757 err) 3758 } 3759 3760 txHash := mtx.TxHash() 3761 resp := &pb.DecodeRawTransactionResponse{ 3762 Transaction: &pb.DecodedTransaction{ 3763 TransactionHash: txHash[:], 3764 TransactionType: marshalTxType(wallet.TxTransactionType(&mtx)), 3765 Version: int32(mtx.Version), 3766 LockTime: mtx.LockTime, 3767 Expiry: mtx.Expiry, 3768 Inputs: marshalDecodedTxInputs(&mtx), 3769 Outputs: marshalDecodedTxOutputs(&mtx, s.chainParams), 3770 }, 3771 } 3772 3773 return resp, nil 3774 } 3775 3776 func (s *walletServer) BestBlock(ctx context.Context, req *pb.BestBlockRequest) (*pb.BestBlockResponse, error) { 3777 hash, height := s.wallet.MainChainTip(ctx) 3778 resp := &pb.BestBlockResponse{ 3779 Hash: hash[:], 3780 Height: uint32(height), 3781 } 3782 return resp, nil 3783 } 3784 3785 func (s *walletServer) SignHashes(ctx context.Context, req *pb.SignHashesRequest) (*pb.SignHashesResponse, error) { 3786 if len(req.Passphrase) > 0 { 3787 lock := make(chan time.Time, 1) 3788 defer func() { 3789 lock <- time.Time{} // send matters, not the value 3790 }() 3791 err := s.wallet.Unlock(ctx, req.Passphrase, lock) 3792 if err != nil { 3793 return nil, translateError(err) 3794 } 3795 } 3796 3797 addr, err := decodeAddress(req.Address, s.wallet.ChainParams()) 3798 if err != nil { 3799 return nil, err 3800 } 3801 3802 // Addresses must have an associated secp256k1 private key and therefore 3803 // must be P2PK or P2PKH (P2SH is not allowed). 3804 switch addr.(type) { 3805 case *stdaddr.AddressPubKeyEcdsaSecp256k1V0: 3806 case *stdaddr.AddressPubKeyHashEcdsaSecp256k1V0: 3807 default: 3808 return nil, status.Error(codes.InvalidArgument, 3809 "address must be secp256k1 P2PK or P2PKH") 3810 } 3811 3812 signatures, pubKey, err := s.wallet.SignHashes(ctx, req.Hashes, addr) 3813 if err != nil { 3814 return nil, translateError(err) 3815 } 3816 3817 return &pb.SignHashesResponse{ 3818 PublicKey: pubKey, 3819 Signatures: signatures, 3820 }, nil 3821 } 3822 3823 func (s *walletServer) AbandonTransaction(ctx context.Context, req *pb.AbandonTransactionRequest) ( 3824 *pb.AbandonTransactionResponse, error) { 3825 3826 txHash, err := chainhash.NewHash(req.TransactionHash) 3827 if err != nil { 3828 return nil, status.Errorf(codes.InvalidArgument, "invalid transaction hash: %v", err) 3829 } 3830 err = s.wallet.AbandonTransaction(ctx, txHash) 3831 if err != nil { 3832 return nil, translateError(err) 3833 } 3834 return &pb.AbandonTransactionResponse{}, nil 3835 } 3836 3837 // StartNetworkService starts the NetworkService. 3838 func StartNetworkService(server *grpc.Server, wallet *wallet.Wallet) { 3839 networkService.wallet = wallet 3840 if atomic.SwapUint32(&networkService.ready, 1) != 0 { 3841 panic("service already started") 3842 } 3843 } 3844 3845 func (s *networkServer) checkReady() bool { 3846 return atomic.LoadUint32(&s.ready) != 0 3847 } 3848 3849 func (s *networkServer) GetRawBlock(ctx context.Context, req *pb.GetRawBlockRequest) (*pb.GetRawBlockResponse, error) { 3850 n, err := s.wallet.NetworkBackend() 3851 if err != nil { 3852 return nil, translateError(err) 3853 } 3854 3855 var bh *chainhash.Hash 3856 if req.BlockHash == nil { 3857 id := wallet.NewBlockIdentifierFromHeight(req.BlockHeight) 3858 info, err := s.wallet.BlockInfo(ctx, id) 3859 if err != nil { 3860 return nil, translateError(err) 3861 } 3862 3863 bh = &info.Hash 3864 } else { 3865 bh, err = chainhash.NewHash(req.BlockHash) 3866 if err != nil { 3867 return nil, status.Errorf(codes.InvalidArgument, "invalid blockhash: %s", err.Error()) 3868 } 3869 } 3870 3871 blocks, err := n.Blocks(ctx, []*chainhash.Hash{bh}) 3872 if err != nil { 3873 return nil, translateError(err) 3874 } 3875 if len(blocks) == 0 { 3876 // Should never happen but protects against a possible panic on 3877 // the following code. 3878 return nil, status.Errorf(codes.Internal, "network returned 0 blocks") 3879 } 3880 3881 rawBlock, err := blocks[0].Bytes() 3882 if err != nil { 3883 return nil, translateError(err) 3884 } 3885 return &pb.GetRawBlockResponse{ 3886 Block: rawBlock, 3887 }, nil 3888 } 3889 3890 func (s *walletServer) GetCoinjoinOutputspByAcct(ctx context.Context, req *pb.GetCoinjoinOutputspByAcctRequest) ( 3891 *pb.GetCoinjoinOutputspByAcctResponse, error) { 3892 coinjumSumByAcct, err := s.wallet.GetCoinjoinTxsSumbByAcct(ctx) 3893 if err != nil { 3894 return nil, translateError(err) 3895 } 3896 var resp []*pb.CoinjoinTxsSumByAcct 3897 for acctIdx, sum := range coinjumSumByAcct { 3898 s := &pb.CoinjoinTxsSumByAcct{ 3899 AccountNumber: acctIdx, 3900 CoinjoinTxsSum: int32(sum), 3901 } 3902 resp = append(resp, s) 3903 } 3904 3905 return &pb.GetCoinjoinOutputspByAcctResponse{ 3906 Data: resp, 3907 }, nil 3908 } 3909 3910 func (s *walletServer) SetAccountPassphrase(ctx context.Context, req *pb.SetAccountPassphraseRequest) ( 3911 *pb.SetAccountPassphraseResponse, error) { 3912 _, err := s.wallet.AccountName(ctx, req.AccountNumber) 3913 if err != nil { 3914 if errors.Is(err, errors.NotExist) { 3915 return nil, status.Errorf(codes.NotFound, "account not found") 3916 } 3917 return nil, err 3918 } 3919 encryptedAcct, err := s.wallet.AccountHasPassphrase(ctx, req.AccountNumber) 3920 if err != nil { 3921 return nil, err 3922 } 3923 3924 // if account is not encrypted we need to unlock the wallet. Otherwise it is 3925 // used the account passphrase for it. 3926 if encryptedAcct && len(req.AccountPassphrase) > 0 { 3927 err = s.wallet.UnlockAccount(ctx, req.AccountNumber, req.AccountPassphrase) 3928 if err != nil { 3929 return nil, translateError(err) 3930 } 3931 defer func() { 3932 zero(req.AccountPassphrase) 3933 err = s.wallet.LockAccount(ctx, req.AccountNumber) 3934 }() 3935 } else if len(req.WalletPassphrase) > 0 { 3936 lock := make(chan time.Time, 1) 3937 defer func() { 3938 zero(req.WalletPassphrase) 3939 lock <- time.Time{} // send matters, not the value 3940 }() 3941 err = s.wallet.Unlock(ctx, req.WalletPassphrase, lock) 3942 if err != nil { 3943 return nil, translateError(err) 3944 } 3945 } 3946 3947 err = s.wallet.SetAccountPassphrase(ctx, req.AccountNumber, req.NewAccountPassphrase) 3948 if err != nil { 3949 return nil, translateError(err) 3950 } 3951 return &pb.SetAccountPassphraseResponse{}, nil 3952 } 3953 3954 func (s *walletServer) UnlockAccount(ctx context.Context, req *pb.UnlockAccountRequest) ( 3955 *pb.UnlockAccountResponse, error) { 3956 _, err := s.wallet.AccountName(ctx, req.AccountNumber) 3957 if err != nil { 3958 if errors.Is(err, errors.NotExist) { 3959 return nil, status.Errorf(codes.NotFound, "account not found") 3960 } 3961 return nil, err 3962 } 3963 err = s.wallet.UnlockAccount(ctx, req.AccountNumber, req.Passphrase) 3964 if err != nil { 3965 return nil, translateError(err) 3966 } 3967 return &pb.UnlockAccountResponse{}, nil 3968 } 3969 3970 func (s *walletServer) LockAccount(ctx context.Context, req *pb.LockAccountRequest) ( 3971 *pb.LockAccountResponse, error) { 3972 _, err := s.wallet.AccountName(ctx, req.AccountNumber) 3973 if err != nil { 3974 if errors.Is(err, errors.NotExist) { 3975 return nil, status.Errorf(codes.NotFound, "account not found") 3976 } 3977 return nil, translateError(err) 3978 } 3979 err = s.wallet.LockAccount(ctx, req.AccountNumber) 3980 if err != nil { 3981 return nil, translateError(err) 3982 } 3983 return &pb.LockAccountResponse{}, nil 3984 } 3985 3986 func (s *walletServer) AccountUnlocked(ctx context.Context, req *pb.AccountUnlockedRequest) ( 3987 *pb.AccountUnlockedResponse, error) { 3988 _, err := s.wallet.AccountName(ctx, req.AccountNumber) 3989 if err != nil { 3990 if errors.Is(err, errors.NotExist) { 3991 return nil, status.Errorf(codes.NotFound, "account not found") 3992 } 3993 return nil, translateError(err) 3994 } 3995 unlocked, err := s.wallet.AccountUnlocked(ctx, req.AccountNumber) 3996 if err != nil { 3997 return nil, translateError(err) 3998 } 3999 return &pb.AccountUnlockedResponse{ 4000 Unlocked: unlocked, 4001 }, nil 4002 } 4003 4004 func (s *walletServer) UnlockWallet(ctx context.Context, req *pb.UnlockWalletRequest) ( 4005 *pb.UnlockWalletResponse, error) { 4006 4007 defer zero(req.Passphrase) 4008 err := s.wallet.Unlock(ctx, req.Passphrase, nil) 4009 if err != nil { 4010 return nil, translateError(err) 4011 } 4012 return &pb.UnlockWalletResponse{}, nil 4013 } 4014 4015 func (s *walletServer) LockWallet(ctx context.Context, req *pb.LockWalletRequest) ( 4016 *pb.LockWalletResponse, error) { 4017 4018 s.wallet.Lock() 4019 return &pb.LockWalletResponse{}, nil 4020 } 4021 4022 // getPeerInfo responds to the getpeerinfo request. 4023 // It gets the network backend and views the data on remote peers when in spv mode 4024 func (s *walletServer) GetPeerInfo(ctx context.Context, req *pb.GetPeerInfoRequest) (*pb.GetPeerInfoResponse, error) { 4025 n, err := s.wallet.NetworkBackend() 4026 if err != nil { 4027 return nil, err 4028 } 4029 syncer, ok := n.(*spv.Syncer) 4030 if !ok { 4031 var resp []*struct { 4032 ID int32 `json:"id"` 4033 Addr string `json:"addr"` 4034 AddrLocal string `json:"addrlocal"` 4035 Services string `json:"services"` 4036 Version uint32 `json:"version"` 4037 SubVer string `json:"subver"` 4038 StartingHeight int64 `json:"startingheight"` 4039 BanScore int32 `json:"banscore"` 4040 } 4041 if rpc, ok := n.(*dcrd.RPC); ok { 4042 err := rpc.Call(ctx, "getpeerinfo", &resp) 4043 if err != nil { 4044 return nil, err 4045 } 4046 } 4047 grpcResp := []*pb.GetPeerInfoResponse_PeerInfo{} 4048 for _, peerInfo := range resp { 4049 peerInfo := &pb.GetPeerInfoResponse_PeerInfo{ 4050 Id: peerInfo.ID, 4051 Addr: peerInfo.Addr, 4052 AddrLocal: peerInfo.AddrLocal, 4053 Services: peerInfo.Services, 4054 Version: peerInfo.Version, 4055 SubVer: peerInfo.SubVer, 4056 StartingHeight: peerInfo.StartingHeight, 4057 BanScore: peerInfo.BanScore, 4058 } 4059 grpcResp = append(grpcResp, peerInfo) 4060 } 4061 4062 return &pb.GetPeerInfoResponse{ 4063 PeerInfo: grpcResp, 4064 }, nil 4065 } 4066 4067 rps := syncer.GetRemotePeers() 4068 infos := make([]*pb.GetPeerInfoResponse_PeerInfo, 0, len(rps)) 4069 4070 for _, rp := range rps { 4071 info := &pb.GetPeerInfoResponse_PeerInfo{ 4072 Id: int32(rp.ID()), 4073 Addr: rp.RemoteAddr().String(), 4074 AddrLocal: rp.LocalAddr().String(), 4075 Services: fmt.Sprintf("%08d", uint64(rp.Services())), 4076 Version: rp.Pver(), 4077 SubVer: rp.UA(), 4078 StartingHeight: int64(rp.InitialHeight()), 4079 BanScore: int32(rp.BanScore()), 4080 } 4081 infos = append(infos, info) 4082 } 4083 sort.Slice(infos, func(i, j int) bool { 4084 return infos[i].Id < infos[j].Id 4085 }) 4086 return &pb.GetPeerInfoResponse{ 4087 PeerInfo: infos, 4088 }, nil 4089 } 4090 4091 func (s *walletServer) GetVSPTicketsByFeeStatus(ctx context.Context, req *pb.GetVSPTicketsByFeeStatusRequest) ( 4092 *pb.GetVSPTicketsByFeeStatusResponse, error) { 4093 var feeStatus int 4094 switch req.FeeStatus { 4095 case pb.GetVSPTicketsByFeeStatusRequest_VSP_FEE_PROCESS_STARTED: 4096 feeStatus = int(udb.VSPFeeProcessStarted) 4097 case pb.GetVSPTicketsByFeeStatusRequest_VSP_FEE_PROCESS_PAID: 4098 feeStatus = int(udb.VSPFeeProcessPaid) 4099 case pb.GetVSPTicketsByFeeStatusRequest_VSP_FEE_PROCESS_ERRORED: 4100 feeStatus = int(udb.VSPFeeProcessErrored) 4101 case pb.GetVSPTicketsByFeeStatusRequest_VSP_FEE_PROCESS_CONFIRMED: 4102 feeStatus = int(udb.VSPFeeProcessConfirmed) 4103 default: 4104 return nil, status.Errorf(codes.InvalidArgument, "fee status=%v", req.FeeStatus) 4105 } 4106 4107 failedTicketsFee, err := s.wallet.GetVSPTicketsByFeeStatus(ctx, feeStatus) 4108 if err != nil { 4109 return nil, err 4110 } 4111 4112 hashes := make([][]byte, len(failedTicketsFee)) 4113 for i := range failedTicketsFee { 4114 hashes[i] = failedTicketsFee[i][:] 4115 } 4116 return &pb.GetVSPTicketsByFeeStatusResponse{ 4117 TicketsHashes: hashes, 4118 }, nil 4119 } 4120 4121 func (s *walletServer) SyncVSPFailedTickets(ctx context.Context, req *pb.SyncVSPTicketsRequest) ( 4122 *pb.SyncVSPTicketsResponse, error) { 4123 failedTicketsFee, err := s.wallet.GetVSPTicketsByFeeStatus(ctx, int(udb.VSPFeeProcessErrored)) 4124 if err != nil { 4125 return nil, err 4126 } 4127 4128 vspHost := req.VspHost 4129 vspPubKey := req.VspPubkey 4130 if vspPubKey == "" { 4131 return nil, status.Errorf(codes.InvalidArgument, "vsp pubkey can not be null") 4132 } 4133 if vspHost == "" { 4134 return nil, status.Errorf(codes.InvalidArgument, "vsp host can not be null") 4135 } 4136 policy := vsp.Policy{ 4137 MaxFee: 0.1e8, 4138 FeeAcct: req.Account, 4139 ChangeAcct: req.ChangeAccount, 4140 } 4141 cfg := vsp.Config{ 4142 URL: vspHost, 4143 PubKey: vspPubKey, 4144 Dialer: nil, 4145 Wallet: s.wallet, 4146 Policy: policy, 4147 } 4148 vspClient, err := loader.VSP(cfg) 4149 if err != nil { 4150 return nil, status.Errorf(codes.Unknown, "TicketBuyerV3 instance failed to start. Error: %v", err) 4151 } 4152 4153 // process tickets fee if needed. 4154 for _, ticketHash := range failedTicketsFee { 4155 feeTx := new(wire.MsgTx) 4156 err := vspClient.Process(ctx, &ticketHash, feeTx) 4157 if err != nil { 4158 // if it fails to process again, we log it and continue with 4159 // the wallet start. 4160 // Not sure we need to log here since it's already warned elsewhere 4161 } 4162 } 4163 return &pb.SyncVSPTicketsResponse{}, nil 4164 } 4165 4166 func (s *walletServer) ProcessManagedTickets(ctx context.Context, req *pb.ProcessManagedTicketsRequest) ( 4167 *pb.ProcessManagedTicketsResponse, error) { 4168 4169 vspHost := req.VspHost 4170 vspPubKey := req.VspPubkey 4171 if vspPubKey == "" { 4172 return nil, status.Errorf(codes.InvalidArgument, "vsp pubkey can not be null") 4173 } 4174 if vspHost == "" { 4175 return nil, status.Errorf(codes.InvalidArgument, "vsp host can not be null") 4176 } 4177 policy := vsp.Policy{ 4178 MaxFee: 0.1e8, 4179 FeeAcct: req.FeeAccount, 4180 ChangeAcct: req.ChangeAccount, 4181 } 4182 cfg := vsp.Config{ 4183 URL: vspHost, 4184 PubKey: vspPubKey, 4185 Dialer: nil, 4186 Wallet: s.wallet, 4187 Policy: policy, 4188 } 4189 vspClient, err := loader.VSP(cfg) 4190 if err != nil { 4191 return nil, status.Errorf(codes.Unknown, "VSPClient instance failed to start. Error: %v", err) 4192 } 4193 4194 err = vspClient.ProcessManagedTickets(ctx) 4195 if err != nil { 4196 return nil, status.Errorf(codes.Unknown, "ProcessManagedTickets failed. Error: %v", err) 4197 } 4198 4199 return &pb.ProcessManagedTicketsResponse{}, nil 4200 } 4201 4202 func (s *walletServer) ProcessUnmanagedTickets(ctx context.Context, req *pb.ProcessUnmanagedTicketsRequest) ( 4203 *pb.ProcessUnmanagedTicketsResponse, error) { 4204 4205 vspHost := req.VspHost 4206 vspPubKey := req.VspPubkey 4207 if vspPubKey == "" { 4208 return nil, status.Errorf(codes.InvalidArgument, "vsp pubkey can not be null") 4209 } 4210 if vspHost == "" { 4211 return nil, status.Errorf(codes.InvalidArgument, "vsp host can not be null") 4212 } 4213 policy := vsp.Policy{ 4214 MaxFee: 0.1e8, 4215 FeeAcct: req.FeeAccount, 4216 ChangeAcct: req.ChangeAccount, 4217 } 4218 cfg := vsp.Config{ 4219 URL: vspHost, 4220 PubKey: vspPubKey, 4221 Dialer: nil, 4222 Wallet: s.wallet, 4223 Policy: policy, 4224 } 4225 vspClient, err := loader.VSP(cfg) 4226 if err != nil { 4227 return nil, status.Errorf(codes.Unknown, "VSPClient instance failed to start. Error: %v", err) 4228 } 4229 4230 errUnmanagedTickets := errors.New("unmanaged tickets") 4231 err = s.wallet.ForUnspentUnexpiredTickets(ctx, func(hash *chainhash.Hash) error { 4232 _, err := s.wallet.VSPFeeHashForTicket(ctx, hash) 4233 if errors.Is(err, errors.NotExist) { 4234 return errUnmanagedTickets 4235 } 4236 return nil 4237 }) 4238 if errors.Is(err, errUnmanagedTickets) { 4239 vspClient.ProcessUnprocessedTickets(ctx) 4240 } 4241 4242 return &pb.ProcessUnmanagedTicketsResponse{}, nil 4243 } 4244 4245 func (s *walletServer) SetVspdVoteChoices(ctx context.Context, req *pb.SetVspdVoteChoicesRequest) ( 4246 *pb.SetVspdVoteChoicesResponse, error) { 4247 4248 vspHost := req.VspHost 4249 vspPubKey := req.VspPubkey 4250 if vspPubKey == "" { 4251 return nil, status.Errorf(codes.InvalidArgument, "vsp pubkey can not be null") 4252 } 4253 if vspHost == "" { 4254 return nil, status.Errorf(codes.InvalidArgument, "vsp host can not be null") 4255 } 4256 policy := vsp.Policy{ 4257 MaxFee: 0.1e8, 4258 FeeAcct: req.FeeAccount, 4259 ChangeAcct: req.ChangeAccount, 4260 } 4261 cfg := vsp.Config{ 4262 URL: vspHost, 4263 PubKey: vspPubKey, 4264 Dialer: nil, 4265 Wallet: s.wallet, 4266 Policy: policy, 4267 } 4268 vspClient, err := loader.VSP(cfg) 4269 if err != nil { 4270 return nil, status.Errorf(codes.Unknown, "VSPClient instance failed to start. Error: %v", err) 4271 } 4272 4273 treasuryChoices := make(map[string]string) 4274 treasuryPolicies := s.wallet.TreasuryKeyPolicies() 4275 for _, value := range treasuryPolicies { 4276 var choice string 4277 switch value.Policy { 4278 case stake.TreasuryVoteYes: 4279 choice = "yes" 4280 case stake.TreasuryVoteNo: 4281 choice = "no" 4282 default: 4283 choice = "abstain" 4284 } 4285 treasuryChoices[hex.EncodeToString(value.PiKey)] = choice 4286 } 4287 4288 tSpendChoices := make(map[string]string) 4289 tspendPolicies := s.wallet.GetAllTSpends(ctx) 4290 for i := range tspendPolicies { 4291 tspendHash := tspendPolicies[i].TxHash() 4292 p := s.wallet.TSpendPolicy(&tspendHash, nil) 4293 4294 var policy string 4295 switch p { 4296 case stake.TreasuryVoteYes: 4297 policy = "yes" 4298 case stake.TreasuryVoteNo: 4299 policy = "no" 4300 } 4301 tSpendChoices[tspendHash.String()] = policy 4302 } 4303 4304 err = s.wallet.ForUnspentUnexpiredTickets(ctx, func(hash *chainhash.Hash) error { 4305 // Skip errors here, but should we log at least? 4306 choices, _, err := s.wallet.AgendaChoices(ctx, hash) 4307 if err != nil { 4308 return nil 4309 } 4310 ticketHost, err := s.wallet.VSPHostForTicket(ctx, hash) 4311 if err != nil { 4312 return err 4313 } 4314 if ticketHost == vspHost { 4315 _ = vspClient.SetVoteChoice(ctx, hash, choices.Map(), tSpendChoices, treasuryChoices) 4316 } 4317 return nil 4318 }) 4319 if err != nil { 4320 return nil, status.Errorf(codes.Unknown, "ForUnspentUnexpiredTickets failed. Error: %v", err) 4321 } 4322 4323 return &pb.SetVspdVoteChoicesResponse{}, nil 4324 } 4325 4326 func marshalVSPTrackedTickets(tickets []*vsp.TicketInfo) []*pb.GetTrackedVSPTicketsResponse_Ticket { 4327 res := make([]*pb.GetTrackedVSPTicketsResponse_Ticket, len(tickets)) 4328 for i, ticket := range tickets { 4329 res[i] = &pb.GetTrackedVSPTicketsResponse_Ticket{ 4330 TicketHash: ticket.TicketHash[:], 4331 CommitmentAddress: ticket.CommitmentAddr.String(), 4332 VotingAddress: ticket.VotingAddr.String(), 4333 State: ticket.State, 4334 Fee: int64(ticket.Fee), 4335 FeeHash: ticket.FeeHash[:], 4336 } 4337 } 4338 return res 4339 } 4340 4341 func (s *walletServer) GetTrackedVSPTickets(ctx context.Context, req *pb.GetTrackedVSPTicketsRequest) (*pb.GetTrackedVSPTicketsResponse, error) { 4342 vspClients := loader.AllVSPs() 4343 res := &pb.GetTrackedVSPTicketsResponse{ 4344 Vsps: make([]*pb.GetTrackedVSPTicketsResponse_VSP, 0, len(vspClients)), 4345 } 4346 for host, vspClient := range vspClients { 4347 tickets := vspClient.TrackedTickets() 4348 vspInfo := &pb.GetTrackedVSPTicketsResponse_VSP{ 4349 Host: host, 4350 Tickets: marshalVSPTrackedTickets(tickets), 4351 } 4352 res.Vsps = append(res.Vsps, vspInfo) 4353 } 4354 4355 return res, nil 4356 } 4357 4358 func (s *walletServer) DiscoverUsage(ctx context.Context, req *pb.DiscoverUsageRequest) (*pb.DiscoverUsageResponse, error) { 4359 n, err := s.requireNetworkBackend() 4360 if err != nil { 4361 return nil, status.Errorf(codes.Unknown, "Unable to retrieve network backend. Error: %v", err) 4362 } 4363 4364 startBlock := s.wallet.ChainParams().GenesisHash 4365 if req.StartingBlockHash != nil { 4366 h, err := chainhash.NewHash(req.StartingBlockHash) 4367 if err != nil { 4368 return nil, status.Errorf(codes.Unknown, "Invalid starting block hash provided. Error: %v", err) 4369 } 4370 startBlock = *h 4371 } 4372 4373 gapLimit := s.wallet.GapLimit() 4374 if req.GapLimit != 0 { 4375 gapLimit = req.GapLimit 4376 } 4377 4378 err = s.wallet.DiscoverActiveAddresses(ctx, n, &startBlock, req.DiscoverAccounts, gapLimit) 4379 if err != nil { 4380 return nil, err 4381 } 4382 4383 return &pb.DiscoverUsageResponse{}, nil 4384 }