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 }