@@ -176,4 +176,92 @@ For an in-depth explanation of how this information is structured, refer to the
176176Serialization] ( #branch-and-root-deserialization-and-serialization ) section.
177177
178178TODO: Joining and Leaving The Pool
179- TODO: Selecting A Random Group
179+
180+ ### Selecting A Random Group
181+
182+ To select a random group of of operators from the pool size ` N ` , we construct
183+ an array of size ` N ` to house the result, and then populate it, one random
184+ operator at a time, with replacement.
185+
186+ We start with a seed provided from the [ random
187+ beacon] ( https://github.com/keep-network/keep-core/tree/main/solidity/random-beacon )
188+ and use that seed in combination with the total weight of the root node,
189+ calculatable by summing the weight of the slots: see [ Branch And Root
190+ Deserialization And
191+ Serialization] ( #branch-and-root-deserialization-and-serialization ) to generate
192+ a random uniform integer in ` [0, root.totalWeight) ` , as well as the next random
193+ seed for the next random operator. In order to generate this next random seed,
194+ we're using
195+ ```
196+ newState = keccak256(abi.encodePacked(newState, address(this)));
197+ ```
198+ which, according to [ A Pseudorandom Number Generator with KECCAK Hash Function
199+ by A. Gholipour and S. Mirzakuchak] ( http://www.ijcee.org/papers/439-JE503.pdf ) ,
200+ has "excellent pseudo randomness".
201+
202+ Once we have our random integer, we are able to descend down the tree according
203+ to the algorithm outlined in [ building intuition] ( building-intuition.md ) .
204+
205+ ```
206+ function pickWeightedLeaf(uint256 index, uint256 _root)
207+ internal
208+ view
209+ returns (uint256 leafPosition)
210+ {
211+ uint256 currentIndex = index;
212+ uint256 currentNode = _root;
213+ uint256 currentPosition = 0;
214+ uint256 currentSlot;
215+
216+ require(index < currentNode.sumWeight(), "Index exceeds weight");
217+
218+ // get root slot
219+ (currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);
220+
221+ // get slots from levels 2 to 7
222+ for (uint256 level = 2; level <= LEVELS; level++) {
223+ currentPosition = currentPosition.child(currentSlot);
224+ currentNode = branches[level][currentPosition];
225+ (currentSlot, currentIndex) = currentNode.pickWeightedSlot(currentIndex);
226+ }
227+
228+ // get leaf position
229+ leafPosition = currentPosition.child(currentSlot);
230+ }
231+
232+ function pickWeightedSlot(uint256 node, uint256 index)
233+ internal
234+ pure
235+ returns (uint256 slot, uint256 newIndex)
236+ {
237+ unchecked {
238+ newIndex = index;
239+ uint256 newNode = node;
240+ uint256 currentSlotWeight = newNode & SLOT_MAX;
241+ while (newIndex >= currentSlotWeight) {
242+ newIndex -= currentSlotWeight;
243+ slot++;
244+ newNode = newNode >> SLOT_WIDTH;
245+ currentSlotWeight = newNode & SLOT_MAX;
246+ }
247+ return (slot, newIndex);
248+ }
249+ }
250+ ```
251+
252+ At a particular root/branch node, we inspect the right-most 32 bits by applying
253+ a bitwise ` & ` against ` 2^32 - 1 ` , which leaves only the last 32 bits as
254+ potentially non-zero.
255+
256+ If this quantity if greater than our random number, we found our path of
257+ descent and repeat the process at the next layer. If it isn't, then we increase
258+ our slot counter, decrease our random number by the quantity, and shift our
259+ number over 32 bits to the right and repeat.
260+
261+ Eventually we will find a slot that exceeds our random number and be able to
262+ descend to the next layer where the process repeats until we get to the leaf
263+ layer where the task is finished.
264+
265+ We record our chosen operator in the result list, use the fresh seed from
266+ ` keccak256 ` to generate a new random number and new seed, and repeat until we
267+ have a full group.
0 commit comments