github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/queue/queue.go (about)

     1  package queue
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/onflow/flow-go/model/flow"
     8  )
     9  
    10  type Node struct {
    11  	Item     Blockify
    12  	Children []*Node
    13  }
    14  
    15  // Blockify becuase Blocker seems a bit off.
    16  // Make items behave like a block, so it can be queued
    17  type Blockify interface {
    18  	flow.Entity
    19  	Height() uint64
    20  	ParentID() flow.Identifier
    21  }
    22  
    23  // Queue is a fork-aware queue/tree of blocks for use in execution Node, where parallel forks
    24  // can be processed simultaneously. For fast lookup which is predicted to be common case
    25  // all nodes are kept as one queue, which is expected to split into separate queues once
    26  // a fork (multiple children) is reached.
    27  // Note that this is not a thread-safe structure and external synchronisation is required
    28  // to use in concurrent environment
    29  type Queue struct {
    30  	Head    *Node
    31  	Highest *Node
    32  	Nodes   map[flow.Identifier]*Node
    33  }
    34  
    35  // Make queue an entity so it can be stored in mempool
    36  
    37  func (q *Queue) ID() flow.Identifier {
    38  	return q.Head.Item.ID()
    39  }
    40  
    41  func (q *Queue) Checksum() flow.Identifier {
    42  	return q.Head.Item.Checksum()
    43  }
    44  
    45  // Size returns number of elements in the queue
    46  func (q *Queue) Size() int {
    47  	return len(q.Nodes)
    48  }
    49  
    50  // Returns difference between lowest and highest element in the queue
    51  // Formally, the Queue stores a tree. The height of the tree is the
    52  // number of edges on the longest downward path between the root and any leaf.
    53  func (q *Queue) Height() uint64 {
    54  	return q.Highest.Item.Height() - q.Head.Item.Height()
    55  }
    56  
    57  // traverse Node children recursively and populate m
    58  func traverse(node *Node, m map[flow.Identifier]*Node, highest *Node) {
    59  	m[node.Item.ID()] = node
    60  	for _, node := range node.Children {
    61  		if node.Item.Height() > highest.Item.Height() {
    62  			*highest = *node
    63  		}
    64  		traverse(node, m, highest)
    65  	}
    66  }
    67  
    68  func NewQueue(blockify Blockify) *Queue {
    69  	n := &Node{
    70  		Item:     blockify,
    71  		Children: nil,
    72  	}
    73  	return &Queue{
    74  		Head:    n,
    75  		Highest: n,
    76  		Nodes:   map[flow.Identifier]*Node{n.Item.ID(): n},
    77  	}
    78  }
    79  
    80  // rebuildQueue makes a new queue from a Node which was already part of other queue
    81  // and fills lookup cache
    82  func rebuildQueue(n *Node) *Queue {
    83  	// rebuild map-cache
    84  	cache := make(map[flow.Identifier]*Node)
    85  	highest := *n //copy n
    86  	traverse(n, cache, &highest)
    87  
    88  	return &Queue{
    89  		Head:    n,
    90  		Nodes:   cache,
    91  		Highest: &highest,
    92  	}
    93  }
    94  
    95  // Special case for removing single-childed head element
    96  func dequeue(queue *Queue) *Queue {
    97  	onlyChild := queue.Head.Children[0]
    98  
    99  	cache := make(map[flow.Identifier]*Node)
   100  
   101  	//copy all but head caches
   102  	headID := queue.Head.Item.ID() // ID computation is about as expensive 1000 Go int additions
   103  	for key, val := range queue.Nodes {
   104  		if key != headID {
   105  			cache[key] = val
   106  		}
   107  	}
   108  
   109  	return &Queue{
   110  		Head:    onlyChild,
   111  		Nodes:   cache,
   112  		Highest: queue.Highest,
   113  	}
   114  }
   115  
   116  // TryAdd tries to add a new element to the queue.
   117  // A element can only be added if the parent exists in the queue.
   118  // TryAdd(elmt) is an idempotent operation for the same elmt, i.e.
   119  // after the first, subsequent additions of the same elements are NoOps.
   120  // Returns:
   121  // stored = True if and only if _after_ the operation, the element is stored in the
   122  // queue. This is the case if (a) element was newly added to the queue or
   123  // (b) element was already stored in the queue _before_ the call.
   124  // new = Indicates if element was new to the queue, when `stored` was true. It lets
   125  // distinguish (a) and (b) cases.
   126  // Adding an element fails with return value `false` for `stored` in the following cases:
   127  //   - element.ParentID() is _not_ stored in the queue
   128  //   - element's height is _unequal to_ its parent's height + 1
   129  func (q *Queue) TryAdd(element Blockify) (stored bool, new bool) {
   130  	if _, found := q.Nodes[element.ID()]; found {
   131  		// (b) element was already stored in the queue _before_ the call.
   132  		return true, false
   133  	}
   134  	// at this point, we are sure that the element is _not_ in the queue and therefore,
   135  	// the element cannot be referenced as a child by any other element in the queue
   136  	n, ok := q.Nodes[element.ParentID()]
   137  	if !ok {
   138  		return false, false
   139  	}
   140  	if n.Item.Height() != element.Height()-1 {
   141  		return false, false
   142  	}
   143  	newNode := &Node{
   144  		Item:     element,
   145  		Children: nil,
   146  	}
   147  	// we know: element is _not_ (yet) in the queue
   148  	// => it cannot be in _any_ nodes Children list
   149  	// => the following operation is guaranteed to _not_ produce
   150  	//    duplicates in the Children list
   151  	n.Children = append(n.Children, newNode)
   152  	q.Nodes[element.ID()] = newNode
   153  	if element.Height() > q.Highest.Item.Height() {
   154  		q.Highest = newNode
   155  	}
   156  	return true, true
   157  }
   158  
   159  // Dismount removes the head element, returns it and it's children as new queues
   160  func (q *Queue) Dismount() (Blockify, []*Queue) {
   161  
   162  	queues := make([]*Queue, len(q.Head.Children))
   163  	if len(q.Head.Children) == 1 { //optimize for most common single-child case
   164  		queues[0] = dequeue(q)
   165  	} else {
   166  		for i, child := range q.Head.Children {
   167  			queues[i] = rebuildQueue(child)
   168  		}
   169  	}
   170  	return q.Head.Item, queues
   171  }
   172  
   173  func (q *Queue) String() string {
   174  	builder := new(strings.Builder)
   175  	builder.WriteString(fmt.Sprintf("Header: %v\n", q.Head.Item.ID()))
   176  	builder.WriteString(fmt.Sprintf("Highest: %v\n", q.Highest.Item.ID()))
   177  	builder.WriteString(fmt.Sprintf("Size: %v, Height: %v\n", q.Size(), q.Height()))
   178  	builder.WriteString(fmt.Sprintf("Node(height: %v): %v (children: %v)\n",
   179  		q.Head.Item.Height(),
   180  		q.Head.Item.ID(),
   181  		len(q.Head.Children),
   182  	))
   183  	printChildren(builder, q.Head, "")
   184  	return builder.String()
   185  }
   186  
   187  func printChildren(builder *strings.Builder, n *Node, prefix string) {
   188  	for _, child := range n.Children {
   189  		builder.WriteString(fmt.Sprintf("%s Node(height: %v): %v (children: %v)\n",
   190  			prefix+"|-",
   191  			child.Item.Height(),
   192  			child.Item.ID(),
   193  			len(child.Children),
   194  		))
   195  		printChildren(builder, child, prefix+"   ")
   196  	}
   197  }