github.com/ethereum/go-ethereum@v1.16.1/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 "time" 23 24 "github.com/ethereum/go-ethereum" 25 "github.com/ethereum/go-ethereum/core" 26 "github.com/ethereum/go-ethereum/event" 27 "github.com/ethereum/go-ethereum/rpc" 28 ) 29 30 // DownloaderAPI provides an API which gives information about the current 31 // synchronisation status. It offers only methods that operates on data that 32 // can be available to anyone without security risks. 33 type DownloaderAPI struct { 34 d *Downloader 35 chain *core.BlockChain 36 mux *event.TypeMux 37 installSyncSubscription chan chan interface{} 38 uninstallSyncSubscription chan *uninstallSyncSubscriptionRequest 39 } 40 41 // NewDownloaderAPI creates a new DownloaderAPI. The API has an internal event loop that 42 // listens for events from the downloader through the global event mux. In case it receives one of 43 // these events it broadcasts it to all syncing subscriptions that are installed through the 44 // installSyncSubscription channel. 45 func NewDownloaderAPI(d *Downloader, chain *core.BlockChain, m *event.TypeMux) *DownloaderAPI { 46 api := &DownloaderAPI{ 47 d: d, 48 chain: chain, 49 mux: m, 50 installSyncSubscription: make(chan chan interface{}), 51 uninstallSyncSubscription: make(chan *uninstallSyncSubscriptionRequest), 52 } 53 go api.eventLoop() 54 return api 55 } 56 57 // eventLoop runs a loop until the event mux closes. It will install and uninstall 58 // new sync subscriptions and broadcasts sync status updates to the installed sync 59 // subscriptions. 60 // 61 // The sync status pushed to subscriptions can be a stream like: 62 // >>> {Syncing: true, Progress: {...}} 63 // >>> {false} 64 // 65 // If the node is already synced up, then only a single event subscribers will 66 // receive is {false}. 67 func (api *DownloaderAPI) eventLoop() { 68 var ( 69 sub = api.mux.Subscribe(StartEvent{}) 70 syncSubscriptions = make(map[chan interface{}]struct{}) 71 checkInterval = time.Second * 60 72 checkTimer = time.NewTimer(checkInterval) 73 74 // status flags 75 started bool 76 done bool 77 78 getProgress = func() ethereum.SyncProgress { 79 prog := api.d.Progress() 80 if txProg, err := api.chain.TxIndexProgress(); err == nil { 81 prog.TxIndexFinishedBlocks = txProg.Indexed 82 prog.TxIndexRemainingBlocks = txProg.Remaining 83 } 84 remain, err := api.chain.StateIndexProgress() 85 if err == nil { 86 prog.StateIndexRemaining = remain 87 } 88 return prog 89 } 90 ) 91 defer checkTimer.Stop() 92 93 for { 94 select { 95 case i := <-api.installSyncSubscription: 96 syncSubscriptions[i] = struct{}{} 97 if done { 98 i <- false 99 } 100 case u := <-api.uninstallSyncSubscription: 101 delete(syncSubscriptions, u.c) 102 close(u.uninstalled) 103 case event := <-sub.Chan(): 104 if event == nil { 105 return 106 } 107 switch event.Data.(type) { 108 case StartEvent: 109 started = true 110 } 111 case <-checkTimer.C: 112 if !started { 113 checkTimer.Reset(checkInterval) 114 continue 115 } 116 prog := getProgress() 117 if !prog.Done() { 118 notification := &SyncingResult{ 119 Syncing: true, 120 Status: prog, 121 } 122 for c := range syncSubscriptions { 123 c <- notification 124 } 125 checkTimer.Reset(checkInterval) 126 continue 127 } 128 for c := range syncSubscriptions { 129 c <- false 130 } 131 done = true 132 } 133 } 134 } 135 136 // Syncing provides information when this node starts synchronising with the Ethereum network and when it's finished. 137 func (api *DownloaderAPI) Syncing(ctx context.Context) (*rpc.Subscription, error) { 138 notifier, supported := rpc.NotifierFromContext(ctx) 139 if !supported { 140 return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported 141 } 142 143 rpcSub := notifier.CreateSubscription() 144 145 go func() { 146 statuses := make(chan interface{}) 147 sub := api.SubscribeSyncStatus(statuses) 148 defer sub.Unsubscribe() 149 150 for { 151 select { 152 case status := <-statuses: 153 notifier.Notify(rpcSub.ID, status) 154 case <-rpcSub.Err(): 155 return 156 } 157 } 158 }() 159 160 return rpcSub, nil 161 } 162 163 // SyncingResult provides information about the current synchronisation status for this node. 164 type SyncingResult struct { 165 Syncing bool `json:"syncing"` 166 Status ethereum.SyncProgress `json:"status"` 167 } 168 169 // uninstallSyncSubscriptionRequest uninstalls a syncing subscription in the API event loop. 170 type uninstallSyncSubscriptionRequest struct { 171 c chan interface{} 172 uninstalled chan interface{} 173 } 174 175 // SyncStatusSubscription represents a syncing subscription. 176 type SyncStatusSubscription struct { 177 api *DownloaderAPI // register subscription in event loop of this api instance 178 c chan interface{} // channel where events are broadcasted to 179 unsubOnce sync.Once // make sure unsubscribe logic is executed once 180 } 181 182 // Unsubscribe uninstalls the subscription from the DownloadAPI event loop. 183 // The status channel that was passed to subscribeSyncStatus isn't used anymore 184 // after this method returns. 185 func (s *SyncStatusSubscription) Unsubscribe() { 186 s.unsubOnce.Do(func() { 187 req := uninstallSyncSubscriptionRequest{s.c, make(chan interface{})} 188 s.api.uninstallSyncSubscription <- &req 189 190 for { 191 select { 192 case <-s.c: 193 // drop new status events until uninstall confirmation 194 continue 195 case <-req.uninstalled: 196 return 197 } 198 } 199 }) 200 } 201 202 // SubscribeSyncStatus creates a subscription that will broadcast new synchronisation updates. 203 // The given channel must receive interface values, the result can either. 204 func (api *DownloaderAPI) SubscribeSyncStatus(status chan interface{}) *SyncStatusSubscription { 205 api.installSyncSubscription <- status 206 return &SyncStatusSubscription{api: api, c: status} 207 }