github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/vent/service/consumer.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/hyperledger/burrow/encoding"
    11  	"github.com/hyperledger/burrow/logging"
    12  	"github.com/hyperledger/burrow/logging/structure"
    13  	"github.com/hyperledger/burrow/rpc/lib/jsonrpc"
    14  	"github.com/hyperledger/burrow/rpc/rpcevents"
    15  	"github.com/hyperledger/burrow/rpc/web3/ethclient"
    16  	"github.com/hyperledger/burrow/vent/chain"
    17  	"github.com/hyperledger/burrow/vent/chain/burrow"
    18  	"github.com/hyperledger/burrow/vent/chain/ethereum"
    19  	"github.com/hyperledger/burrow/vent/config"
    20  	"github.com/hyperledger/burrow/vent/sqldb"
    21  	"github.com/hyperledger/burrow/vent/sqlsol"
    22  	"github.com/hyperledger/burrow/vent/types"
    23  	"github.com/pkg/errors"
    24  	"google.golang.org/grpc/connectivity"
    25  )
    26  
    27  // Consumer contains basic configuration for consumer to run
    28  type Consumer struct {
    29  	Config *config.VentConfig
    30  	Logger *logging.Logger
    31  	DB     *sqldb.SQLDB
    32  	Chain  chain.Chain
    33  	// external events channel used for when vent is leveraged as a library
    34  	EventsChannel       chan types.EventData
    35  	Done                chan struct{}
    36  	shutdownOnce        sync.Once
    37  	LastProcessedHeight uint64
    38  }
    39  
    40  // NewConsumer constructs a new consumer configuration.
    41  // The event channel will be passed a collection of rows generated from all of the events in a single block
    42  // It will be closed by the consumer when it is finished
    43  func NewConsumer(cfg *config.VentConfig, log *logging.Logger, eventChannel chan types.EventData) *Consumer {
    44  	cfg.BlockConsumerConfig.Complete()
    45  	return &Consumer{
    46  		Config:        cfg,
    47  		Logger:        log,
    48  		EventsChannel: eventChannel,
    49  		Done:          make(chan struct{}),
    50  	}
    51  }
    52  
    53  // Run connects to a grpc service and subscribes to log events,
    54  // then gets tables structures, maps them & parse event data.
    55  // Store data in SQL event tables, it runs forever
    56  func (c *Consumer) Run(projection *sqlsol.Projection, stream bool) error {
    57  	var err error
    58  
    59  	c.Logger.InfoMsg("Connecting to Burrow gRPC server")
    60  
    61  	c.Chain, err = c.connectToChain()
    62  	if err != nil {
    63  		return errors.Wrapf(err, "Error connecting to Burrow gRPC server at %s", c.Config.ChainAddress)
    64  	}
    65  	defer c.Chain.Close()
    66  	defer close(c.EventsChannel)
    67  
    68  	abiProvider, err := NewAbiProvider(c.Config.AbiFileOrDirs, c.Chain, c.Logger)
    69  	if err != nil {
    70  		return errors.Wrapf(err, "Error loading ABIs")
    71  	}
    72  
    73  	if len(projection.Spec) == 0 {
    74  		c.Logger.InfoMsg("No events specifications found")
    75  		return nil
    76  	}
    77  
    78  	c.Logger.InfoMsg("Connecting to SQL database")
    79  
    80  	connection := types.SQLConnection{
    81  		DBAdapter: c.Config.DBAdapter,
    82  		DBURL:     c.Config.DBURL,
    83  		DBSchema:  c.Config.DBSchema,
    84  		Log:       c.Logger,
    85  	}
    86  
    87  	c.DB, err = sqldb.NewSQLDB(connection)
    88  	if err != nil {
    89  		return fmt.Errorf("error connecting to SQL database: %v", err)
    90  	}
    91  	defer c.DB.Close()
    92  
    93  	err = c.DB.Init(c.Chain.GetChainID(), c.Chain.GetVersion())
    94  	if err != nil {
    95  		return fmt.Errorf("could not clean tables after ChainID change: %v", err)
    96  	}
    97  
    98  	c.Logger.InfoMsg("Synchronizing config and database projection structures")
    99  
   100  	err = c.DB.SynchronizeDB(c.Chain.GetChainID(), projection.Tables)
   101  	if err != nil {
   102  		return errors.Wrap(err, "Error trying to synchronize database")
   103  	}
   104  
   105  	// doneCh is used for sending a "done" signal from each goroutine to the main thread
   106  	// eventCh is used for sending received events to the main thread to be stored in the db
   107  	errCh := make(chan error, 1)
   108  	eventCh := make(chan types.EventData)
   109  
   110  	go func() {
   111  		defer func() {
   112  			c.Shutdown()
   113  		}()
   114  		go c.announceEvery(c.Done)
   115  
   116  		c.Logger.InfoMsg("Getting last processed block number from SQL log table")
   117  
   118  		fromBlock, err := c.DB.LastBlockHeight(c.Chain.GetChainID())
   119  		if err != nil {
   120  			errCh <- errors.Wrapf(err, "Error trying to get last processed block number")
   121  			return
   122  		}
   123  
   124  		startingBlock := fromBlock
   125  		// Start the block after the last one successfully committed - apart from if this is the first block
   126  		// We include block 0 because it is where we currently place dump/restored transactions
   127  		if startingBlock > 0 {
   128  			startingBlock++
   129  		}
   130  
   131  		// Allows us skip historical/checkpointed state
   132  		if startingBlock < c.Config.MinimumHeight {
   133  			startingBlock = c.Config.MinimumHeight
   134  		}
   135  
   136  		// setup block range to get needed blocks server side
   137  		var end *rpcevents.Bound
   138  		if stream {
   139  			end = rpcevents.StreamBound()
   140  		} else {
   141  			end = rpcevents.LatestBound()
   142  		}
   143  
   144  		request := &rpcevents.BlocksRequest{
   145  			BlockRange: rpcevents.NewBlockRange(rpcevents.AbsoluteBound(startingBlock), end),
   146  		}
   147  
   148  		c.Logger.TraceMsg("Waiting for blocks...")
   149  
   150  		// gets blocks in given range based on last processed block taken from database
   151  		consumer := NewBlockConsumer(c.Chain.GetChainID(), projection, c.Config.SpecOpt, abiProvider.GetEventAbi,
   152  			eventCh, c.Done, c.Logger)
   153  
   154  		err = c.Chain.ConsumeBlocks(context.Background(), request.BlockRange, consumer)
   155  
   156  		if err != nil {
   157  			if err == io.EOF {
   158  				c.Logger.InfoMsg("EOF stream received...")
   159  			} else {
   160  				if finished(c.Done) {
   161  					c.Logger.TraceMsg("GRPC connection closed")
   162  				} else {
   163  					errCh <- errors.Wrapf(err, "Error receiving blocks")
   164  					return
   165  				}
   166  			}
   167  		}
   168  	}()
   169  
   170  	for {
   171  		select {
   172  		// Process block events
   173  		case blk := <-eventCh:
   174  			c.LastProcessedHeight = blk.BlockHeight
   175  			err := c.commitBlock(projection, blk)
   176  			if err != nil {
   177  				c.Logger.InfoMsg("error committing block", "err", err)
   178  				return err
   179  			}
   180  
   181  		// Await completion
   182  		case <-c.Done:
   183  			select {
   184  
   185  			// Select possible error
   186  			case err := <-errCh:
   187  				c.Logger.InfoMsg("finished with error", "err", err)
   188  				return err
   189  
   190  			// Or fallback to success
   191  			default:
   192  				c.Logger.InfoMsg("finished successfully")
   193  				return nil
   194  			}
   195  		}
   196  	}
   197  }
   198  
   199  func (c *Consumer) commitBlock(projection *sqlsol.Projection, blockEvents types.EventData) error {
   200  	// upsert rows in specific SQL event tables and update block number
   201  	if err := c.DB.SetBlock(c.Chain.GetChainID(), projection.Tables, blockEvents); err != nil {
   202  		return fmt.Errorf("error upserting rows in database: %v", err)
   203  	}
   204  
   205  	// send to the external events channel in a non-blocking manner
   206  	select {
   207  	case c.EventsChannel <- blockEvents:
   208  	default:
   209  	}
   210  	return nil
   211  }
   212  
   213  // Health returns the health status for the consumer
   214  func (c *Consumer) Health() error {
   215  	if finished(c.Done) {
   216  		return errors.New("closing service")
   217  	}
   218  
   219  	// check db status
   220  	if c.DB == nil {
   221  		return errors.New("database disconnected")
   222  	}
   223  
   224  	if err := c.DB.Ping(); err != nil {
   225  		return errors.New("database unavailable")
   226  	}
   227  
   228  	// check grpc connection status
   229  	if c.Chain == nil {
   230  		return errors.New("grpc disconnected")
   231  	}
   232  
   233  	if grpcState := c.Chain.Connectivity(); grpcState != connectivity.Ready {
   234  		return errors.New("grpc connection not ready")
   235  	}
   236  
   237  	return nil
   238  }
   239  
   240  // Shutdown gracefully shuts down the events consumer
   241  func (c *Consumer) Shutdown() {
   242  	c.shutdownOnce.Do(func() {
   243  		c.Logger.InfoMsg("Shutting down vent consumer...")
   244  		close(c.Done)
   245  		err := c.Chain.Close()
   246  		if err != nil {
   247  			c.Logger.InfoMsg("Could not close Chain connection", structure.ErrorKey, err)
   248  		}
   249  	})
   250  }
   251  
   252  func (c *Consumer) StatusMessage(ctx context.Context) []interface{} {
   253  	return c.Chain.StatusMessage(context.Background(), c.LastProcessedHeight)
   254  }
   255  
   256  func (c *Consumer) announceEvery(doneCh <-chan struct{}) {
   257  	if c.Config.AnnounceEvery != 0 {
   258  		ticker := time.NewTicker(c.Config.AnnounceEvery)
   259  		for {
   260  			select {
   261  			case <-ticker.C:
   262  				c.Logger.InfoMsg("Announcement", c.StatusMessage(context.Background())...)
   263  			case <-doneCh:
   264  				ticker.Stop()
   265  				return
   266  			}
   267  		}
   268  	}
   269  }
   270  
   271  func finished(doneCh chan struct{}) bool {
   272  	select {
   273  	case <-doneCh:
   274  		return true
   275  	default:
   276  		return false
   277  	}
   278  }
   279  
   280  func (c *Consumer) connectToChain() (chain.Chain, error) {
   281  	filter := &chain.Filter{
   282  		Addresses: c.Config.WatchAddresses,
   283  	}
   284  	c.Logger.InfoMsg("Attempting to detect chain type", "chain_address", c.Config.ChainAddress)
   285  	burrowChain, burrowErr := dialBurrow(c.Config.ChainAddress, filter)
   286  	if burrowErr == nil {
   287  		return burrowChain, nil
   288  	}
   289  	ethChain, ethErr := dialEthereum(c.Config.ChainAddress, filter, &c.Config.BlockConsumerConfig, c.Logger)
   290  	if ethErr != nil {
   291  		return nil, fmt.Errorf("could not connect to either Burrow or Ethereum chain, "+
   292  			"Burrow error: %v, Ethereum error: %v", burrowErr, ethErr)
   293  	}
   294  	return ethChain, nil
   295  }
   296  
   297  func dialBurrow(chainAddress string, filter *chain.Filter) (*burrow.Chain, error) {
   298  	conn, err := encoding.GRPCDial(chainAddress)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  	return burrow.New(conn, filter)
   303  }
   304  
   305  func dialEthereum(chainAddress string, filter *chain.Filter, consumerConfig *chain.BlockConsumerConfig,
   306  	logger *logging.Logger) (*ethereum.Chain, error) {
   307  	client := ethclient.NewEthClient(jsonrpc.NewClient(chainAddress))
   308  	return ethereum.New(client, filter, consumerConfig, logger)
   309  }