github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/plugins/dcrdata/dcrdata.go (about) 1 // Copyright (c) 2020-2021 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package dcrdata 6 7 import ( 8 "fmt" 9 "net/http" 10 "sync" 11 12 "github.com/decred/dcrd/chaincfg/v3" 13 exptypes "github.com/decred/dcrdata/v6/explorer/types" 14 pstypes "github.com/decred/dcrdata/v6/pubsub/types" 15 backend "github.com/decred/politeia/politeiad/backendv2" 16 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins" 17 "github.com/decred/politeia/politeiad/plugins/dcrdata" 18 "github.com/decred/politeia/politeiawww/wsdcrdata" 19 "github.com/decred/politeia/util" 20 ) 21 22 const ( 23 // Dcrdata http routes 24 routeBestBlock = "/api/block/best" 25 routeBlockDetails = "/api/block/{height}" 26 routeTicketPool = "/api/stake/pool/b/{hash}/full" 27 routeTxsTrimmed = "/api/txs/trimmed" 28 29 // Request headers 30 headerContentType = "Content-Type" 31 32 // Header values 33 contentTypeJSON = "application/json; charset=utf-8" 34 ) 35 36 var ( 37 _ plugins.PluginClient = (*dcrdataPlugin)(nil) 38 ) 39 40 // dcrdataPlugin is the tstore backend implementation of the dcrdata plugin. 41 // The dcrdata plugin provides and API for interacting with the dcrdata http 42 // and websocket APIs. 43 // 44 // dcrdataPlugin satisfies the plugins PluginClient interface. 45 type dcrdataPlugin struct { 46 sync.Mutex 47 activeNetParams *chaincfg.Params 48 client *http.Client 49 ws *wsdcrdata.Client 50 51 // Plugin settings 52 hostHTTP string // dcrdata HTTP host 53 hostWS string // dcrdata websocket host 54 55 // bestBlock is the cached best block height. This field is kept up 56 // to date by the websocket connection. If the websocket connection 57 // drops, the best block is marked as stale and is not marked as 58 // current again until the connection has been re-established and 59 // a new best block message is received. 60 bestBlock uint32 61 bestBlockStale bool 62 } 63 64 // bestBlockGet returns the cached best block. 65 func (p *dcrdataPlugin) bestBlockGet() uint32 { 66 p.Lock() 67 defer p.Unlock() 68 69 return p.bestBlock 70 } 71 72 // bestBlockSet sets the cached best block to a new value. 73 func (p *dcrdataPlugin) bestBlockSet(bb uint32) { 74 p.Lock() 75 defer p.Unlock() 76 77 p.bestBlock = bb 78 p.bestBlockStale = false 79 } 80 81 // bestBlockSetStale marks the cached best block as stale. 82 func (p *dcrdataPlugin) bestBlockSetStale() { 83 p.Lock() 84 defer p.Unlock() 85 86 p.bestBlockStale = true 87 } 88 89 // bestBlockIsStale returns whether the cached best block has been marked as 90 // being stale. 91 func (p *dcrdataPlugin) bestBlockIsStale() bool { 92 p.Lock() 93 defer p.Unlock() 94 95 return p.bestBlockStale 96 } 97 98 func (p *dcrdataPlugin) websocketMonitor() { 99 defer func() { 100 log.Infof("Dcrdata websocket closed") 101 }() 102 103 // Setup messages channel 104 receiver := p.ws.Receive() 105 106 for { 107 // Monitor for a new message 108 msg, ok := <-receiver 109 if !ok { 110 // Check if the websocket was shut down intentionally or was 111 // dropped unexpectedly. 112 if p.ws.Status() == wsdcrdata.StatusShutdown { 113 return 114 } 115 log.Infof("Dcrdata websocket connection unexpectedly dropped") 116 goto reconnect 117 } 118 119 // Handle new message 120 switch m := msg.Message.(type) { 121 case *exptypes.WebsocketBlock: 122 log.Debugf("WebsocketBlock: %v", m.Block.Height) 123 124 // Update cached best block 125 p.bestBlockSet(uint32(m.Block.Height)) 126 127 case *pstypes.HangUp: 128 log.Infof("Dcrdata websocket has hung up. Will reconnect.") 129 goto reconnect 130 131 case int: 132 // Ping messages are of type int 133 134 default: 135 log.Errorf("ws message of type %v unhandled: %v", 136 msg.EventId, m) 137 } 138 139 // Check for next message 140 continue 141 142 reconnect: 143 // Mark cached best block as stale 144 p.bestBlockSetStale() 145 146 // Reconnect 147 p.ws.Reconnect() 148 149 // Setup a new messages channel using the new connection. 150 receiver = p.ws.Receive() 151 152 log.Infof("Dcrdata websocket successfully reconnected") 153 } 154 } 155 156 func (p *dcrdataPlugin) websocketSetup() { 157 // Setup websocket subscriptions 158 var done bool 159 for !done { 160 // Best block 161 err := p.ws.NewBlockSubscribe() 162 if err != nil && err != wsdcrdata.ErrDuplicateSub { 163 log.Errorf("dcrdataPlugin: NewBlockSubscribe: %v", err) 164 goto reconnect 165 } 166 167 // All subscriptions setup 168 done = true 169 continue 170 171 reconnect: 172 p.ws.Reconnect() 173 } 174 175 // Monitor websocket connection 176 go p.websocketMonitor() 177 } 178 179 // Setup performs any plugin setup that is required. 180 // 181 // This function satisfies the plugins PluginClient interface. 182 func (p *dcrdataPlugin) Setup() error { 183 log.Tracef("dcrdata Setup") 184 185 // Setup dcrdata websocket subscriptions and monitoring. This is 186 // done in a go routine so setup will continue in the event that 187 // a dcrdata websocket connection was not able to be made during 188 // client initialization and reconnection attempts are required. 189 go p.websocketSetup() 190 191 return nil 192 } 193 194 // Cmd executes a plugin command. 195 // 196 // This function satisfies the plugins PluginClient interface. 197 func (p *dcrdataPlugin) Cmd(token []byte, cmd, payload string) (string, error) { 198 log.Tracef("dcrdata Cmd: %x %v %v", token, cmd, payload) 199 200 switch cmd { 201 case dcrdata.CmdBestBlock: 202 return p.cmdBestBlock(payload) 203 case dcrdata.CmdBlockDetails: 204 return p.cmdBlockDetails(payload) 205 case dcrdata.CmdTicketPool: 206 return p.cmdTicketPool(payload) 207 case dcrdata.CmdTxsTrimmed: 208 return p.cmdTxsTrimmed(payload) 209 } 210 211 return "", backend.ErrPluginCmdInvalid 212 } 213 214 // Hook executes a plugin hook. 215 // 216 // This function satisfies the plugins PluginClient interface. 217 func (p *dcrdataPlugin) Hook(h plugins.HookT, payload string) error { 218 log.Tracef("dcrdata Hook: %v", plugins.Hooks[h]) 219 220 return nil 221 } 222 223 // Fsck performs a plugin file system check. The plugin is provided with the 224 // tokens for all records in the backend. 225 // 226 // This function satisfies the plugins PluginClient interface. 227 func (p *dcrdataPlugin) Fsck(tokens [][]byte) error { 228 log.Tracef("dcrdata Fsck") 229 230 return nil 231 } 232 233 // Settings returns the plugin's settings. 234 // 235 // This function satisfies the plugins PluginClient interface. 236 func (p *dcrdataPlugin) Settings() []backend.PluginSetting { 237 log.Tracef("dcrdata Settings") 238 239 return nil 240 } 241 242 func New(settings []backend.PluginSetting, activeNetParams *chaincfg.Params) (*dcrdataPlugin, error) { 243 // Plugin setting 244 var ( 245 hostHTTP string 246 hostWS string 247 ) 248 249 // Set plugin settings to defaults. These will be overwritten if 250 // the setting was specified by the user. 251 switch activeNetParams.Name { 252 case chaincfg.MainNetParams().Name: 253 hostHTTP = dcrdata.SettingHostHTTPMainNet 254 hostWS = dcrdata.SettingHostWSMainNet 255 case chaincfg.TestNet3Params().Name: 256 hostHTTP = dcrdata.SettingHostHTTPTestNet 257 hostWS = dcrdata.SettingHostWSTestNet 258 default: 259 return nil, fmt.Errorf("unknown active net: %v", activeNetParams.Name) 260 } 261 262 // Override defaults with any passed in settings 263 for _, v := range settings { 264 switch v.Key { 265 case dcrdata.SettingKeyHostHTTP: 266 hostHTTP = v.Value 267 log.Infof("Plugin setting updated: dcrdata %v %v", 268 dcrdata.SettingKeyHostHTTP, hostHTTP) 269 270 case dcrdata.SettingKeyHostWS: 271 hostWS = v.Value 272 log.Infof("Plugin setting updated: dcrdata %v %v", 273 dcrdata.SettingKeyHostWS, hostWS) 274 275 default: 276 return nil, fmt.Errorf("invalid plugin setting '%v'", v.Key) 277 } 278 } 279 280 // Setup http client 281 log.Infof("Dcrdata HTTP host: %v", hostHTTP) 282 client, err := util.NewHTTPClient(false, "") 283 if err != nil { 284 return nil, err 285 } 286 287 // Setup websocket client 288 ws, err := wsdcrdata.New(hostWS) 289 if err != nil { 290 // Continue even if a websocket connection was not able to be 291 // made. Reconnection attempts will be made in the plugin setup. 292 log.Errorf("wsdcrdata New: %v", err) 293 } 294 295 return &dcrdataPlugin{ 296 activeNetParams: activeNetParams, 297 client: client, 298 ws: ws, 299 hostHTTP: hostHTTP, 300 hostWS: hostWS, 301 }, nil 302 }