decred.org/dcrdex@v1.0.5/client/asset/btc/electrum/network.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  // Package electrum provides a client for an ElectrumX server. Not all methods
     5  // are implemented. For the methods and their request and response types, see
     6  // https://electrumx.readthedocs.io/en/latest/protocol-methods.html.
     7  package electrum
     8  
     9  import (
    10  	"bufio"
    11  	"context"
    12  	"crypto/tls"
    13  	"encoding/json"
    14  	"errors"
    15  	"fmt"
    16  	"io"
    17  	"net"
    18  	"os"
    19  	"strconv"
    20  	"strings"
    21  	"sync"
    22  	"sync/atomic"
    23  	"time"
    24  
    25  	"github.com/decred/go-socks/socks"
    26  )
    27  
    28  // Printer is a function with the signature of a logger method.
    29  type Printer func(format string, params ...any)
    30  
    31  var (
    32  	// StdoutPrinter is a DebugLogger that uses fmt.Printf.
    33  	StdoutPrinter = Printer(func(format string, params ...any) {
    34  		fmt.Printf(format+"\n", params...) // discard the returns
    35  	})
    36  	// StderrPrinter is a DebugLogger that uses fmt.Fprintf(os.Stderr, ...).
    37  	StderrPrinter = Printer(func(format string, params ...any) {
    38  		fmt.Fprintf(os.Stderr, format+"\n", params...)
    39  	})
    40  
    41  	disabledPrinter = Printer(func(string, ...any) {})
    42  )
    43  
    44  const pingInterval = 10 * time.Second
    45  
    46  // ServerConn represents a connection to an Electrum server e.g. ElectrumX. It
    47  // is a single use type that must be replaced if the connection is lost. Use
    48  // ConnectServer to construct a ServerConn and connect to the server.
    49  type ServerConn struct {
    50  	conn   net.Conn
    51  	cancel context.CancelFunc
    52  	done   chan struct{}
    53  	proto  string
    54  	debug  Printer
    55  
    56  	reqID uint64
    57  
    58  	respHandlersMtx sync.Mutex
    59  	respHandlers    map[uint64]chan *response // reqID => requestor
    60  
    61  	ntfnHandlersMtx sync.RWMutex
    62  	ntfnHandlers    map[string][]chan []byte // method => subscribers
    63  }
    64  
    65  func (sc *ServerConn) nextID() uint64 {
    66  	return atomic.AddUint64(&sc.reqID, 1)
    67  }
    68  
    69  const newline = byte('\n')
    70  
    71  func (sc *ServerConn) listen(ctx context.Context) {
    72  	// listen is charged with sending on the response and notification channels.
    73  	// As such, only listen should close these channels, and only after the read
    74  	// loop has finished.
    75  	defer sc.cancelRequests()      // close the response chans
    76  	defer sc.deleteSubscriptions() // close the ntfn chans
    77  
    78  	reader := bufio.NewReader(io.LimitReader(sc.conn, 1<<18))
    79  
    80  	for {
    81  		if ctx.Err() != nil {
    82  			return
    83  		}
    84  		msg, err := reader.ReadBytes(newline)
    85  		if err != nil {
    86  			if ctx.Err() == nil { // unexpected
    87  				sc.debug("ReadBytes: %v", err)
    88  			}
    89  			sc.cancel()
    90  			return
    91  		}
    92  
    93  		var jsonResp response
    94  		err = json.Unmarshal(msg, &jsonResp)
    95  		if err != nil {
    96  			sc.debug("response Unmarshal error: %v", err)
    97  			continue
    98  		}
    99  
   100  		if jsonResp.Method != "" { // notification
   101  			var ntfnParams ntfnData // the ntfn payload is in the params field of a request object (!)
   102  			err = json.Unmarshal(msg, &ntfnParams)
   103  			if err != nil {
   104  				sc.debug("notification Unmarshal error: %v", err)
   105  				continue
   106  			}
   107  			for _, c := range sc.subChans(jsonResp.Method) {
   108  				select {
   109  				case c <- ntfnParams.Params:
   110  				default: // non-blocking, but consider deleting sub and closing chan
   111  				}
   112  			}
   113  			continue
   114  		}
   115  		// sc.debug(string(msg))
   116  
   117  		c := sc.responseChan(jsonResp.ID)
   118  		if c == nil {
   119  			sc.debug("Received response for unknown request ID %d", jsonResp.ID)
   120  			continue
   121  		}
   122  		c <- &jsonResp // buffered and single use => cannot block
   123  	}
   124  }
   125  
   126  func (sc *ServerConn) pinger(ctx context.Context) {
   127  	t := time.NewTicker(pingInterval)
   128  	defer t.Stop()
   129  
   130  	for {
   131  		// listen => ReadBytes cannot wait forever. Reset the read deadline for
   132  		// the next ping's response, as the ping loop is running.
   133  		err := sc.conn.SetReadDeadline(time.Now().Add(pingInterval * 5 / 4))
   134  		if err != nil {
   135  			sc.debug("SetReadDeadline: %v", err) // just dropped conn, but for debugging...
   136  			sc.cancel()
   137  			return
   138  		}
   139  		if err = sc.Ping(ctx); err != nil {
   140  			sc.debug("Ping: %v", err)
   141  			sc.cancel()
   142  			return
   143  		}
   144  
   145  		select {
   146  		case <-ctx.Done():
   147  			return
   148  		case <-t.C:
   149  		}
   150  	}
   151  }
   152  
   153  // negotiateVersion should only be called once, and before starting the listen
   154  // read loop. As such, this does not use the Request method.
   155  func (sc *ServerConn) negotiateVersion() (string, error) {
   156  	reqMsg, err := prepareRequest(sc.nextID(), "server.version", positional{"Electrum", "1.4"})
   157  	if err != nil {
   158  		return "", err
   159  	}
   160  	reqMsg = append(reqMsg, newline)
   161  
   162  	if err = sc.send(reqMsg); err != nil {
   163  		return "", err
   164  	}
   165  
   166  	err = sc.conn.SetReadDeadline(time.Now().Add(10 * time.Second))
   167  	if err != nil {
   168  		return "", err
   169  	}
   170  
   171  	reader := bufio.NewReader(io.LimitReader(sc.conn, 1<<18))
   172  	msg, err := reader.ReadBytes(newline)
   173  	if err != nil {
   174  		return "", err
   175  	}
   176  
   177  	var jsonResp response
   178  	err = json.Unmarshal(msg, &jsonResp)
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  
   183  	var vers []string // [server_software_version, protocol_version]
   184  	err = json.Unmarshal(jsonResp.Result, &vers)
   185  	if err != nil {
   186  		return "", err
   187  	}
   188  	if len(vers) != 2 {
   189  		return "", fmt.Errorf("unexpected version response: %v", vers)
   190  	}
   191  	return vers[1], nil
   192  }
   193  
   194  type ConnectOpts struct {
   195  	TLSConfig   *tls.Config // nil means plain
   196  	TorProxy    string
   197  	DebugLogger Printer
   198  }
   199  
   200  // ConnectServer connects to the electrum server at the given address. To close
   201  // the connection and shutdown ServerConn, either cancel the context or use the
   202  // Shutdown method, then wait on the channel from Done() to ensure a clean
   203  // shutdown (connection closed and all requests handled). There is no automatic
   204  // reconnection functionality, as the caller should handle dropped connections
   205  // by potentially cycling to a different server.
   206  func ConnectServer(ctx context.Context, addr string, opts *ConnectOpts) (*ServerConn, error) {
   207  	var dial func(ctx context.Context, network, addr string) (net.Conn, error)
   208  	if opts.TorProxy != "" {
   209  		proxy := &socks.Proxy{
   210  			Addr: opts.TorProxy,
   211  		}
   212  		dial = proxy.DialContext
   213  	} else {
   214  		dial = new(net.Dialer).DialContext
   215  	}
   216  
   217  	dialCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
   218  	defer cancel()
   219  	conn, err := dial(dialCtx, "tcp", addr)
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	if opts.TLSConfig != nil {
   225  		conn = tls.Client(conn, opts.TLSConfig)
   226  		err = conn.(*tls.Conn).HandshakeContext(ctx)
   227  		if err != nil {
   228  			conn.Close()
   229  			return nil, err
   230  		}
   231  	}
   232  
   233  	logger := opts.DebugLogger
   234  	if logger == nil {
   235  		logger = disabledPrinter
   236  	}
   237  
   238  	sc := &ServerConn{
   239  		conn:         conn,
   240  		done:         make(chan struct{}),
   241  		debug:        logger,
   242  		respHandlers: make(map[uint64]chan *response),
   243  		ntfnHandlers: make(map[string][]chan []byte),
   244  	}
   245  
   246  	// Wrap the context with a cancel function for internal shutdown, and so the
   247  	// user can use Shutdown, instead of cancelling the parent context.
   248  	ctx, sc.cancel = context.WithCancel(ctx)
   249  
   250  	// Negotiate protocol version.
   251  	sc.proto, err = sc.negotiateVersion()
   252  	if err != nil {
   253  		conn.Close()
   254  		return nil, err // e.g. code 1: "unsupported protocol version: 1.4"
   255  	}
   256  
   257  	sc.debug("Connected to server %s using negotiated protocol version %s",
   258  		addr, sc.proto)
   259  
   260  	go sc.listen(ctx) // must be running to receive response
   261  	go sc.pinger(ctx)
   262  
   263  	go func() {
   264  		<-ctx.Done()
   265  		conn.Close()
   266  		close(sc.done)
   267  	}()
   268  
   269  	return sc, nil
   270  }
   271  
   272  // Proto returns the electrum protocol of the connected server. e.g. "1.4.2".
   273  func (sc *ServerConn) Proto() string {
   274  	return sc.proto
   275  }
   276  
   277  // Shutdown begins shutting down the connection and request handling goroutines.
   278  // Receive on the channel from Done() to wait for shutdown to complete.
   279  func (sc *ServerConn) Shutdown() {
   280  	sc.cancel()
   281  }
   282  
   283  // Done returns a channel that is closed when the ServerConn is fully shutdown.
   284  func (sc *ServerConn) Done() <-chan struct{} {
   285  	return sc.done
   286  }
   287  
   288  func (sc *ServerConn) send(msg []byte) error {
   289  	err := sc.conn.SetWriteDeadline(time.Now().Add(7 * time.Second))
   290  	if err != nil {
   291  		return err
   292  	}
   293  	_, err = sc.conn.Write(msg)
   294  	return err
   295  }
   296  
   297  func (sc *ServerConn) registerRequest(id uint64) chan *response {
   298  	c := make(chan *response, 1)
   299  	sc.respHandlersMtx.Lock()
   300  	sc.respHandlers[id] = c
   301  	sc.respHandlersMtx.Unlock()
   302  	return c
   303  }
   304  
   305  func (sc *ServerConn) responseChan(id uint64) chan *response {
   306  	sc.respHandlersMtx.Lock()
   307  	defer sc.respHandlersMtx.Unlock()
   308  	c := sc.respHandlers[id]
   309  	delete(sc.respHandlers, id)
   310  	return c
   311  }
   312  
   313  // cancelRequests deletes all response handlers from the respHandlers map and
   314  // closes all of the channels. As such, this method MUST be called from the same
   315  // goroutine that sends on the channel.
   316  func (sc *ServerConn) cancelRequests() {
   317  	sc.respHandlersMtx.Lock()
   318  	defer sc.respHandlersMtx.Unlock()
   319  	for id, c := range sc.respHandlers {
   320  		close(c) // requester receives nil immediately
   321  		delete(sc.respHandlers, id)
   322  	}
   323  }
   324  
   325  func (sc *ServerConn) registerSub(method string) <-chan []byte {
   326  	c := make(chan []byte, 1)
   327  	sc.ntfnHandlersMtx.Lock()
   328  	sc.ntfnHandlers[method] = append(sc.ntfnHandlers[method], c)
   329  	sc.ntfnHandlersMtx.Unlock()
   330  	return c
   331  }
   332  
   333  func (sc *ServerConn) subChans(method string) []chan []byte {
   334  	sc.ntfnHandlersMtx.RLock()
   335  	defer sc.ntfnHandlersMtx.RUnlock()
   336  	return sc.ntfnHandlers[method]
   337  }
   338  
   339  // deleteSubscriptions deletes all subscriptions from the ntfnHandlers map and
   340  // closes all of the channels. As such, this method MUST be called from the same
   341  // goroutine that sends on the channel.
   342  func (sc *ServerConn) deleteSubscriptions() {
   343  	sc.ntfnHandlersMtx.Lock()
   344  	defer sc.ntfnHandlersMtx.Unlock()
   345  	for method, cs := range sc.ntfnHandlers {
   346  		for _, c := range cs {
   347  			close(c) // sub handler loop receives nil immediately
   348  		}
   349  		delete(sc.ntfnHandlers, method)
   350  	}
   351  }
   352  
   353  // Request performs a request to the remote server for the given method using
   354  // the provided arguments, which may either be positional (e.g.
   355  // []interface{arg1, arg2}), named (any struct), or nil if there are no
   356  // arguments. args may not be any other basic type. The the response does not
   357  // include an error, the result will be unmarshalled into result, unless the
   358  // provided result is nil in which case the response payload will be ignored.
   359  func (sc *ServerConn) Request(ctx context.Context, method string, args any, result any) error {
   360  	id := sc.nextID()
   361  	reqMsg, err := prepareRequest(id, method, args)
   362  	if err != nil {
   363  		return err
   364  	}
   365  	reqMsg = append(reqMsg, newline)
   366  
   367  	c := sc.registerRequest(id)
   368  
   369  	if err = sc.send(reqMsg); err != nil {
   370  		sc.cancel()
   371  		return err
   372  	}
   373  
   374  	var resp *response
   375  	select {
   376  	case <-ctx.Done():
   377  		return ctx.Err() // either timeout or canceled
   378  	case resp = <-c:
   379  	}
   380  
   381  	if resp == nil { // channel closed
   382  		return errors.New("connection terminated")
   383  	}
   384  
   385  	if resp.Error != nil {
   386  		return resp.Error
   387  	}
   388  
   389  	if result != nil {
   390  		return json.Unmarshal(resp.Result, result)
   391  	}
   392  	return nil
   393  }
   394  
   395  // Ping pings the remote server. This can be used as a connectivity test on
   396  // demand, although a ServerConn started with ConnectServer will launch a pinger
   397  // goroutine to keep the connection alive.
   398  func (sc *ServerConn) Ping(ctx context.Context) error {
   399  	return sc.Request(ctx, "server.ping", nil, nil)
   400  }
   401  
   402  // Banner retrieves the server's banner, which is any announcement set by the
   403  // server operator. It should be interpreted with caution as the content is
   404  // untrusted.
   405  func (sc *ServerConn) Banner(ctx context.Context) (string, error) {
   406  	var resp string
   407  	err := sc.Request(ctx, "server.banner", nil, &resp)
   408  	if err != nil {
   409  		return "", err
   410  	}
   411  	return resp, nil
   412  }
   413  
   414  // ServerFeatures represents the result of a server features requests.
   415  type ServerFeatures struct {
   416  	Genesis  string                       `json:"genesis_hash"`
   417  	Hosts    map[string]map[string]uint32 `json:"hosts"` // e.g. {"host.com": {"tcp_port": 51001, "ssl_port": 51002}}, may be unset!
   418  	ProtoMax string                       `json:"protocol_max"`
   419  	ProtoMin string                       `json:"protocol_min"`
   420  	Pruning  any                          `json:"pruning,omitempty"` // supposedly an integer, but maybe a string or even JSON null
   421  	Version  string                       `json:"server_version"`    // server software version, not proto
   422  	HashFunc string                       `json:"hash_function"`     // e.g. sha256
   423  	// Services []string                     `json:"services,omitempty"` // e.g. ["tcp://host.com:51001", "ssl://host.com:51002"]
   424  }
   425  
   426  // Features requests the features claimed by the server. The caller should check
   427  // the Genesis hash field to ensure it is the intended network.
   428  func (sc *ServerConn) Features(ctx context.Context) (*ServerFeatures, error) {
   429  	var feats ServerFeatures
   430  	err := sc.Request(ctx, "server.features", nil, &feats)
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	return &feats, nil
   435  }
   436  
   437  // PeersResult represents the results of a peers server request.
   438  type PeersResult struct {
   439  	Addr  string // IP address or .onion name
   440  	Host  string
   441  	Feats []string
   442  }
   443  
   444  // Peers requests the known peers from a server (other servers). See
   445  // SSLPeerAddrs to assist parsing useable peers.
   446  func (sc *ServerConn) Peers(ctx context.Context) ([]*PeersResult, error) {
   447  	// Note that the Electrum exchange wallet type does not currently use this
   448  	// method since it follows the Electrum wallet server peer or one of the
   449  	// wallets other servers. See (*electrumWallet).connect and
   450  	// (*WalletClient).GetServers. We might wish to in the future though.
   451  
   452  	// [["ip", "host", ["featA", "featB", ...]], ...]
   453  	// [][]any{string, string, []any{string, ...}}
   454  	var resp [][]any
   455  	err := sc.Request(ctx, "server.peers.subscribe", nil, &resp) // not really a subscription!
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  	peers := make([]*PeersResult, 0, len(resp))
   460  	for _, peer := range resp {
   461  		if len(peer) != 3 {
   462  			sc.debug("bad peer data: %v (%T)", peer, peer)
   463  			continue
   464  		}
   465  		addr, ok := peer[0].(string)
   466  		if !ok {
   467  			sc.debug("bad peer IP data: %v (%T)", peer[0], peer[0])
   468  			continue
   469  		}
   470  		host, ok := peer[1].(string)
   471  		if !ok {
   472  			sc.debug("bad peer hostname: %v (%T)", peer[1], peer[1])
   473  			continue
   474  		}
   475  		featsI, ok := peer[2].([]any)
   476  		if !ok {
   477  			sc.debug("bad peer feature data: %v (%T)", peer[2], peer[2])
   478  			continue
   479  		}
   480  		feats := make([]string, len(featsI))
   481  		for i, featI := range featsI {
   482  			feat, ok := featI.(string)
   483  			if !ok {
   484  				sc.debug("bad peer feature data: %v (%T)", featI, featI)
   485  				continue
   486  			}
   487  			feats[i] = feat
   488  		}
   489  		peers = append(peers, &PeersResult{
   490  			Addr:  addr,
   491  			Host:  host,
   492  			Feats: feats,
   493  		})
   494  	}
   495  	return peers, nil
   496  }
   497  
   498  // SSLPeerAddrs filters the peers slice and returns the addresses in a
   499  // "host:port" format in separate slices for SSL-enabled servers and optionally
   500  // TCP-only hidden services (.onion host names). Note that if requesting to
   501  // include onion hosts, the SSL slice may include onion hosts that also use SSL.
   502  func SSLPeerAddrs(peers []*PeersResult, includeOnion bool) (ssl, tcpOnlyOnion []string) {
   503  peerloop:
   504  	for _, peer := range peers {
   505  		isOnion := strings.HasSuffix(peer.Addr, ".onion")
   506  		if isOnion && !includeOnion {
   507  			continue
   508  		}
   509  		var tcpOnion string // host to accept if no ssl
   510  		for _, feat := range peer.Feats {
   511  			// We require a port set after the transport letter. The default
   512  			// port depends on the asset network, so we could consider providing
   513  			// that as an input in the future, but most servers set a port.
   514  			if len(feat) < 2 {
   515  				continue
   516  			}
   517  			switch []rune(feat)[0] {
   518  			case 't':
   519  				if !isOnion {
   520  					continue
   521  				}
   522  				port := feat[1:]
   523  				if _, err := strconv.Atoi(port); err != nil {
   524  					continue
   525  				}
   526  				tcpOnion = net.JoinHostPort(peer.Host, port) // hang onto this if there is no ssl
   527  			case 's':
   528  				port := feat[1:]
   529  				if _, err := strconv.Atoi(port); err != nil {
   530  					continue
   531  				}
   532  				addr := net.JoinHostPort(peer.Host, port)
   533  				ssl = append(ssl, addr) // we know the first rune is one byte
   534  				continue peerloop
   535  			}
   536  		}
   537  		if tcpOnion != "" {
   538  			tcpOnlyOnion = append(tcpOnlyOnion, tcpOnion)
   539  		}
   540  	}
   541  	return
   542  }
   543  
   544  // SigScript represents the signature script in a Vin returned by a transaction
   545  // request.
   546  type SigScript struct {
   547  	Asm string `json:"asm"` // this is not the sigScript you're looking for
   548  	Hex string `json:"hex"`
   549  }
   550  
   551  // Vin represents a transaction input in a requested transaction.
   552  type Vin struct {
   553  	TxID      string     `json:"txid"`
   554  	Vout      uint32     `json:"vout"`
   555  	SigScript *SigScript `json:"scriptsig"`
   556  	Witness   []string   `json:"txinwitness,omitempty"`
   557  	Sequence  uint32     `json:"sequence"`
   558  	Coinbase  string     `json:"coinbase,omitempty"`
   559  }
   560  
   561  // PkScript represents the pkScript/scriptPubKey of a transaction output
   562  // returned by a transaction requests.
   563  type PkScript struct {
   564  	Asm       string   `json:"asm"`
   565  	Hex       string   `json:"hex"`
   566  	ReqSigs   uint32   `json:"reqsigs"`
   567  	Type      string   `json:"type"`
   568  	Addresses []string `json:"addresses,omitempty"`
   569  }
   570  
   571  // Vout represents a transaction output in a requested transaction.
   572  type Vout struct {
   573  	Value    float64  `json:"value"`
   574  	N        uint32   `json:"n"`
   575  	PkScript PkScript `json:"scriptpubkey"`
   576  }
   577  
   578  // GetTransactionResult is the data returned by a transaction request.
   579  type GetTransactionResult struct {
   580  	TxID string `json:"txid"`
   581  	// Hash          string `json:"hash"` // ??? don't use, not always the txid! witness not stripped?
   582  	Version       uint32 `json:"version"`
   583  	Size          uint32 `json:"size"`
   584  	VSize         uint32 `json:"vsize"`
   585  	Weight        uint32 `json:"weight"`
   586  	LockTime      uint32 `json:"locktime"`
   587  	Hex           string `json:"hex"`
   588  	Vin           []Vin  `json:"vin"`
   589  	Vout          []Vout `json:"vout"`
   590  	BlockHash     string `json:"blockhash,omitempty"`
   591  	Confirmations int32  `json:"confirmations,omitempty"` // probably uint32 ok because it seems to be omitted, but could be -1?
   592  	Time          int64  `json:"time,omitempty"`
   593  	BlockTime     int64  `json:"blocktime,omitempty"` // same as Time?
   594  	// Merkel // proto 1.5+
   595  }
   596  
   597  // GetTransaction requests a transaction.
   598  func (sc *ServerConn) GetTransaction(ctx context.Context, txid string) (*GetTransactionResult, error) {
   599  	var resp GetTransactionResult
   600  	err := sc.Request(ctx, "blockchain.transaction.get", positional{txid, true}, &resp)
   601  	if err != nil {
   602  		return nil, err
   603  	}
   604  	return &resp, nil
   605  }
   606  
   607  // BlockHeader requests the block header at the given height, returning
   608  // hexadecimal encoded serialized header.
   609  func (sc *ServerConn) BlockHeader(ctx context.Context, height uint32) (string, error) {
   610  	var resp string
   611  	err := sc.Request(ctx, "blockchain.block.header", positional{height}, &resp)
   612  	if err != nil {
   613  		return "", err
   614  	}
   615  	return resp, nil
   616  }
   617  
   618  // GetBlockHeadersResult represent the result of a batch request for block
   619  // headers via the BlockHeaders method. The serialized block headers are
   620  // concatenated in the HexConcat field, which contains Count headers.
   621  type GetBlockHeadersResult struct {
   622  	Count     uint32 `json:"count"`
   623  	HexConcat string `json:"hex"`
   624  	Max       uint32 `json:"max"`
   625  }
   626  
   627  // BlockHeaders requests a batch of block headers beginning at the given height.
   628  // The sever may respond with a different number of headers, so the caller
   629  // should check the Count field of the result.
   630  func (sc *ServerConn) BlockHeaders(ctx context.Context, startHeight, count uint32) (*GetBlockHeadersResult, error) {
   631  	var resp GetBlockHeadersResult
   632  	err := sc.Request(ctx, "blockchain.block.headers", positional{startHeight, count}, &resp)
   633  	if err != nil {
   634  		return nil, err
   635  	}
   636  	return &resp, nil
   637  }
   638  
   639  // SubscribeHeadersResult is the contents of a block header notification.
   640  type SubscribeHeadersResult struct {
   641  	Height int32  `json:"height"`
   642  	Hex    string `json:"hex"`
   643  }
   644  
   645  // SubscribeHeaders subscribes for block header notifications. There seems to be
   646  // no guarantee that we will be notified of all new blocks, such as when there
   647  // are blocks in rapid succession.
   648  func (sc *ServerConn) SubscribeHeaders(ctx context.Context) (*SubscribeHeadersResult, <-chan *SubscribeHeadersResult, error) {
   649  	const method = "blockchain.headers.subscribe"
   650  	c := sc.registerSub(method)
   651  
   652  	var resp SubscribeHeadersResult
   653  	err := sc.Request(ctx, method, nil, &resp)
   654  	if err != nil {
   655  		return nil, nil, err
   656  	}
   657  
   658  	ntfnChan := make(chan *SubscribeHeadersResult, 10)
   659  
   660  	go func() {
   661  		defer close(ntfnChan)
   662  
   663  		for data := range c {
   664  			var res []*SubscribeHeadersResult
   665  			err = json.Unmarshal(data, &res)
   666  			if err != nil {
   667  				sc.debug("SubscribeHeaders - unmarshal ntfn data: %v", err)
   668  				continue
   669  			}
   670  
   671  			for _, r := range res { // should just be one, but the params are a slice...
   672  				ntfnChan <- r
   673  			}
   674  		}
   675  	}()
   676  
   677  	return &resp, ntfnChan, nil
   678  }