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 }