github.com/palcoin-project/palcd@v1.0.0/rpcclient/infrastructure.go (about) 1 // Copyright (c) 2014-2017 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package rpcclient 6 7 import ( 8 "bytes" 9 "container/list" 10 "crypto/tls" 11 "crypto/x509" 12 "encoding/base64" 13 "encoding/json" 14 "errors" 15 "fmt" 16 "io" 17 "io/ioutil" 18 "math" 19 "net" 20 "net/http" 21 "net/url" 22 "os" 23 "strings" 24 "sync" 25 "sync/atomic" 26 "time" 27 28 "github.com/btcsuite/go-socks/socks" 29 "github.com/btcsuite/websocket" 30 "github.com/palcoin-project/palcd/btcjson" 31 "github.com/palcoin-project/palcd/chaincfg" 32 ) 33 34 var ( 35 // ErrInvalidAuth is an error to describe the condition where the client 36 // is either unable to authenticate or the specified endpoint is 37 // incorrect. 38 ErrInvalidAuth = errors.New("authentication failure") 39 40 // ErrInvalidEndpoint is an error to describe the condition where the 41 // websocket handshake failed with the specified endpoint. 42 ErrInvalidEndpoint = errors.New("the endpoint either does not support " + 43 "websockets or does not exist") 44 45 // ErrClientNotConnected is an error to describe the condition where a 46 // websocket client has been created, but the connection was never 47 // established. This condition differs from ErrClientDisconnect, which 48 // represents an established connection that was lost. 49 ErrClientNotConnected = errors.New("the client was never connected") 50 51 // ErrClientDisconnect is an error to describe the condition where the 52 // client has been disconnected from the RPC server. When the 53 // DisableAutoReconnect option is not set, any outstanding futures 54 // when a client disconnect occurs will return this error as will 55 // any new requests. 56 ErrClientDisconnect = errors.New("the client has been disconnected") 57 58 // ErrClientShutdown is an error to describe the condition where the 59 // client is either already shutdown, or in the process of shutting 60 // down. Any outstanding futures when a client shutdown occurs will 61 // return this error as will any new requests. 62 ErrClientShutdown = errors.New("the client has been shutdown") 63 64 // ErrNotWebsocketClient is an error to describe the condition of 65 // calling a Client method intended for a websocket client when the 66 // client has been configured to run in HTTP POST mode instead. 67 ErrNotWebsocketClient = errors.New("client is not configured for " + 68 "websockets") 69 70 // ErrClientAlreadyConnected is an error to describe the condition where 71 // a new client connection cannot be established due to a websocket 72 // client having already connected to the RPC server. 73 ErrClientAlreadyConnected = errors.New("websocket client has already " + 74 "connected") 75 ) 76 77 const ( 78 // sendBufferSize is the number of elements the websocket send channel 79 // can queue before blocking. 80 sendBufferSize = 50 81 82 // sendPostBufferSize is the number of elements the HTTP POST send 83 // channel can queue before blocking. 84 sendPostBufferSize = 100 85 86 // connectionRetryInterval is the amount of time to wait in between 87 // retries when automatically reconnecting to an RPC server. 88 connectionRetryInterval = time.Second * 5 89 ) 90 91 // sendPostDetails houses an HTTP POST request to send to an RPC server as well 92 // as the original JSON-RPC command and a channel to reply on when the server 93 // responds with the result. 94 type sendPostDetails struct { 95 httpRequest *http.Request 96 jsonRequest *jsonRequest 97 } 98 99 // jsonRequest holds information about a json request that is used to properly 100 // detect, interpret, and deliver a reply to it. 101 type jsonRequest struct { 102 id uint64 103 method string 104 cmd interface{} 105 marshalledJSON []byte 106 responseChan chan *response 107 } 108 109 // BackendVersion represents the version of the backend the client is currently 110 // connected to. 111 type BackendVersion uint8 112 113 const ( 114 // BitcoindPre19 represents a bitcoind version before 0.19.0. 115 BitcoindPre19 BackendVersion = iota 116 117 // BitcoindPost19 represents a bitcoind version equal to or greater than 118 // 0.19.0. 119 BitcoindPost19 120 121 // Btcd represents a catch-all btcd version. 122 Btcd 123 ) 124 125 // Client represents a Bitcoin RPC client which allows easy access to the 126 // various RPC methods available on a Bitcoin RPC server. Each of the wrapper 127 // functions handle the details of converting the passed and return types to and 128 // from the underlying JSON types which are required for the JSON-RPC 129 // invocations 130 // 131 // The client provides each RPC in both synchronous (blocking) and asynchronous 132 // (non-blocking) forms. The asynchronous forms are based on the concept of 133 // futures where they return an instance of a type that promises to deliver the 134 // result of the invocation at some future time. Invoking the Receive method on 135 // the returned future will block until the result is available if it's not 136 // already. 137 type Client struct { 138 id uint64 // atomic, so must stay 64-bit aligned 139 140 // config holds the connection configuration assoiated with this client. 141 config *ConnConfig 142 143 // chainParams holds the params for the chain that this client is using, 144 // and is used for many wallet methods. 145 chainParams *chaincfg.Params 146 147 // wsConn is the underlying websocket connection when not in HTTP POST 148 // mode. 149 wsConn *websocket.Conn 150 151 // httpClient is the underlying HTTP client to use when running in HTTP 152 // POST mode. 153 httpClient *http.Client 154 155 // backendVersion is the version of the backend the client is currently 156 // connected to. This should be retrieved through GetVersion. 157 backendVersionMu sync.Mutex 158 backendVersion *BackendVersion 159 160 // mtx is a mutex to protect access to connection related fields. 161 mtx sync.Mutex 162 163 // disconnected indicated whether or not the server is disconnected. 164 disconnected bool 165 166 // whether or not to batch requests, false unless changed by Batch() 167 batch bool 168 batchList *list.List 169 170 // retryCount holds the number of times the client has tried to 171 // reconnect to the RPC server. 172 retryCount int64 173 174 // Track command and their response channels by ID. 175 requestLock sync.Mutex 176 requestMap map[uint64]*list.Element 177 requestList *list.List 178 179 // Notifications. 180 ntfnHandlers *NotificationHandlers 181 ntfnStateLock sync.Mutex 182 ntfnState *notificationState 183 184 // Networking infrastructure. 185 sendChan chan []byte 186 sendPostChan chan *sendPostDetails 187 connEstablished chan struct{} 188 disconnect chan struct{} 189 shutdown chan struct{} 190 wg sync.WaitGroup 191 } 192 193 // NextID returns the next id to be used when sending a JSON-RPC message. This 194 // ID allows responses to be associated with particular requests per the 195 // JSON-RPC specification. Typically the consumer of the client does not need 196 // to call this function, however, if a custom request is being created and used 197 // this function should be used to ensure the ID is unique amongst all requests 198 // being made. 199 func (c *Client) NextID() uint64 { 200 return atomic.AddUint64(&c.id, 1) 201 } 202 203 // addRequest associates the passed jsonRequest with its id. This allows the 204 // response from the remote server to be unmarshalled to the appropriate type 205 // and sent to the specified channel when it is received. 206 // 207 // If the client has already begun shutting down, ErrClientShutdown is returned 208 // and the request is not added. 209 // 210 // This function is safe for concurrent access. 211 func (c *Client) addRequest(jReq *jsonRequest) error { 212 c.requestLock.Lock() 213 defer c.requestLock.Unlock() 214 215 // A non-blocking read of the shutdown channel with the request lock 216 // held avoids adding the request to the client's internal data 217 // structures if the client is in the process of shutting down (and 218 // has not yet grabbed the request lock), or has finished shutdown 219 // already (responding to each outstanding request with 220 // ErrClientShutdown). 221 select { 222 case <-c.shutdown: 223 return ErrClientShutdown 224 default: 225 } 226 227 if !c.batch { 228 element := c.requestList.PushBack(jReq) 229 c.requestMap[jReq.id] = element 230 } else { 231 element := c.batchList.PushBack(jReq) 232 c.requestMap[jReq.id] = element 233 } 234 return nil 235 } 236 237 // removeRequest returns and removes the jsonRequest which contains the response 238 // channel and original method associated with the passed id or nil if there is 239 // no association. 240 // 241 // This function is safe for concurrent access. 242 func (c *Client) removeRequest(id uint64) *jsonRequest { 243 c.requestLock.Lock() 244 defer c.requestLock.Unlock() 245 246 element := c.requestMap[id] 247 if element != nil { 248 delete(c.requestMap, id) 249 request := c.requestList.Remove(element).(*jsonRequest) 250 return request 251 } 252 253 return nil 254 } 255 256 // removeAllRequests removes all the jsonRequests which contain the response 257 // channels for outstanding requests. 258 // 259 // This function MUST be called with the request lock held. 260 func (c *Client) removeAllRequests() { 261 c.requestMap = make(map[uint64]*list.Element) 262 c.requestList.Init() 263 } 264 265 // trackRegisteredNtfns examines the passed command to see if it is one of 266 // the notification commands and updates the notification state that is used 267 // to automatically re-establish registered notifications on reconnects. 268 func (c *Client) trackRegisteredNtfns(cmd interface{}) { 269 // Nothing to do if the caller is not interested in notifications. 270 if c.ntfnHandlers == nil { 271 return 272 } 273 274 c.ntfnStateLock.Lock() 275 defer c.ntfnStateLock.Unlock() 276 277 switch bcmd := cmd.(type) { 278 case *btcjson.NotifyBlocksCmd: 279 c.ntfnState.notifyBlocks = true 280 281 case *btcjson.NotifyNewTransactionsCmd: 282 if bcmd.Verbose != nil && *bcmd.Verbose { 283 c.ntfnState.notifyNewTxVerbose = true 284 } else { 285 c.ntfnState.notifyNewTx = true 286 287 } 288 289 case *btcjson.NotifySpentCmd: 290 for _, op := range bcmd.OutPoints { 291 c.ntfnState.notifySpent[op] = struct{}{} 292 } 293 294 case *btcjson.NotifyReceivedCmd: 295 for _, addr := range bcmd.Addresses { 296 c.ntfnState.notifyReceived[addr] = struct{}{} 297 } 298 } 299 } 300 301 // FutureGetBulkResult waits for the responses promised by the future 302 // and returns them in a channel 303 type FutureGetBulkResult chan *response 304 305 // Receive waits for the response promised by the future and returns an map 306 // of results by request id 307 func (r FutureGetBulkResult) Receive() (BulkResult, error) { 308 m := make(BulkResult) 309 res, err := receiveFuture(r) 310 if err != nil { 311 return nil, err 312 } 313 var arr []IndividualBulkResult 314 err = json.Unmarshal(res, &arr) 315 if err != nil { 316 return nil, err 317 } 318 319 for _, results := range arr { 320 m[results.Id] = results 321 } 322 323 return m, nil 324 } 325 326 // IndividualBulkResult represents one result 327 // from a bulk json rpc api 328 type IndividualBulkResult struct { 329 Result interface{} `json:"result"` 330 Error *btcjson.RPCError `json:"error"` 331 Id uint64 `json:"id"` 332 } 333 334 type BulkResult = map[uint64]IndividualBulkResult 335 336 // inMessage is the first type that an incoming message is unmarshaled 337 // into. It supports both requests (for notification support) and 338 // responses. The partially-unmarshaled message is a notification if 339 // the embedded ID (from the response) is nil. Otherwise, it is a 340 // response. 341 type inMessage struct { 342 ID *float64 `json:"id"` 343 *rawNotification 344 *rawResponse 345 } 346 347 // rawNotification is a partially-unmarshaled JSON-RPC notification. 348 type rawNotification struct { 349 Method string `json:"method"` 350 Params []json.RawMessage `json:"params"` 351 } 352 353 // rawResponse is a partially-unmarshaled JSON-RPC response. For this 354 // to be valid (according to JSON-RPC 1.0 spec), ID may not be nil. 355 type rawResponse struct { 356 Result json.RawMessage `json:"result"` 357 Error *btcjson.RPCError `json:"error"` 358 } 359 360 // response is the raw bytes of a JSON-RPC result, or the error if the response 361 // error object was non-null. 362 type response struct { 363 result []byte 364 err error 365 } 366 367 // result checks whether the unmarshaled response contains a non-nil error, 368 // returning an unmarshaled btcjson.RPCError (or an unmarshaling error) if so. 369 // If the response is not an error, the raw bytes of the request are 370 // returned for further unmashaling into specific result types. 371 func (r rawResponse) result() (result []byte, err error) { 372 if r.Error != nil { 373 return nil, r.Error 374 } 375 return r.Result, nil 376 } 377 378 // handleMessage is the main handler for incoming notifications and responses. 379 func (c *Client) handleMessage(msg []byte) { 380 // Attempt to unmarshal the message as either a notification or 381 // response. 382 var in inMessage 383 in.rawResponse = new(rawResponse) 384 in.rawNotification = new(rawNotification) 385 err := json.Unmarshal(msg, &in) 386 if err != nil { 387 log.Warnf("Remote server sent invalid message: %v", err) 388 return 389 } 390 391 // JSON-RPC 1.0 notifications are requests with a null id. 392 if in.ID == nil { 393 ntfn := in.rawNotification 394 if ntfn == nil { 395 log.Warn("Malformed notification: missing " + 396 "method and parameters") 397 return 398 } 399 if ntfn.Method == "" { 400 log.Warn("Malformed notification: missing method") 401 return 402 } 403 // params are not optional: nil isn't valid (but len == 0 is) 404 if ntfn.Params == nil { 405 log.Warn("Malformed notification: missing params") 406 return 407 } 408 // Deliver the notification. 409 log.Tracef("Received notification [%s]", in.Method) 410 c.handleNotification(in.rawNotification) 411 return 412 } 413 414 // ensure that in.ID can be converted to an integer without loss of precision 415 if *in.ID < 0 || *in.ID != math.Trunc(*in.ID) { 416 log.Warn("Malformed response: invalid identifier") 417 return 418 } 419 420 if in.rawResponse == nil { 421 log.Warn("Malformed response: missing result and error") 422 return 423 } 424 425 id := uint64(*in.ID) 426 log.Tracef("Received response for id %d (result %s)", id, in.Result) 427 request := c.removeRequest(id) 428 429 // Nothing more to do if there is no request associated with this reply. 430 if request == nil || request.responseChan == nil { 431 log.Warnf("Received unexpected reply: %s (id %d)", in.Result, 432 id) 433 return 434 } 435 436 // Since the command was successful, examine it to see if it's a 437 // notification, and if is, add it to the notification state so it 438 // can automatically be re-established on reconnect. 439 c.trackRegisteredNtfns(request.cmd) 440 441 // Deliver the response. 442 result, err := in.rawResponse.result() 443 request.responseChan <- &response{result: result, err: err} 444 } 445 446 // shouldLogReadError returns whether or not the passed error, which is expected 447 // to have come from reading from the websocket connection in wsInHandler, 448 // should be logged. 449 func (c *Client) shouldLogReadError(err error) bool { 450 // No logging when the connetion is being forcibly disconnected. 451 select { 452 case <-c.shutdown: 453 return false 454 default: 455 } 456 457 // No logging when the connection has been disconnected. 458 if err == io.EOF { 459 return false 460 } 461 if opErr, ok := err.(*net.OpError); ok && !opErr.Temporary() { 462 return false 463 } 464 465 return true 466 } 467 468 // wsInHandler handles all incoming messages for the websocket connection 469 // associated with the client. It must be run as a goroutine. 470 func (c *Client) wsInHandler() { 471 out: 472 for { 473 // Break out of the loop once the shutdown channel has been 474 // closed. Use a non-blocking select here so we fall through 475 // otherwise. 476 select { 477 case <-c.shutdown: 478 break out 479 default: 480 } 481 482 _, msg, err := c.wsConn.ReadMessage() 483 if err != nil { 484 // Log the error if it's not due to disconnecting. 485 if c.shouldLogReadError(err) { 486 log.Errorf("Websocket receive error from "+ 487 "%s: %v", c.config.Host, err) 488 } 489 break out 490 } 491 c.handleMessage(msg) 492 } 493 494 // Ensure the connection is closed. 495 c.Disconnect() 496 c.wg.Done() 497 log.Tracef("RPC client input handler done for %s", c.config.Host) 498 } 499 500 // disconnectChan returns a copy of the current disconnect channel. The channel 501 // is read protected by the client mutex, and is safe to call while the channel 502 // is being reassigned during a reconnect. 503 func (c *Client) disconnectChan() <-chan struct{} { 504 c.mtx.Lock() 505 ch := c.disconnect 506 c.mtx.Unlock() 507 return ch 508 } 509 510 // wsOutHandler handles all outgoing messages for the websocket connection. It 511 // uses a buffered channel to serialize output messages while allowing the 512 // sender to continue running asynchronously. It must be run as a goroutine. 513 func (c *Client) wsOutHandler() { 514 out: 515 for { 516 // Send any messages ready for send until the client is 517 // disconnected closed. 518 select { 519 case msg := <-c.sendChan: 520 err := c.wsConn.WriteMessage(websocket.TextMessage, msg) 521 if err != nil { 522 c.Disconnect() 523 break out 524 } 525 526 case <-c.disconnectChan(): 527 break out 528 } 529 } 530 531 // Drain any channels before exiting so nothing is left waiting around 532 // to send. 533 cleanup: 534 for { 535 select { 536 case <-c.sendChan: 537 default: 538 break cleanup 539 } 540 } 541 c.wg.Done() 542 log.Tracef("RPC client output handler done for %s", c.config.Host) 543 } 544 545 // sendMessage sends the passed JSON to the connected server using the 546 // websocket connection. It is backed by a buffered channel, so it will not 547 // block until the send channel is full. 548 func (c *Client) sendMessage(marshalledJSON []byte) { 549 // Don't send the message if disconnected. 550 select { 551 case c.sendChan <- marshalledJSON: 552 case <-c.disconnectChan(): 553 return 554 } 555 } 556 557 // reregisterNtfns creates and sends commands needed to re-establish the current 558 // notification state associated with the client. It should only be called on 559 // on reconnect by the resendRequests function. 560 func (c *Client) reregisterNtfns() error { 561 // Nothing to do if the caller is not interested in notifications. 562 if c.ntfnHandlers == nil { 563 return nil 564 } 565 566 // In order to avoid holding the lock on the notification state for the 567 // entire time of the potentially long running RPCs issued below, make a 568 // copy of it and work from that. 569 // 570 // Also, other commands will be running concurrently which could modify 571 // the notification state (while not under the lock of course) which 572 // also register it with the remote RPC server, so this prevents double 573 // registrations. 574 c.ntfnStateLock.Lock() 575 stateCopy := c.ntfnState.Copy() 576 c.ntfnStateLock.Unlock() 577 578 // Reregister notifyblocks if needed. 579 if stateCopy.notifyBlocks { 580 log.Debugf("Reregistering [notifyblocks]") 581 if err := c.NotifyBlocks(); err != nil { 582 return err 583 } 584 } 585 586 // Reregister notifynewtransactions if needed. 587 if stateCopy.notifyNewTx || stateCopy.notifyNewTxVerbose { 588 log.Debugf("Reregistering [notifynewtransactions] (verbose=%v)", 589 stateCopy.notifyNewTxVerbose) 590 err := c.NotifyNewTransactions(stateCopy.notifyNewTxVerbose) 591 if err != nil { 592 return err 593 } 594 } 595 596 // Reregister the combination of all previously registered notifyspent 597 // outpoints in one command if needed. 598 nslen := len(stateCopy.notifySpent) 599 if nslen > 0 { 600 outpoints := make([]btcjson.OutPoint, 0, nslen) 601 for op := range stateCopy.notifySpent { 602 outpoints = append(outpoints, op) 603 } 604 log.Debugf("Reregistering [notifyspent] outpoints: %v", outpoints) 605 if err := c.notifySpentInternal(outpoints).Receive(); err != nil { 606 return err 607 } 608 } 609 610 // Reregister the combination of all previously registered 611 // notifyreceived addresses in one command if needed. 612 nrlen := len(stateCopy.notifyReceived) 613 if nrlen > 0 { 614 addresses := make([]string, 0, nrlen) 615 for addr := range stateCopy.notifyReceived { 616 addresses = append(addresses, addr) 617 } 618 log.Debugf("Reregistering [notifyreceived] addresses: %v", addresses) 619 if err := c.notifyReceivedInternal(addresses).Receive(); err != nil { 620 return err 621 } 622 } 623 624 return nil 625 } 626 627 // ignoreResends is a set of all methods for requests that are "long running" 628 // are not be reissued by the client on reconnect. 629 var ignoreResends = map[string]struct{}{ 630 "rescan": {}, 631 } 632 633 // resendRequests resends any requests that had not completed when the client 634 // disconnected. It is intended to be called once the client has reconnected as 635 // a separate goroutine. 636 func (c *Client) resendRequests() { 637 // Set the notification state back up. If anything goes wrong, 638 // disconnect the client. 639 if err := c.reregisterNtfns(); err != nil { 640 log.Warnf("Unable to re-establish notification state: %v", err) 641 c.Disconnect() 642 return 643 } 644 645 // Since it's possible to block on send and more requests might be 646 // added by the caller while resending, make a copy of all of the 647 // requests that need to be resent now and work from the copy. This 648 // also allows the lock to be released quickly. 649 c.requestLock.Lock() 650 resendReqs := make([]*jsonRequest, 0, c.requestList.Len()) 651 var nextElem *list.Element 652 for e := c.requestList.Front(); e != nil; e = nextElem { 653 nextElem = e.Next() 654 655 jReq := e.Value.(*jsonRequest) 656 if _, ok := ignoreResends[jReq.method]; ok { 657 // If a request is not sent on reconnect, remove it 658 // from the request structures, since no reply is 659 // expected. 660 delete(c.requestMap, jReq.id) 661 c.requestList.Remove(e) 662 } else { 663 resendReqs = append(resendReqs, jReq) 664 } 665 } 666 c.requestLock.Unlock() 667 668 for _, jReq := range resendReqs { 669 // Stop resending commands if the client disconnected again 670 // since the next reconnect will handle them. 671 if c.Disconnected() { 672 return 673 } 674 675 log.Tracef("Sending command [%s] with id %d", jReq.method, 676 jReq.id) 677 c.sendMessage(jReq.marshalledJSON) 678 } 679 } 680 681 // wsReconnectHandler listens for client disconnects and automatically tries 682 // to reconnect with retry interval that scales based on the number of retries. 683 // It also resends any commands that had not completed when the client 684 // disconnected so the disconnect/reconnect process is largely transparent to 685 // the caller. This function is not run when the DisableAutoReconnect config 686 // options is set. 687 // 688 // This function must be run as a goroutine. 689 func (c *Client) wsReconnectHandler() { 690 out: 691 for { 692 select { 693 case <-c.disconnect: 694 // On disconnect, fallthrough to reestablish the 695 // connection. 696 697 case <-c.shutdown: 698 break out 699 } 700 701 reconnect: 702 for { 703 select { 704 case <-c.shutdown: 705 break out 706 default: 707 } 708 709 wsConn, err := dial(c.config) 710 if err != nil { 711 c.retryCount++ 712 log.Infof("Failed to connect to %s: %v", 713 c.config.Host, err) 714 715 // Scale the retry interval by the number of 716 // retries so there is a backoff up to a max 717 // of 1 minute. 718 scaledInterval := connectionRetryInterval.Nanoseconds() * c.retryCount 719 scaledDuration := time.Duration(scaledInterval) 720 if scaledDuration > time.Minute { 721 scaledDuration = time.Minute 722 } 723 log.Infof("Retrying connection to %s in "+ 724 "%s", c.config.Host, scaledDuration) 725 time.Sleep(scaledDuration) 726 continue reconnect 727 } 728 729 log.Infof("Reestablished connection to RPC server %s", 730 c.config.Host) 731 732 // Reset the version in case the backend was 733 // disconnected due to an upgrade. 734 c.backendVersionMu.Lock() 735 c.backendVersion = nil 736 c.backendVersionMu.Unlock() 737 738 // Reset the connection state and signal the reconnect 739 // has happened. 740 c.wsConn = wsConn 741 c.retryCount = 0 742 743 c.mtx.Lock() 744 c.disconnect = make(chan struct{}) 745 c.disconnected = false 746 c.mtx.Unlock() 747 748 // Start processing input and output for the 749 // new connection. 750 c.start() 751 752 // Reissue pending requests in another goroutine since 753 // the send can block. 754 go c.resendRequests() 755 756 // Break out of the reconnect loop back to wait for 757 // disconnect again. 758 break reconnect 759 } 760 } 761 c.wg.Done() 762 log.Tracef("RPC client reconnect handler done for %s", c.config.Host) 763 } 764 765 // handleSendPostMessage handles performing the passed HTTP request, reading the 766 // result, unmarshalling it, and delivering the unmarshalled result to the 767 // provided response channel. 768 func (c *Client) handleSendPostMessage(details *sendPostDetails) { 769 jReq := details.jsonRequest 770 log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id) 771 httpResponse, err := c.httpClient.Do(details.httpRequest) 772 if err != nil { 773 jReq.responseChan <- &response{err: err} 774 return 775 } 776 777 // Read the raw bytes and close the response. 778 respBytes, err := ioutil.ReadAll(httpResponse.Body) 779 httpResponse.Body.Close() 780 if err != nil { 781 err = fmt.Errorf("error reading json reply: %v", err) 782 jReq.responseChan <- &response{err: err} 783 return 784 } 785 786 // Try to unmarshal the response as a regular JSON-RPC response. 787 var resp rawResponse 788 var batchResponse json.RawMessage 789 if c.batch { 790 err = json.Unmarshal(respBytes, &batchResponse) 791 } else { 792 err = json.Unmarshal(respBytes, &resp) 793 } 794 if err != nil { 795 // When the response itself isn't a valid JSON-RPC response 796 // return an error which includes the HTTP status code and raw 797 // response bytes. 798 err = fmt.Errorf("status code: %d, response: %q", 799 httpResponse.StatusCode, string(respBytes)) 800 jReq.responseChan <- &response{err: err} 801 return 802 } 803 var res []byte 804 if c.batch { 805 // errors must be dealt with downstream since a whole request cannot 806 // "error out" other than through the status code error handled above 807 res, err = batchResponse, nil 808 } else { 809 res, err = resp.result() 810 } 811 jReq.responseChan <- &response{result: res, err: err} 812 } 813 814 // sendPostHandler handles all outgoing messages when the client is running 815 // in HTTP POST mode. It uses a buffered channel to serialize output messages 816 // while allowing the sender to continue running asynchronously. It must be run 817 // as a goroutine. 818 func (c *Client) sendPostHandler() { 819 out: 820 for { 821 // Send any messages ready for send until the shutdown channel 822 // is closed. 823 select { 824 case details := <-c.sendPostChan: 825 c.handleSendPostMessage(details) 826 827 case <-c.shutdown: 828 break out 829 } 830 } 831 832 // Drain any wait channels before exiting so nothing is left waiting 833 // around to send. 834 cleanup: 835 for { 836 select { 837 case details := <-c.sendPostChan: 838 details.jsonRequest.responseChan <- &response{ 839 result: nil, 840 err: ErrClientShutdown, 841 } 842 843 default: 844 break cleanup 845 } 846 } 847 c.wg.Done() 848 log.Tracef("RPC client send handler done for %s", c.config.Host) 849 850 } 851 852 // sendPostRequest sends the passed HTTP request to the RPC server using the 853 // HTTP client associated with the client. It is backed by a buffered channel, 854 // so it will not block until the send channel is full. 855 func (c *Client) sendPostRequest(httpReq *http.Request, jReq *jsonRequest) { 856 // Don't send the message if shutting down. 857 select { 858 case <-c.shutdown: 859 jReq.responseChan <- &response{result: nil, err: ErrClientShutdown} 860 default: 861 } 862 863 c.sendPostChan <- &sendPostDetails{ 864 jsonRequest: jReq, 865 httpRequest: httpReq, 866 } 867 } 868 869 // newFutureError returns a new future result channel that already has the 870 // passed error waitin on the channel with the reply set to nil. This is useful 871 // to easily return errors from the various Async functions. 872 func newFutureError(err error) chan *response { 873 responseChan := make(chan *response, 1) 874 responseChan <- &response{err: err} 875 return responseChan 876 } 877 878 // receiveFuture receives from the passed futureResult channel to extract a 879 // reply or any errors. The examined errors include an error in the 880 // futureResult and the error in the reply from the server. This will block 881 // until the result is available on the passed channel. 882 func receiveFuture(f chan *response) ([]byte, error) { 883 // Wait for a response on the returned channel. 884 r := <-f 885 return r.result, r.err 886 } 887 888 // sendPost sends the passed request to the server by issuing an HTTP POST 889 // request using the provided response channel for the reply. Typically a new 890 // connection is opened and closed for each command when using this method, 891 // however, the underlying HTTP client might coalesce multiple commands 892 // depending on several factors including the remote server configuration. 893 func (c *Client) sendPost(jReq *jsonRequest) { 894 // Generate a request to the configured RPC server. 895 protocol := "http" 896 if !c.config.DisableTLS { 897 protocol = "https" 898 } 899 url := protocol + "://" + c.config.Host 900 bodyReader := bytes.NewReader(jReq.marshalledJSON) 901 httpReq, err := http.NewRequest("POST", url, bodyReader) 902 if err != nil { 903 jReq.responseChan <- &response{result: nil, err: err} 904 return 905 } 906 httpReq.Close = true 907 httpReq.Header.Set("Content-Type", "application/json") 908 for key, value := range c.config.ExtraHeaders { 909 httpReq.Header.Set(key, value) 910 } 911 912 // Configure basic access authorization. 913 user, pass, err := c.config.getAuth() 914 if err != nil { 915 jReq.responseChan <- &response{result: nil, err: err} 916 return 917 } 918 httpReq.SetBasicAuth(user, pass) 919 920 log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id) 921 c.sendPostRequest(httpReq, jReq) 922 } 923 924 // sendRequest sends the passed json request to the associated server using the 925 // provided response channel for the reply. It handles both websocket and HTTP 926 // POST mode depending on the configuration of the client. 927 func (c *Client) sendRequest(jReq *jsonRequest) { 928 // Choose which marshal and send function to use depending on whether 929 // the client running in HTTP POST mode or not. When running in HTTP 930 // POST mode, the command is issued via an HTTP client. Otherwise, 931 // the command is issued via the asynchronous websocket channels. 932 if c.config.HTTPPostMode { 933 if c.batch { 934 if err := c.addRequest(jReq); err != nil { 935 log.Warn(err) 936 } 937 } else { 938 c.sendPost(jReq) 939 } 940 return 941 } 942 943 // Check whether the websocket connection has never been established, 944 // in which case the handler goroutines are not running. 945 select { 946 case <-c.connEstablished: 947 default: 948 jReq.responseChan <- &response{err: ErrClientNotConnected} 949 return 950 } 951 952 // Add the request to the internal tracking map so the response from the 953 // remote server can be properly detected and routed to the response 954 // channel. Then send the marshalled request via the websocket 955 // connection. 956 if err := c.addRequest(jReq); err != nil { 957 jReq.responseChan <- &response{err: err} 958 return 959 } 960 log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id) 961 c.sendMessage(jReq.marshalledJSON) 962 } 963 964 // sendCmd sends the passed command to the associated server and returns a 965 // response channel on which the reply will be delivered at some point in the 966 // future. It handles both websocket and HTTP POST mode depending on the 967 // configuration of the client. 968 func (c *Client) sendCmd(cmd interface{}) chan *response { 969 rpcVersion := btcjson.RpcVersion1 970 if c.batch { 971 rpcVersion = btcjson.RpcVersion2 972 } 973 // Get the method associated with the command. 974 method, err := btcjson.CmdMethod(cmd) 975 if err != nil { 976 return newFutureError(err) 977 } 978 979 // Marshal the command. 980 id := c.NextID() 981 marshalledJSON, err := btcjson.MarshalCmd(rpcVersion, id, cmd) 982 if err != nil { 983 return newFutureError(err) 984 } 985 986 // Generate the request and send it along with a channel to respond on. 987 responseChan := make(chan *response, 1) 988 jReq := &jsonRequest{ 989 id: id, 990 method: method, 991 cmd: cmd, 992 marshalledJSON: marshalledJSON, 993 responseChan: responseChan, 994 } 995 996 c.sendRequest(jReq) 997 998 return responseChan 999 } 1000 1001 // sendCmdAndWait sends the passed command to the associated server, waits 1002 // for the reply, and returns the result from it. It will return the error 1003 // field in the reply if there is one. 1004 func (c *Client) sendCmdAndWait(cmd interface{}) (interface{}, error) { 1005 // Marshal the command to JSON-RPC, send it to the connected server, and 1006 // wait for a response on the returned channel. 1007 return receiveFuture(c.sendCmd(cmd)) 1008 } 1009 1010 // Disconnected returns whether or not the server is disconnected. If a 1011 // websocket client was created but never connected, this also returns false. 1012 func (c *Client) Disconnected() bool { 1013 c.mtx.Lock() 1014 defer c.mtx.Unlock() 1015 1016 select { 1017 case <-c.connEstablished: 1018 return c.disconnected 1019 default: 1020 return false 1021 } 1022 } 1023 1024 // doDisconnect disconnects the websocket associated with the client if it 1025 // hasn't already been disconnected. It will return false if the disconnect is 1026 // not needed or the client is running in HTTP POST mode. 1027 // 1028 // This function is safe for concurrent access. 1029 func (c *Client) doDisconnect() bool { 1030 if c.config.HTTPPostMode { 1031 return false 1032 } 1033 1034 c.mtx.Lock() 1035 defer c.mtx.Unlock() 1036 1037 // Nothing to do if already disconnected. 1038 if c.disconnected { 1039 return false 1040 } 1041 1042 log.Tracef("Disconnecting RPC client %s", c.config.Host) 1043 close(c.disconnect) 1044 if c.wsConn != nil { 1045 c.wsConn.Close() 1046 } 1047 c.disconnected = true 1048 return true 1049 } 1050 1051 // doShutdown closes the shutdown channel and logs the shutdown unless shutdown 1052 // is already in progress. It will return false if the shutdown is not needed. 1053 // 1054 // This function is safe for concurrent access. 1055 func (c *Client) doShutdown() bool { 1056 // Ignore the shutdown request if the client is already in the process 1057 // of shutting down or already shutdown. 1058 select { 1059 case <-c.shutdown: 1060 return false 1061 default: 1062 } 1063 1064 log.Tracef("Shutting down RPC client %s", c.config.Host) 1065 close(c.shutdown) 1066 return true 1067 } 1068 1069 // Disconnect disconnects the current websocket associated with the client. The 1070 // connection will automatically be re-established unless the client was 1071 // created with the DisableAutoReconnect flag. 1072 // 1073 // This function has no effect when the client is running in HTTP POST mode. 1074 func (c *Client) Disconnect() { 1075 // Nothing to do if already disconnected or running in HTTP POST mode. 1076 if !c.doDisconnect() { 1077 return 1078 } 1079 1080 c.requestLock.Lock() 1081 defer c.requestLock.Unlock() 1082 1083 // When operating without auto reconnect, send errors to any pending 1084 // requests and shutdown the client. 1085 if c.config.DisableAutoReconnect { 1086 for e := c.requestList.Front(); e != nil; e = e.Next() { 1087 req := e.Value.(*jsonRequest) 1088 req.responseChan <- &response{ 1089 result: nil, 1090 err: ErrClientDisconnect, 1091 } 1092 } 1093 c.removeAllRequests() 1094 c.doShutdown() 1095 } 1096 } 1097 1098 // Shutdown shuts down the client by disconnecting any connections associated 1099 // with the client and, when automatic reconnect is enabled, preventing future 1100 // attempts to reconnect. It also stops all goroutines. 1101 func (c *Client) Shutdown() { 1102 // Do the shutdown under the request lock to prevent clients from 1103 // adding new requests while the client shutdown process is initiated. 1104 c.requestLock.Lock() 1105 defer c.requestLock.Unlock() 1106 1107 // Ignore the shutdown request if the client is already in the process 1108 // of shutting down or already shutdown. 1109 if !c.doShutdown() { 1110 return 1111 } 1112 1113 // Send the ErrClientShutdown error to any pending requests. 1114 for e := c.requestList.Front(); e != nil; e = e.Next() { 1115 req := e.Value.(*jsonRequest) 1116 req.responseChan <- &response{ 1117 result: nil, 1118 err: ErrClientShutdown, 1119 } 1120 } 1121 c.removeAllRequests() 1122 1123 // Disconnect the client if needed. 1124 c.doDisconnect() 1125 } 1126 1127 // start begins processing input and output messages. 1128 func (c *Client) start() { 1129 log.Tracef("Starting RPC client %s", c.config.Host) 1130 1131 // Start the I/O processing handlers depending on whether the client is 1132 // in HTTP POST mode or the default websocket mode. 1133 if c.config.HTTPPostMode { 1134 c.wg.Add(1) 1135 go c.sendPostHandler() 1136 } else { 1137 c.wg.Add(3) 1138 go func() { 1139 if c.ntfnHandlers != nil { 1140 if c.ntfnHandlers.OnClientConnected != nil { 1141 c.ntfnHandlers.OnClientConnected() 1142 } 1143 } 1144 c.wg.Done() 1145 }() 1146 go c.wsInHandler() 1147 go c.wsOutHandler() 1148 } 1149 } 1150 1151 // WaitForShutdown blocks until the client goroutines are stopped and the 1152 // connection is closed. 1153 func (c *Client) WaitForShutdown() { 1154 c.wg.Wait() 1155 } 1156 1157 // ConnConfig describes the connection configuration parameters for the client. 1158 // This 1159 type ConnConfig struct { 1160 // Host is the IP address and port of the RPC server you want to connect 1161 // to. 1162 Host string 1163 1164 // Endpoint is the websocket endpoint on the RPC server. This is 1165 // typically "ws". 1166 Endpoint string 1167 1168 // User is the username to use to authenticate to the RPC server. 1169 User string 1170 1171 // Pass is the passphrase to use to authenticate to the RPC server. 1172 Pass string 1173 1174 // CookiePath is the path to a cookie file containing the username and 1175 // passphrase to use to authenticate to the RPC server. It is used 1176 // instead of User and Pass if non-empty. 1177 CookiePath string 1178 1179 cookieLastCheckTime time.Time 1180 cookieLastModTime time.Time 1181 cookieLastUser string 1182 cookieLastPass string 1183 cookieLastErr error 1184 1185 // Params is the string representing the network that the server 1186 // is running. If there is no parameter set in the config, then 1187 // mainnet will be used by default. 1188 Params string 1189 1190 // DisableTLS specifies whether transport layer security should be 1191 // disabled. It is recommended to always use TLS if the RPC server 1192 // supports it as otherwise your username and password is sent across 1193 // the wire in cleartext. 1194 DisableTLS bool 1195 1196 // Certificates are the bytes for a PEM-encoded certificate chain used 1197 // for the TLS connection. It has no effect if the DisableTLS parameter 1198 // is true. 1199 Certificates []byte 1200 1201 // Proxy specifies to connect through a SOCKS 5 proxy server. It may 1202 // be an empty string if a proxy is not required. 1203 Proxy string 1204 1205 // ProxyUser is an optional username to use for the proxy server if it 1206 // requires authentication. It has no effect if the Proxy parameter 1207 // is not set. 1208 ProxyUser string 1209 1210 // ProxyPass is an optional password to use for the proxy server if it 1211 // requires authentication. It has no effect if the Proxy parameter 1212 // is not set. 1213 ProxyPass string 1214 1215 // DisableAutoReconnect specifies the client should not automatically 1216 // try to reconnect to the server when it has been disconnected. 1217 DisableAutoReconnect bool 1218 1219 // DisableConnectOnNew specifies that a websocket client connection 1220 // should not be tried when creating the client with New. Instead, the 1221 // client is created and returned unconnected, and Connect must be 1222 // called manually. 1223 DisableConnectOnNew bool 1224 1225 // HTTPPostMode instructs the client to run using multiple independent 1226 // connections issuing HTTP POST requests instead of using the default 1227 // of websockets. Websockets are generally preferred as some of the 1228 // features of the client such notifications only work with websockets, 1229 // however, not all servers support the websocket extensions, so this 1230 // flag can be set to true to use basic HTTP POST requests instead. 1231 HTTPPostMode bool 1232 1233 // ExtraHeaders specifies the extra headers when perform request. It's 1234 // useful when RPC provider need customized headers. 1235 ExtraHeaders map[string]string 1236 1237 // EnableBCInfoHacks is an option provided to enable compatibility hacks 1238 // when connecting to blockchain.info RPC server 1239 EnableBCInfoHacks bool 1240 } 1241 1242 // getAuth returns the username and passphrase that will actually be used for 1243 // this connection. This will be the result of checking the cookie if a cookie 1244 // path is configured; if not, it will be the user-configured username and 1245 // passphrase. 1246 func (config *ConnConfig) getAuth() (username, passphrase string, err error) { 1247 // Try username+passphrase auth first. 1248 if config.Pass != "" { 1249 return config.User, config.Pass, nil 1250 } 1251 1252 // If no username or passphrase is set, try cookie auth. 1253 return config.retrieveCookie() 1254 } 1255 1256 // retrieveCookie returns the cookie username and passphrase. 1257 func (config *ConnConfig) retrieveCookie() (username, passphrase string, err error) { 1258 if !config.cookieLastCheckTime.IsZero() && time.Now().Before(config.cookieLastCheckTime.Add(30*time.Second)) { 1259 return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr 1260 } 1261 1262 config.cookieLastCheckTime = time.Now() 1263 1264 st, err := os.Stat(config.CookiePath) 1265 if err != nil { 1266 config.cookieLastErr = err 1267 return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr 1268 } 1269 1270 modTime := st.ModTime() 1271 if !modTime.Equal(config.cookieLastModTime) { 1272 config.cookieLastModTime = modTime 1273 config.cookieLastUser, config.cookieLastPass, config.cookieLastErr = readCookieFile(config.CookiePath) 1274 } 1275 1276 return config.cookieLastUser, config.cookieLastPass, config.cookieLastErr 1277 } 1278 1279 // newHTTPClient returns a new http client that is configured according to the 1280 // proxy and TLS settings in the associated connection configuration. 1281 func newHTTPClient(config *ConnConfig) (*http.Client, error) { 1282 // Set proxy function if there is a proxy configured. 1283 var proxyFunc func(*http.Request) (*url.URL, error) 1284 if config.Proxy != "" { 1285 proxyURL, err := url.Parse(config.Proxy) 1286 if err != nil { 1287 return nil, err 1288 } 1289 proxyFunc = http.ProxyURL(proxyURL) 1290 } 1291 1292 // Configure TLS if needed. 1293 var tlsConfig *tls.Config 1294 if !config.DisableTLS { 1295 if len(config.Certificates) > 0 { 1296 pool := x509.NewCertPool() 1297 pool.AppendCertsFromPEM(config.Certificates) 1298 tlsConfig = &tls.Config{ 1299 RootCAs: pool, 1300 } 1301 } 1302 } 1303 1304 client := http.Client{ 1305 Transport: &http.Transport{ 1306 Proxy: proxyFunc, 1307 TLSClientConfig: tlsConfig, 1308 }, 1309 } 1310 1311 return &client, nil 1312 } 1313 1314 // dial opens a websocket connection using the passed connection configuration 1315 // details. 1316 func dial(config *ConnConfig) (*websocket.Conn, error) { 1317 // Setup TLS if not disabled. 1318 var tlsConfig *tls.Config 1319 var scheme = "ws" 1320 if !config.DisableTLS { 1321 tlsConfig = &tls.Config{ 1322 MinVersion: tls.VersionTLS12, 1323 } 1324 if len(config.Certificates) > 0 { 1325 pool := x509.NewCertPool() 1326 pool.AppendCertsFromPEM(config.Certificates) 1327 tlsConfig.RootCAs = pool 1328 } 1329 scheme = "wss" 1330 } 1331 1332 // Create a websocket dialer that will be used to make the connection. 1333 // It is modified by the proxy setting below as needed. 1334 dialer := websocket.Dialer{TLSClientConfig: tlsConfig} 1335 1336 // Setup the proxy if one is configured. 1337 if config.Proxy != "" { 1338 proxy := &socks.Proxy{ 1339 Addr: config.Proxy, 1340 Username: config.ProxyUser, 1341 Password: config.ProxyPass, 1342 } 1343 dialer.NetDial = proxy.Dial 1344 } 1345 1346 // The RPC server requires basic authorization, so create a custom 1347 // request header with the Authorization header set. 1348 user, pass, err := config.getAuth() 1349 if err != nil { 1350 return nil, err 1351 } 1352 login := user + ":" + pass 1353 auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login)) 1354 requestHeader := make(http.Header) 1355 requestHeader.Add("Authorization", auth) 1356 for key, value := range config.ExtraHeaders { 1357 requestHeader.Add(key, value) 1358 } 1359 1360 // Dial the connection. 1361 url := fmt.Sprintf("%s://%s/%s", scheme, config.Host, config.Endpoint) 1362 wsConn, resp, err := dialer.Dial(url, requestHeader) 1363 if err != nil { 1364 if err != websocket.ErrBadHandshake || resp == nil { 1365 return nil, err 1366 } 1367 1368 // Detect HTTP authentication error status codes. 1369 if resp.StatusCode == http.StatusUnauthorized || 1370 resp.StatusCode == http.StatusForbidden { 1371 return nil, ErrInvalidAuth 1372 } 1373 1374 // The connection was authenticated and the status response was 1375 // ok, but the websocket handshake still failed, so the endpoint 1376 // is invalid in some way. 1377 if resp.StatusCode == http.StatusOK { 1378 return nil, ErrInvalidEndpoint 1379 } 1380 1381 // Return the status text from the server if none of the special 1382 // cases above apply. 1383 return nil, errors.New(resp.Status) 1384 } 1385 return wsConn, nil 1386 } 1387 1388 // New creates a new RPC client based on the provided connection configuration 1389 // details. The notification handlers parameter may be nil if you are not 1390 // interested in receiving notifications and will be ignored if the 1391 // configuration is set to run in HTTP POST mode. 1392 func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error) { 1393 // Either open a websocket connection or create an HTTP client depending 1394 // on the HTTP POST mode. Also, set the notification handlers to nil 1395 // when running in HTTP POST mode. 1396 var wsConn *websocket.Conn 1397 var httpClient *http.Client 1398 connEstablished := make(chan struct{}) 1399 var start bool 1400 if config.HTTPPostMode { 1401 ntfnHandlers = nil 1402 start = true 1403 1404 var err error 1405 httpClient, err = newHTTPClient(config) 1406 if err != nil { 1407 return nil, err 1408 } 1409 } else { 1410 if !config.DisableConnectOnNew { 1411 var err error 1412 wsConn, err = dial(config) 1413 if err != nil { 1414 return nil, err 1415 } 1416 start = true 1417 } 1418 } 1419 1420 client := &Client{ 1421 config: config, 1422 wsConn: wsConn, 1423 httpClient: httpClient, 1424 requestMap: make(map[uint64]*list.Element), 1425 requestList: list.New(), 1426 batch: false, 1427 batchList: list.New(), 1428 ntfnHandlers: ntfnHandlers, 1429 ntfnState: newNotificationState(), 1430 sendChan: make(chan []byte, sendBufferSize), 1431 sendPostChan: make(chan *sendPostDetails, sendPostBufferSize), 1432 connEstablished: connEstablished, 1433 disconnect: make(chan struct{}), 1434 shutdown: make(chan struct{}), 1435 } 1436 1437 // Default network is mainnet, no parameters are necessary but if mainnet 1438 // is specified it will be the param 1439 switch config.Params { 1440 case "": 1441 fallthrough 1442 case chaincfg.MainNetParams.Name: 1443 client.chainParams = &chaincfg.MainNetParams 1444 case chaincfg.TestNet3Params.Name: 1445 client.chainParams = &chaincfg.TestNet3Params 1446 case chaincfg.RegressionNetParams.Name: 1447 client.chainParams = &chaincfg.RegressionNetParams 1448 case chaincfg.SimNetParams.Name: 1449 client.chainParams = &chaincfg.SimNetParams 1450 default: 1451 return nil, fmt.Errorf("rpcclient.New: Unknown chain %s", config.Params) 1452 } 1453 1454 if start { 1455 log.Infof("Established connection to RPC server %s", 1456 config.Host) 1457 close(connEstablished) 1458 client.start() 1459 if !client.config.HTTPPostMode && !client.config.DisableAutoReconnect { 1460 client.wg.Add(1) 1461 go client.wsReconnectHandler() 1462 } 1463 } 1464 1465 return client, nil 1466 } 1467 1468 // Batch is a factory that creates a client able to interact with the server using 1469 // JSON-RPC 2.0. The client is capable of accepting an arbitrary number of requests 1470 // and having the server process the all at the same time. It's compatible with both 1471 // btcd and bitcoind 1472 func NewBatch(config *ConnConfig) (*Client, error) { 1473 if !config.HTTPPostMode { 1474 return nil, errors.New("http post mode is required to use batch client") 1475 } 1476 // notification parameter is nil since notifications are not supported in POST mode. 1477 client, err := New(config, nil) 1478 if err != nil { 1479 return nil, err 1480 } 1481 client.batch = true //copy the client with changed batch setting 1482 client.start() 1483 return client, nil 1484 } 1485 1486 // Connect establishes the initial websocket connection. This is necessary when 1487 // a client was created after setting the DisableConnectOnNew field of the 1488 // Config struct. 1489 // 1490 // Up to tries number of connections (each after an increasing backoff) will 1491 // be tried if the connection can not be established. The special value of 0 1492 // indicates an unlimited number of connection attempts. 1493 // 1494 // This method will error if the client is not configured for websockets, if the 1495 // connection has already been established, or if none of the connection 1496 // attempts were successful. 1497 func (c *Client) Connect(tries int) error { 1498 c.mtx.Lock() 1499 defer c.mtx.Unlock() 1500 1501 if c.config.HTTPPostMode { 1502 return ErrNotWebsocketClient 1503 } 1504 if c.wsConn != nil { 1505 return ErrClientAlreadyConnected 1506 } 1507 1508 // Begin connection attempts. Increase the backoff after each failed 1509 // attempt, up to a maximum of one minute. 1510 var err error 1511 var backoff time.Duration 1512 for i := 0; tries == 0 || i < tries; i++ { 1513 var wsConn *websocket.Conn 1514 wsConn, err = dial(c.config) 1515 if err != nil { 1516 backoff = connectionRetryInterval * time.Duration(i+1) 1517 if backoff > time.Minute { 1518 backoff = time.Minute 1519 } 1520 time.Sleep(backoff) 1521 continue 1522 } 1523 1524 // Connection was established. Set the websocket connection 1525 // member of the client and start the goroutines necessary 1526 // to run the client. 1527 log.Infof("Established connection to RPC server %s", 1528 c.config.Host) 1529 c.wsConn = wsConn 1530 close(c.connEstablished) 1531 c.start() 1532 if !c.config.DisableAutoReconnect { 1533 c.wg.Add(1) 1534 go c.wsReconnectHandler() 1535 } 1536 return nil 1537 } 1538 1539 // All connection attempts failed, so return the last error. 1540 return err 1541 } 1542 1543 const ( 1544 // bitcoind19Str is the string representation of bitcoind v0.19.0. 1545 bitcoind19Str = "0.19.0" 1546 1547 // bitcoindVersionPrefix specifies the prefix included in every bitcoind 1548 // version exposed through GetNetworkInfo. 1549 bitcoindVersionPrefix = "/Satoshi:" 1550 1551 // bitcoindVersionSuffix specifies the suffix included in every bitcoind 1552 // version exposed through GetNetworkInfo. 1553 bitcoindVersionSuffix = "/" 1554 ) 1555 1556 // parseBitcoindVersion parses the bitcoind version from its string 1557 // representation. 1558 func parseBitcoindVersion(version string) BackendVersion { 1559 // Trim the version of its prefix and suffix to determine the 1560 // appropriate version number. 1561 version = strings.TrimPrefix( 1562 strings.TrimSuffix(version, bitcoindVersionSuffix), 1563 bitcoindVersionPrefix, 1564 ) 1565 switch { 1566 case version < bitcoind19Str: 1567 return BitcoindPre19 1568 default: 1569 return BitcoindPost19 1570 } 1571 } 1572 1573 // BackendVersion retrieves the version of the backend the client is currently 1574 // connected to. 1575 func (c *Client) BackendVersion() (BackendVersion, error) { 1576 c.backendVersionMu.Lock() 1577 defer c.backendVersionMu.Unlock() 1578 1579 if c.backendVersion != nil { 1580 return *c.backendVersion, nil 1581 } 1582 1583 // We'll start by calling GetInfo. This method doesn't exist for 1584 // bitcoind nodes as of v0.16.0, so we'll assume the client is connected 1585 // to a btcd backend if it does exist. 1586 info, err := c.GetInfo() 1587 1588 switch err := err.(type) { 1589 // Parse the btcd version and cache it. 1590 case nil: 1591 log.Debugf("Detected btcd version: %v", info.Version) 1592 version := Btcd 1593 c.backendVersion = &version 1594 return *c.backendVersion, nil 1595 1596 // Inspect the RPC error to ensure the method was not found, otherwise 1597 // we actually ran into an error. 1598 case *btcjson.RPCError: 1599 if err.Code != btcjson.ErrRPCMethodNotFound.Code { 1600 return 0, fmt.Errorf("unable to detect btcd version: "+ 1601 "%v", err) 1602 } 1603 1604 default: 1605 return 0, fmt.Errorf("unable to detect btcd version: %v", err) 1606 } 1607 1608 // Since the GetInfo method was not found, we assume the client is 1609 // connected to a bitcoind backend, which exposes its version through 1610 // GetNetworkInfo. 1611 networkInfo, err := c.GetNetworkInfo() 1612 if err != nil { 1613 return 0, fmt.Errorf("unable to detect bitcoind version: %v", err) 1614 } 1615 1616 // Parse the bitcoind version and cache it. 1617 log.Debugf("Detected bitcoind version: %v", networkInfo.SubVersion) 1618 version := parseBitcoindVersion(networkInfo.SubVersion) 1619 c.backendVersion = &version 1620 1621 return *c.backendVersion, nil 1622 } 1623 1624 func (c *Client) sendAsync() FutureGetBulkResult { 1625 // convert the array of marshalled json requests to a single request we can send 1626 responseChan := make(chan *response, 1) 1627 marshalledRequest := []byte("[") 1628 for iter := c.batchList.Front(); iter != nil; iter = iter.Next() { 1629 request := iter.Value.(*jsonRequest) 1630 marshalledRequest = append(marshalledRequest, request.marshalledJSON...) 1631 marshalledRequest = append(marshalledRequest, []byte(",")...) 1632 } 1633 if len(marshalledRequest) > 0 { 1634 // removes the trailing comma to process the request individually 1635 marshalledRequest = marshalledRequest[:len(marshalledRequest)-1] 1636 } 1637 marshalledRequest = append(marshalledRequest, []byte("]")...) 1638 request := jsonRequest{ 1639 id: c.NextID(), 1640 method: "", 1641 cmd: nil, 1642 marshalledJSON: marshalledRequest, 1643 responseChan: responseChan, 1644 } 1645 c.sendPost(&request) 1646 return responseChan 1647 } 1648 1649 // Marshall's bulk requests and sends to the server 1650 // creates a response channel to receive the response 1651 func (c *Client) Send() error { 1652 // if batchlist is empty, there's nothing to send 1653 if c.batchList.Len() == 0 { 1654 return nil 1655 } 1656 1657 // clear batchlist in case of an error 1658 defer func() { 1659 c.batchList = list.New() 1660 }() 1661 1662 result, err := c.sendAsync().Receive() 1663 1664 if err != nil { 1665 return err 1666 } 1667 1668 for iter := c.batchList.Front(); iter != nil; iter = iter.Next() { 1669 var requestError error 1670 request := iter.Value.(*jsonRequest) 1671 individualResult := result[request.id] 1672 fullResult, err := json.Marshal(individualResult.Result) 1673 if err != nil { 1674 return err 1675 } 1676 1677 if individualResult.Error != nil { 1678 requestError = individualResult.Error 1679 } 1680 1681 result := response{ 1682 result: fullResult, 1683 err: requestError, 1684 } 1685 request.responseChan <- &result 1686 } 1687 return nil 1688 }