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  }