github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/execution/exec/stream_event.go (about) 1 package exec 2 3 import ( 4 "fmt" 5 "io" 6 7 "github.com/hyperledger/burrow/event" 8 "github.com/hyperledger/burrow/event/query" 9 ) 10 11 type EventStream interface { 12 Recv() (*StreamEvent, error) 13 } 14 15 func (ses *StreamEvents) Recv() (*StreamEvent, error) { 16 if len(ses.StreamEvents) == 0 { 17 return nil, io.EOF 18 } 19 ev := ses.StreamEvents[0] 20 ses.StreamEvents = ses.StreamEvents[1:] 21 return ev, nil 22 } 23 24 func (ev *StreamEvent) EventType() EventType { 25 switch { 26 case ev.BeginBlock != nil: 27 return TypeBeginBlock 28 case ev.BeginTx != nil: 29 return TypeBeginTx 30 case ev.Envelope != nil: 31 return TypeEnvelope 32 case ev.Event != nil: 33 return ev.Event.EventType() 34 case ev.EndTx != nil: 35 return TypeEndTx 36 case ev.EndBlock != nil: 37 return TypeEndBlock 38 case ev.Event.Print != nil: 39 return TypePrint 40 } 41 return TypeUnknown 42 } 43 44 func (ev *StreamEvent) Get(key string) (interface{}, bool) { 45 switch key { 46 case event.EventTypeKey: 47 return ev.EventType(), true 48 } 49 // Flatten this sum type 50 return query.TagsFor( 51 ev.GetBeginBlock().GetHeader(), 52 ev.BeginBlock, 53 ev.GetBeginTx().GetTxHeader(), 54 ev.BeginTx, 55 ev.Envelope, 56 ev.Event, 57 ev.EndTx, 58 ev.EndBlock).Get(key) 59 } 60 61 type ContinuityOpt byte 62 63 func (so ContinuityOpt) Allows(opt ContinuityOpt) bool { 64 return so&opt > 0 65 } 66 67 // ContinuityOpt encodes the following possible relaxations in continuity 68 const ( 69 // Default - continuous blocks, txs, and events are always permitted 70 Continuous ContinuityOpt = iota 71 // Allows consumption of blocks where the next block has a different predecessor block to that which was last consumed 72 NonConsecutiveBlocks 73 // Allows consumption of transactions with non-monotonic index (within block) or a different number of transactions 74 // to that which is expected 75 NonConsecutiveTxs 76 // Allows consumption of events with non-monotonic index (within transaction) or a different number of events 77 // to that which is expected 78 NonConsecutiveEvents 79 ) 80 81 type BlockAccumulator struct { 82 block *BlockExecution 83 // Number of txs expected in current block 84 numTxs uint64 85 // Height of last block consumed that contained transactions 86 previousNonEmptyBlockHeight uint64 87 // Accumulator for Txs 88 stack TxStack 89 // Continuity requirements for the stream 90 continuity ContinuityOpt 91 } 92 93 func GetContinuity(continuityOptions ...ContinuityOpt) ContinuityOpt { 94 continuity := Continuous 95 for _, opt := range continuityOptions { 96 continuity |= opt 97 } 98 return continuity 99 } 100 101 func NewBlockAccumulator(continuityOptions ...ContinuityOpt) *BlockAccumulator { 102 continuity := GetContinuity(continuityOptions...) 103 return &BlockAccumulator{ 104 continuity: continuity, 105 stack: TxStack{ 106 continuity: continuity, 107 }, 108 } 109 } 110 111 func (ba *BlockAccumulator) ConsumeBlockExecution(stream EventStream) (block *BlockExecution, err error) { 112 var ev *StreamEvent 113 for ev, err = stream.Recv(); err == nil; ev, err = stream.Recv() { 114 block, err = ba.Consume(ev) 115 if err != nil { 116 return nil, err 117 } 118 if block != nil { 119 return block, nil 120 } 121 } 122 // If we reach here then we have failed to consume a complete block 123 return nil, err 124 } 125 126 // Consume will add the StreamEvent passed to the block accumulator and if the block complete is complete return the 127 // BlockExecution, otherwise will return nil 128 func (ba *BlockAccumulator) Consume(ev *StreamEvent) (*BlockExecution, error) { 129 switch { 130 case ev.BeginBlock != nil: 131 if !ba.continuity.Allows(NonConsecutiveBlocks) && 132 (ba.previousNonEmptyBlockHeight > 0 && ba.previousNonEmptyBlockHeight != ev.BeginBlock.PredecessorHeight) { 133 return nil, fmt.Errorf("BlockAccumulator.Consume: received non-consecutive block at height %d: "+ 134 "predecessor height %d, but previous (non-empty) block height was %d", 135 ev.BeginBlock.Height, ev.BeginBlock.PredecessorHeight, ba.previousNonEmptyBlockHeight) 136 } 137 // If we are consuming blocks over the event stream (rather than from state) we may see empty blocks 138 // by definition empty blocks will not be a predecessor 139 if ev.BeginBlock.NumTxs > 0 { 140 ba.previousNonEmptyBlockHeight = ev.BeginBlock.Height 141 } 142 ba.numTxs = ev.BeginBlock.NumTxs 143 ba.block = &BlockExecution{ 144 Height: ev.BeginBlock.Height, 145 PredecessorHeight: ev.BeginBlock.PredecessorHeight, 146 Header: ev.BeginBlock.Header, 147 TxExecutions: make([]*TxExecution, 0, ba.numTxs), 148 } 149 case ev.BeginTx != nil, ev.Envelope != nil, ev.Event != nil, ev.EndTx != nil: 150 txe, err := ba.stack.Consume(ev) 151 if err != nil { 152 return nil, err 153 } 154 if txe != nil { 155 if !ba.continuity.Allows(NonConsecutiveTxs) && uint64(len(ba.block.TxExecutions)) != txe.Index { 156 return nil, fmt.Errorf("BlockAccumulator.Consume recieved transaction with index %d at "+ 157 "position %d in the event stream", txe.Index, len(ba.block.TxExecutions)) 158 } 159 ba.block.TxExecutions = append(ba.block.TxExecutions, txe) 160 } 161 case ev.EndBlock != nil: 162 if !ba.continuity.Allows(NonConsecutiveTxs) && uint64(len(ba.block.TxExecutions)) != ba.numTxs { 163 return nil, fmt.Errorf("BlockAccumulator.Consume did not receive the expected number of "+ 164 "transactions for block %d, expected: %d, received: %d", 165 ba.block.Height, ba.numTxs, len(ba.block.TxExecutions)) 166 } 167 return ba.block, nil 168 } 169 return nil, nil 170 } 171 172 // TxStack is able to consume potentially nested txs 173 type TxStack struct { 174 // Stack of TxExecutions, top of stack is TxExecution receiving innermost events 175 txes []*TxExecution 176 // Track the expected number events from the BeginTx event (also a stack) 177 numEvents []uint64 178 // Relaxations of transaction/event continuity 179 continuity ContinuityOpt 180 } 181 182 func (stack *TxStack) Push(beginTx *BeginTx) { 183 // Put this txe in the parent position 184 stack.txes = append(stack.txes, &TxExecution{ 185 TxHeader: beginTx.TxHeader, 186 Result: beginTx.Result, 187 Events: make([]*Event, 0, beginTx.NumEvents), 188 Exception: beginTx.Exception, 189 }) 190 stack.numEvents = append(stack.numEvents, beginTx.NumEvents) 191 } 192 193 func (stack *TxStack) Peek() (*TxExecution, error) { 194 if len(stack.txes) < 1 { 195 return nil, fmt.Errorf("tried to peek from an empty TxStack - might be missing essential StreamEvents") 196 } 197 return stack.txes[len(stack.txes)-1], nil 198 } 199 200 func (stack *TxStack) Pop() (*TxExecution, error) { 201 txe, err := stack.Peek() 202 if err != nil { 203 return nil, err 204 } 205 newLength := len(stack.txes) - 1 206 stack.txes = stack.txes[:newLength] 207 numEvents := stack.numEvents[newLength] 208 if !stack.continuity.Allows(NonConsecutiveEvents) && uint64(len(txe.Events)) != numEvents { 209 return nil, fmt.Errorf("TxStack.Pop emitted transaction %s with wrong number of events, "+ 210 "expected: %d, received: %d", txe.TxHash, numEvents, len(txe.Events)) 211 } 212 stack.numEvents = stack.numEvents[:newLength] 213 return txe, nil 214 } 215 216 func (stack *TxStack) Length() int { 217 return len(stack.txes) 218 } 219 220 // Consume will add the StreamEvent to the transaction stack and if that completes a single outermost transaction 221 // returns the TxExecution otherwise will return nil 222 func (stack *TxStack) Consume(ev *StreamEvent) (*TxExecution, error) { 223 switch { 224 case ev.BeginTx != nil: 225 stack.Push(ev.BeginTx) 226 case ev.Envelope != nil: 227 txe, err := stack.Peek() 228 if err != nil { 229 return nil, err 230 } 231 txe.Envelope = ev.Envelope 232 txe.Receipt = txe.Envelope.Tx.GenerateReceipt() 233 case ev.Event != nil: 234 txe, err := stack.Peek() 235 if err != nil { 236 return nil, err 237 } 238 if !stack.continuity.Allows(NonConsecutiveEvents) && uint64(len(txe.Events)) != ev.Event.Header.Index { 239 return nil, fmt.Errorf("TxStack.Consume recieved event with index %d at "+ 240 "position %d in the event stream", ev.Event.GetHeader().GetIndex(), len(txe.Events)) 241 } 242 txe.Events = append(txe.Events, ev.Event) 243 case ev.EndTx != nil: 244 txe, err := stack.Pop() 245 if err != nil { 246 return nil, err 247 } 248 // If Origin _is_ set then it implies the transaction originates from a dump and is in an abbreviated 249 // 'pseudo transaction' for which no envelope is stored (since the dump format is intended to minimal) and we 250 // must relax the Envelope presence continuity check 251 if txe.TxHeader.Origin == nil && (txe.Envelope == nil || txe.Receipt == nil) { 252 return nil, fmt.Errorf("TxStack.Consume did not receive transaction envelope for transaction %s", 253 txe.TxHash) 254 } 255 if stack.Length() == 0 { 256 // This terminates the outermost transaction 257 return txe, nil 258 } 259 // If there is a parent tx on the stack add this tx as child 260 parent, err := stack.Peek() 261 if err != nil { 262 return nil, err 263 } 264 parent.TxExecutions = append(parent.TxExecutions, txe) 265 } 266 return nil, nil 267 }