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