github.com/Tri-stone/burrow@v0.25.0/vent/service/consumer.go (about) 1 package service 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 8 "github.com/hyperledger/burrow/execution/exec" 9 10 "github.com/hyperledger/burrow/execution/evm/abi" 11 "github.com/hyperledger/burrow/rpc/rpcevents" 12 "github.com/hyperledger/burrow/rpc/rpcquery" 13 "github.com/hyperledger/burrow/vent/config" 14 "github.com/hyperledger/burrow/vent/logger" 15 "github.com/hyperledger/burrow/vent/sqldb" 16 "github.com/hyperledger/burrow/vent/sqlsol" 17 "github.com/hyperledger/burrow/vent/types" 18 "github.com/pkg/errors" 19 "google.golang.org/grpc" 20 "google.golang.org/grpc/connectivity" 21 ) 22 23 // Consumer contains basic configuration for consumer to run 24 type Consumer struct { 25 Config *config.VentConfig 26 Log *logger.Logger 27 Closing bool 28 DB *sqldb.SQLDB 29 GRPCConnection *grpc.ClientConn 30 // external events channel used for when vent is leveraged as a library 31 EventsChannel chan types.EventData 32 } 33 34 // NewConsumer constructs a new consumer configuration. 35 // The event channel will be passed a collection of rows generated from all of the events in a single block 36 // It will be closed by the consumer when it is finished 37 func NewConsumer(cfg *config.VentConfig, log *logger.Logger, eventChannel chan types.EventData) *Consumer { 38 return &Consumer{ 39 Config: cfg, 40 Log: log, 41 Closing: false, 42 EventsChannel: eventChannel, 43 } 44 } 45 46 // Run connects to a grpc service and subscribes to log events, 47 // then gets tables structures, maps them & parse event data. 48 // Store data in SQL event tables, it runs forever 49 func (c *Consumer) Run(projection *sqlsol.Projection, abiSpec *abi.AbiSpec, stream bool) error { 50 var err error 51 52 c.Log.Info("msg", "Connecting to Burrow gRPC server") 53 54 c.GRPCConnection, err = grpc.Dial(c.Config.GRPCAddr, grpc.WithInsecure()) 55 if err != nil { 56 return errors.Wrapf(err, "Error connecting to Burrow gRPC server at %s", c.Config.GRPCAddr) 57 } 58 defer c.GRPCConnection.Close() 59 defer close(c.EventsChannel) 60 61 // get the chain ID to compare with the one stored in the db 62 qCli := rpcquery.NewQueryClient(c.GRPCConnection) 63 chainStatus, err := qCli.Status(context.Background(), &rpcquery.StatusParam{}) 64 if err != nil { 65 return errors.Wrapf(err, "Error getting chain status") 66 } 67 68 if len(projection.EventSpec) == 0 { 69 c.Log.Info("msg", "No events specifications found") 70 return nil 71 } 72 73 c.Log.Info("msg", "Connecting to SQL database") 74 75 connection := types.SQLConnection{ 76 DBAdapter: c.Config.DBAdapter, 77 DBURL: c.Config.DBURL, 78 DBSchema: c.Config.DBSchema, 79 Log: c.Log, 80 ChainID: chainStatus.ChainID, 81 BurrowVersion: chainStatus.BurrowVersion, 82 } 83 84 c.DB, err = sqldb.NewSQLDB(connection) 85 if err != nil { 86 return fmt.Errorf("error connecting to SQL database: %v", err) 87 } 88 defer c.DB.Close() 89 90 c.Log.Info("msg", "Synchronizing config and database projection structures") 91 92 err = c.DB.SynchronizeDB(projection.Tables) 93 if err != nil { 94 return errors.Wrap(err, "Error trying to synchronize database") 95 } 96 97 // doneCh is used for sending a "done" signal from each goroutine to the main thread 98 // eventCh is used for sending received events to the main thread to be stored in the db 99 doneCh := make(chan struct{}) 100 errCh := make(chan error, 1) 101 eventCh := make(chan types.EventData) 102 103 go func() { 104 defer func() { 105 close(doneCh) 106 }() 107 108 c.Log.Info("msg", "Getting last processed block number from SQL log table") 109 110 // NOTE [Silas]: I am preserving the comment below that dates from the early days of Vent. I have looked at the 111 // bosmarmot git history and I cannot see why the original author thought that it was the case that there was 112 // no way of knowing if the last block of events was committed since the block and its associated log is 113 // committed atomically in a transaction and this is a core part of he design of Vent - in order that it does not 114 // repeat 115 116 // [ORIGINAL COMMENT] 117 // right now there is no way to know if the last block of events was completely read 118 // so we have to begin processing from the last block number stored in database 119 // and update event data if already present 120 fromBlock, err := c.DB.GetLastBlockHeight() 121 if err != nil { 122 errCh <- errors.Wrapf(err, "Error trying to get last processed block number from SQL log table") 123 return 124 } 125 126 startingBlock := fromBlock 127 // Start the block after the last one successfully committed - apart from if this is the first block 128 // We include block 0 because it is where we currently place dump/restored transactions 129 if startingBlock > 0 { 130 startingBlock++ 131 } 132 133 // setup block range to get needed blocks server side 134 cli := rpcevents.NewExecutionEventsClient(c.GRPCConnection) 135 var end *rpcevents.Bound 136 if stream { 137 end = rpcevents.StreamBound() 138 } else { 139 end = rpcevents.LatestBound() 140 } 141 142 request := &rpcevents.BlocksRequest{ 143 BlockRange: rpcevents.NewBlockRange(rpcevents.AbsoluteBound(startingBlock), end), 144 } 145 146 // gets blocks in given range based on last processed block taken from database 147 stream, err := cli.Stream(context.Background(), request) 148 if err != nil { 149 errCh <- errors.Wrapf(err, "Error connecting to block stream") 150 return 151 } 152 153 // get blocks 154 155 c.Log.Debug("msg", "Waiting for blocks...") 156 157 err = rpcevents.ConsumeBlockExecutions(stream, c.makeBlockConsumer(projection, abiSpec, eventCh)) 158 159 if err != nil { 160 if err == io.EOF { 161 c.Log.Info("msg", "EOF stream received...") 162 } else { 163 if c.Closing { 164 c.Log.Debug("msg", "GRPC connection closed") 165 } else { 166 errCh <- errors.Wrapf(err, "Error receiving blocks") 167 return 168 } 169 } 170 } 171 }() 172 173 for { 174 select { 175 // Process block events 176 case blk := <-eventCh: 177 err := c.commitBlock(projection, blk) 178 if err != nil { 179 c.Log.Info("msg", "error committing block", "err", err) 180 return err 181 } 182 183 // Await completion 184 case <-doneCh: 185 select { 186 187 // Select possible error 188 case err := <-errCh: 189 c.Log.Info("msg", "finished with error", "err", err) 190 return err 191 192 // Or fallback to success 193 default: 194 c.Log.Info("msg", "finished successfully") 195 return nil 196 } 197 } 198 } 199 } 200 201 func (c *Consumer) makeBlockConsumer(projection *sqlsol.Projection, abiSpec *abi.AbiSpec, 202 eventCh chan<- types.EventData) func(blockExecution *exec.BlockExecution) error { 203 204 return func(blockExecution *exec.BlockExecution) error { 205 if c.Closing { 206 return io.EOF 207 } 208 c.Log.Debug("msg", "Block received", "height", blockExecution.Height, "num_txs", len(blockExecution.TxExecutions)) 209 210 // set new block number 211 fromBlock := blockExecution.Height 212 213 // create a fresh new structure to store block data at this height 214 blockData := sqlsol.NewBlockData(fromBlock) 215 216 if c.Config.DBBlockTx { 217 blkRawData, err := buildBlkData(projection.Tables, blockExecution) 218 if err != nil { 219 return errors.Wrapf(err, "Error building block raw data") 220 } 221 // set row in structure 222 blockData.AddRow(types.SQLBlockTableName, blkRawData) 223 } 224 225 // get transactions for a given block 226 for _, txe := range blockExecution.TxExecutions { 227 c.Log.Debug("msg", "Getting transaction", "TxHash", txe.TxHash, "num_events", len(txe.Events)) 228 229 if c.Config.DBBlockTx { 230 txRawData, err := buildTxData(txe) 231 if err != nil { 232 return errors.Wrapf(err, "Error building tx raw data") 233 } 234 // set row in structure 235 blockData.AddRow(types.SQLTxTableName, txRawData) 236 } 237 238 // reverted transactions don't have to update event data tables 239 // so check that condition to filter them 240 if txe.Exception == nil { 241 242 // get events for a given transaction 243 for _, event := range txe.Events { 244 245 taggedEvent := event.Tagged() 246 247 // see which spec filter matches with the one in event data 248 for _, eventClass := range projection.EventSpec { 249 qry, err := eventClass.Query() 250 251 if err != nil { 252 return errors.Wrapf(err, "Error parsing query from filter string") 253 } 254 255 // there's a matching filter, add data to the rows 256 if qry.Matches(taggedEvent) { 257 258 c.Log.Info("msg", fmt.Sprintf("Matched event header: %v", event.Header), 259 "filter", eventClass.Filter) 260 261 // unpack, decode & build event data 262 eventData, err := buildEventData(projection, eventClass, event, abiSpec, c.Log) 263 if err != nil { 264 return errors.Wrapf(err, "Error building event data") 265 } 266 267 // set row in structure 268 blockData.AddRow(eventClass.TableName, eventData) 269 } 270 } 271 } 272 } 273 } 274 275 // upsert rows in specific SQL event tables and update block number 276 // store block data in SQL tables (if any) 277 if blockData.PendingRows(fromBlock) { 278 // gets block data to upsert 279 blk := blockData.Data 280 281 c.Log.Info("msg", fmt.Sprintf("Upserting rows in SQL tables %v", blk), "block", fromBlock) 282 283 eventCh <- blk 284 } 285 return nil 286 } 287 } 288 289 func (c *Consumer) commitBlock(projection *sqlsol.Projection, blockEvents types.EventData) error { 290 // upsert rows in specific SQL event tables and update block number 291 if err := c.DB.SetBlock(projection.Tables, blockEvents); err != nil { 292 return fmt.Errorf("error upserting rows in database: %v", err) 293 } 294 295 // send to the external events channel in a non-blocking manner 296 select { 297 case c.EventsChannel <- blockEvents: 298 default: 299 } 300 return nil 301 } 302 303 // Health returns the health status for the consumer 304 func (c *Consumer) Health() error { 305 if c.Closing { 306 return errors.New("closing service") 307 } 308 309 // check db status 310 if c.DB == nil { 311 return errors.New("database disconnected") 312 } 313 314 if err := c.DB.Ping(); err != nil { 315 return errors.New("database unavailable") 316 } 317 318 // check grpc connection status 319 if c.GRPCConnection == nil { 320 return errors.New("grpc disconnected") 321 } 322 323 if grpcState := c.GRPCConnection.GetState(); grpcState != connectivity.Ready { 324 return errors.New("grpc connection not ready") 325 } 326 327 return nil 328 } 329 330 // Shutdown gracefully shuts down the events consumer 331 func (c *Consumer) Shutdown() { 332 c.Log.Info("msg", "Shutting down vent consumer...") 333 c.Closing = true 334 c.GRPCConnection.Close() 335 }