github.com/ethxdao/go-ethereum@v0.0.0-20221218102228-5ae34a9cc189/les/downloader/api.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package downloader
    18  
    19  import (
    20  	"context"
    21  	"sync"
    22  
    23  	"github.com/ethxdao/go-ethereum/event"
    24  	"github.com/ethxdao/go-ethereum/rpc"
    25  )
    26  
    27  // DownloaderAPI provides an API which gives information about the current synchronisation status.
    28  // It offers only methods that operates on data that can be available to anyone without security risks.
    29  type DownloaderAPI struct {
    30  	d                         *Downloader
    31  	mux                       *event.TypeMux
    32  	installSyncSubscription   chan chan interface{}
    33  	uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
    34  }
    35  
    36  // NewDownloaderAPI create a new PublicDownloaderAPI. The API has an internal event loop that
    37  // listens for events from the downloader through the global event mux. In case it receives one of
    38  // these events it broadcasts it to all syncing subscriptions that are installed through the
    39  // installSyncSubscription channel.
    40  func NewDownloaderAPI(d *Downloader, m *event.TypeMux) *DownloaderAPI {
    41  	api := &DownloaderAPI{
    42  		d:                         d,
    43  		mux:                       m,
    44  		installSyncSubscription:   make(chan chan interface{}),
    45  		uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
    46  	}
    47  
    48  	go api.eventLoop()
    49  
    50  	return api
    51  }
    52  
    53  // eventLoop runs a loop until the event mux closes. It will install and uninstall new
    54  // sync subscriptions and broadcasts sync status updates to the installed sync subscriptions.
    55  func (api *DownloaderAPI) eventLoop() {
    56  	var (
    57  		sub               = api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{})
    58  		syncSubscriptions = make(map[chan interface{}]struct{})
    59  	)
    60  
    61  	for {
    62  		select {
    63  		case i := <-api.installSyncSubscription:
    64  			syncSubscriptions[i] = struct{}{}
    65  		case u := <-api.uninstallSyncSubscription:
    66  			delete(syncSubscriptions, u.c)
    67  			close(u.uninstalled)
    68  		case event := <-sub.Chan():
    69  			if event == nil {
    70  				return
    71  			}
    72  
    73  			var notification interface{}
    74  			switch event.Data.(type) {
    75  			case StartEvent:
    76  				notification = &SyncingResult{
    77  					Syncing: true,
    78  					Status:  api.d.Progress(),
    79  				}
    80  			case DoneEvent, FailedEvent:
    81  				notification = false
    82  			}
    83  			// broadcast
    84  			for c := range syncSubscriptions {
    85  				c <- notification
    86  			}
    87  		}
    88  	}
    89  }
    90  
    91  // Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished.
    92  func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) {
    93  	notifier, supported := rpc.NotifierFromContext(ctx)
    94  	if !supported {
    95  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
    96  	}
    97  
    98  	rpcSub := notifier.CreateSubscription()
    99  
   100  	go func() {
   101  		statuses := make(chan interface{})
   102  		sub := api.SubscribeSyncStatus(statuses)
   103  
   104  		for {
   105  			select {
   106  			case status := <-statuses:
   107  				notifier.Notify(rpcSub.ID, status)
   108  			case <-rpcSub.Err():
   109  				sub.Unsubscribe()
   110  				return
   111  			case <-notifier.Closed():
   112  				sub.Unsubscribe()
   113  				return
   114  			}
   115  		}
   116  	}()
   117  
   118  	return rpcSub, nil
   119  }
   120  
   121  // SyncingResult provides information about the current synchronisation status for this node.
   122  type SyncingResult struct {
   123  	Syncing bool                  `json:"syncing"`
   124  	Status  ethereum.SyncProgress `json:"status"`
   125  }
   126  
   127  // uninstallSyncSubscriptionRequest uninstalls a syncing subscription in the API event loop.
   128  type uninstallSyncSubscriptionRequest struct {
   129  	c           chan interface{}
   130  	uninstalled chan interface{}
   131  }
   132  
   133  // SyncStatusSubscription represents a syncing subscription.
   134  type SyncStatusSubscription struct {
   135  	api       *DownloaderAPI   // register subscription in event loop of this api instance
   136  	c         chan interface{} // channel where events are broadcasted to
   137  	unsubOnce sync.Once        // make sure unsubscribe logic is executed once
   138  }
   139  
   140  // Unsubscribe uninstalls the subscription from the DownloadAPI event loop.
   141  // The status channel that was passed to subscribeSyncStatus isn't used anymore
   142  // after this method returns.
   143  func (s *SyncStatusSubscription) Unsubscribe() {
   144  	s.unsubOnce.Do(func() {
   145  		req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})}
   146  		s.api.uninstallSyncSubscription <- &req
   147  
   148  		for {
   149  			select {
   150  			case <-s.c:
   151  				// drop new status events until uninstall confirmation
   152  				continue
   153  			case <-req.uninstalled:
   154  				return
   155  			}
   156  		}
   157  	})
   158  }
   159  
   160  // SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates.
   161  // The given channel must receive interface values, the result can either
   162  func (api *DownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription {
   163  	api.installSyncSubscription <- status
   164  	return &SyncStatusSubscription{api: api, c: status}
   165  }