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 }