github.com/clem109/go-ethereum@v1.8.3-0.20180316121352-fe6cf00f480a/eth/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  	ethereum "github.com/ethereum/go-ethereum"
    24  	"github.com/ethereum/go-ethereum/event"
    25  	"github.com/ethereum/go-ethereum/rpc"
    26  )
    27  
    28  // PublicDownloaderAPI provides an API which gives information about the current synchronisation status.
    29  // It offers only methods that operates on data that can be available to anyone without security risks.
    30  type PublicDownloaderAPI struct {
    31  	d                         *Downloader
    32  	mux                       *event.TypeMux
    33  	installSyncSubscription   chan chan interface{}
    34  	uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest
    35  }
    36  
    37  // NewPublicDownloaderAPI create a new PublicDownloaderAPI. The API has an internal event loop that
    38  // listens for events from the downloader through the global event mux. In case it receives one of
    39  // these events it broadcasts it to all syncing subscriptions that are installed through the
    40  // installSyncSubscription channel.
    41  func NewPublicDownloaderAPI(d *Downloader, m *event.TypeMux) *PublicDownloaderAPI {
    42  	api := &PublicDownloaderAPI{
    43  		d:   d,
    44  		mux: m,
    45  		installSyncSubscription:   make(chan chan interface{}),
    46  		uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest),
    47  	}
    48  
    49  	go api.eventLoop()
    50  
    51  	return api
    52  }
    53  
    54  // eventLoop runs an loop until the event mux closes. It will install and uninstall new
    55  // sync subscriptions and broadcasts sync status updates to the installed sync subscriptions.
    56  func (api *PublicDownloaderAPI) eventLoop() {
    57  	var (
    58  		sub               = api.mux.Subscribe(StartEvent{}, DoneEvent{}, FailedEvent{})
    59  		syncSubscriptions = make(map[chan interface{}]struct{})
    60  	)
    61  
    62  	for {
    63  		select {
    64  		case i := <-api.installSyncSubscription:
    65  			syncSubscriptions[i] = struct{}{}
    66  		case u := <-api.uninstallSyncSubscription:
    67  			delete(syncSubscriptions, u.c)
    68  			close(u.uninstalled)
    69  		case event := <-sub.Chan():
    70  			if event == nil {
    71  				return
    72  			}
    73  
    74  			var notification interface{}
    75  			switch event.Data.(type) {
    76  			case StartEvent:
    77  				notification = &SyncingResult{
    78  					Syncing: true,
    79  					Status:  api.d.Progress(),
    80  				}
    81  			case DoneEvent, FailedEvent:
    82  				notification = false
    83  			}
    84  			// broadcast
    85  			for c := range syncSubscriptions {
    86  				c <- notification
    87  			}
    88  		}
    89  	}
    90  }
    91  
    92  // Syncing provides information when this nodes starts synchronising with the Ethereum network and when it's finished.
    93  func (api *PublicDownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) {
    94  	notifier, supported := rpc.NotifierFromContext(ctx)
    95  	if !supported {
    96  		return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
    97  	}
    98  
    99  	rpcSub := notifier.CreateSubscription()
   100  
   101  	go func() {
   102  		statuses := make(chan interface{})
   103  		sub := api.SubscribeSyncStatus(statuses)
   104  
   105  		for {
   106  			select {
   107  			case status := <-statuses:
   108  				notifier.Notify(rpcSub.ID, status)
   109  			case <-rpcSub.Err():
   110  				sub.Unsubscribe()
   111  				return
   112  			case <-notifier.Closed():
   113  				sub.Unsubscribe()
   114  				return
   115  			}
   116  		}
   117  	}()
   118  
   119  	return rpcSub, nil
   120  }
   121  
   122  // SyncingResult provides information about the current synchronisation status for this node.
   123  type SyncingResult struct {
   124  	Syncing bool                  `json:"syncing"`
   125  	Status  ethereum.SyncProgress `json:"status"`
   126  }
   127  
   128  // uninstallSyncSubscriptionRequest uninstalles a syncing subscription in the API event loop.
   129  type uninstallSyncSubscriptionRequest struct {
   130  	c           chan interface{}
   131  	uninstalled chan interface{}
   132  }
   133  
   134  // SyncStatusSubscription represents a syncing subscription.
   135  type SyncStatusSubscription struct {
   136  	api       *PublicDownloaderAPI // register subscription in event loop of this api instance
   137  	c         chan interface{}     // channel where events are broadcasted to
   138  	unsubOnce sync.Once            // make sure unsubscribe logic is executed once
   139  }
   140  
   141  // Unsubscribe uninstalls the subscription from the DownloadAPI event loop.
   142  // The status channel that was passed to subscribeSyncStatus isn't used anymore
   143  // after this method returns.
   144  func (s *SyncStatusSubscription) Unsubscribe() {
   145  	s.unsubOnce.Do(func() {
   146  		req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})}
   147  		s.api.uninstallSyncSubscription <- &req
   148  
   149  		for {
   150  			select {
   151  			case <-s.c:
   152  				// drop new status events until uninstall confirmation
   153  				continue
   154  			case <-req.uninstalled:
   155  				return
   156  			}
   157  		}
   158  	})
   159  }
   160  
   161  // SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates.
   162  // The given channel must receive interface values, the result can either
   163  func (api *PublicDownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription {
   164  	api.installSyncSubscription <- status
   165  	return &SyncStatusSubscription{api: api, c: status}
   166  }