github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/rpc/rpcevents/execution_events_server.go (about)

     1  package rpcevents
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/hyperledger/burrow/bcm"
     9  	"github.com/hyperledger/burrow/event"
    10  	"github.com/hyperledger/burrow/event/query"
    11  	"github.com/hyperledger/burrow/execution/exec"
    12  	"github.com/hyperledger/burrow/logging"
    13  	"github.com/hyperledger/burrow/storage"
    14  )
    15  
    16  const SubscribeBufferSize = 100
    17  
    18  type Provider interface {
    19  	// Get transactions
    20  	IterateStreamEvents(startHeight, endHeight *uint64, sortOrder storage.SortOrder,
    21  		consumer func(*exec.StreamEvent) error) (err error)
    22  	// Get a particular TxExecution by hash
    23  	TxByHash(txHash []byte) (*exec.TxExecution, error)
    24  }
    25  
    26  type executionEventsServer struct {
    27  	UnimplementedExecutionEventsServer
    28  	eventsProvider Provider
    29  	emitter        *event.Emitter
    30  	tip            bcm.BlockchainInfo
    31  	logger         *logging.Logger
    32  }
    33  
    34  func NewExecutionEventsServer(eventsProvider Provider, emitter *event.Emitter,
    35  	tip bcm.BlockchainInfo, logger *logging.Logger) ExecutionEventsServer {
    36  
    37  	return &executionEventsServer{
    38  		eventsProvider: eventsProvider,
    39  		emitter:        emitter,
    40  		tip:            tip,
    41  		logger:         logger.WithScope("NewExecutionEventsServer"),
    42  	}
    43  }
    44  
    45  func (ees *executionEventsServer) Tx(ctx context.Context, request *TxRequest) (*exec.TxExecution, error) {
    46  	txe, err := ees.eventsProvider.TxByHash(request.TxHash)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  	if txe != nil {
    51  		return txe, nil
    52  	}
    53  	if !request.Wait {
    54  		return nil, fmt.Errorf("transaction with hash %v not found in state", request.TxHash)
    55  	}
    56  	subID := event.GenSubID()
    57  	out, err := ees.emitter.Subscribe(ctx, subID, exec.QueryForTxExecution(request.TxHash), SubscribeBufferSize)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	defer ees.emitter.UnsubscribeAll(ctx, subID)
    62  	for msg := range out {
    63  		select {
    64  		case <-ctx.Done():
    65  			return nil, ctx.Err()
    66  		default:
    67  			return msg.(*exec.TxExecution), nil
    68  		}
    69  	}
    70  	return nil, fmt.Errorf("subscription waiting for tx %v ended prematurely", request.TxHash)
    71  }
    72  
    73  func (ees *executionEventsServer) Stream(request *BlocksRequest, stream ExecutionEvents_StreamServer) error {
    74  	qry, err := query.NewOrEmpty(request.Query)
    75  	if err != nil {
    76  		return fmt.Errorf("could not parse TxExecution query: %v", err)
    77  	}
    78  	return ees.streamEvents(stream.Context(), request.BlockRange, func(ev *exec.StreamEvent) error {
    79  		if qry.Matches(ev) {
    80  			return stream.Send(ev)
    81  		}
    82  		return nil
    83  	})
    84  }
    85  
    86  func (ees *executionEventsServer) Events(request *BlocksRequest, stream ExecutionEvents_EventsServer) error {
    87  	const errHeader = "Events()"
    88  	qry, err := query.NewOrEmpty(request.Query)
    89  	if err != nil {
    90  		return fmt.Errorf("could not parse Event query: %v", err)
    91  	}
    92  	var response *EventsResponse
    93  	var stack exec.TxStack
    94  	return ees.streamEvents(stream.Context(), request.BlockRange, func(sev *exec.StreamEvent) error {
    95  		switch {
    96  		case sev.BeginBlock != nil:
    97  			response = &EventsResponse{
    98  				Height: sev.BeginBlock.Height,
    99  			}
   100  
   101  		case sev.EndBlock != nil && len(response.Events) > 0:
   102  			return stream.Send(response)
   103  
   104  		default:
   105  			// We need to consume transaction to exclude events belong to an exceptional transaction
   106  			txe, err := stack.Consume(sev)
   107  			if err != nil {
   108  				return fmt.Errorf("%s: %v", errHeader, err)
   109  			}
   110  			if txe != nil && txe.Exception == nil {
   111  				for _, ev := range txe.Events {
   112  					if qry.Matches(ev) {
   113  						response.Events = append(response.Events, ev)
   114  					}
   115  				}
   116  			}
   117  		}
   118  
   119  		return nil
   120  	})
   121  }
   122  
   123  func (ees *executionEventsServer) streamEvents(ctx context.Context, blockRange *BlockRange,
   124  	consumer func(execution *exec.StreamEvent) error) error {
   125  
   126  	lastBlockHeight := ees.tip.LastBlockHeight()
   127  	start, end, streaming := blockRange.Bounds(lastBlockHeight)
   128  	ees.logger.TraceMsg("Streaming blocks", "start", start, "end", end, "streaming", streaming)
   129  
   130  	// Pull blocks from state and receive the upper bound (exclusive) on the what we were able to send
   131  	// Set this to start since it will be the start of next streaming batch (if needed)
   132  	start, err := ees.iterateStreamEvents(start, end, consumer)
   133  
   134  	// If we are not streaming and all (non-empty) blocks up to and including end are available from state then we are done
   135  	if !streaming && end <= lastBlockHeight {
   136  		return err
   137  	}
   138  
   139  	return ees.subscribeBlockExecution(ctx, func(block *exec.BlockExecution) error {
   140  		if block.Height < start {
   141  			// We've managed to receive a block event we already processed directly from state above - wait for next block
   142  			return nil
   143  		}
   144  		// Check if we have missed blocks we need to catch up on
   145  		if start < block.Height {
   146  			// We expect start == block.Height when processing consecutive blocks but we may have missed a block by
   147  			// pubsub dropping an event (e.g. under heavy load) - if so we can fill in here. Since we have just
   148  			// received block at block.Height it should be guaranteed that we have stored all blocks <= block.Height
   149  			// in state (we only publish after successful state update).
   150  			catchupEnd := block.Height - 1
   151  			if catchupEnd > end {
   152  				catchupEnd = end
   153  			}
   154  			start, err = ees.iterateStreamEvents(start, catchupEnd, consumer)
   155  			if err != nil {
   156  				return err
   157  			}
   158  		}
   159  		finished := !streaming && block.Height > end
   160  		if finished {
   161  			return io.EOF
   162  		}
   163  		for _, ev := range block.StreamEvents() {
   164  			err = consumer(ev)
   165  			if err != nil {
   166  				return err
   167  			}
   168  		}
   169  		// We've just streamed block so our next start marker is the next block
   170  		start = block.Height + 1
   171  		return nil
   172  	})
   173  }
   174  
   175  func (ees *executionEventsServer) subscribeBlockExecution(ctx context.Context,
   176  	consumer func(*exec.BlockExecution) error) (err error) {
   177  	// Otherwise we need to begin streaming blocks as they are produced
   178  	subID := event.GenSubID()
   179  	// Subscribe to BlockExecution events
   180  	out, err := ees.emitter.Subscribe(ctx, subID, exec.QueryForBlockExecution(), SubscribeBufferSize)
   181  	if err != nil {
   182  		return err
   183  	}
   184  	defer func() {
   185  		err = ees.emitter.UnsubscribeAll(context.Background(), subID)
   186  		for range out {
   187  			// flush
   188  		}
   189  	}()
   190  
   191  	for msg := range out {
   192  		select {
   193  		case <-ctx.Done():
   194  			return ctx.Err()
   195  		default:
   196  			err = consumer(msg.(*exec.BlockExecution))
   197  			if err != nil {
   198  				return err
   199  			}
   200  		}
   201  	}
   202  	return nil
   203  }
   204  
   205  func (ees *executionEventsServer) iterateStreamEvents(startHeight, endHeight uint64,
   206  	consumer func(*exec.StreamEvent) error) (uint64, error) {
   207  	// Assume that we have seen the previous block before start to have ended up here
   208  	// NOTE: this will underflow when start is 0 (as it often will be - and needs to be for restored chains)
   209  	// however we at most underflow by 1 and we always add 1 back on when returning so we get away with this.
   210  	lastHeightSeen := startHeight - 1
   211  	err := ees.eventsProvider.IterateStreamEvents(&startHeight, &endHeight, storage.AscendingSort,
   212  		func(ev *exec.StreamEvent) error {
   213  			if ev.EndBlock != nil {
   214  				lastHeightSeen = ev.EndBlock.GetHeight()
   215  			}
   216  			return consumer(ev)
   217  		})
   218  	// Returns the appropriate _next_ starting block - the one after the one we have seen - from which to stream next
   219  	return lastHeightSeen + 1, err
   220  }