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  }