Skip to content

Commit 2ee4b60

Browse files
author
Beau Shinkle
committed
Comment the sortition tree
1 parent 479cca6 commit 2ee4b60

1 file changed

Lines changed: 94 additions & 12 deletions

File tree

contracts/SortitionTree.sol

Lines changed: 94 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ contract SortitionTree {
3535
// level6 256k
3636
// level7 2M
3737
uint256 internal root;
38+
39+
// A mapping from layer => (index => branch). For example, to access the 6th
40+
// (0-index) branch in the 2nd layer (right below the root node), call
41+
// branches[2][6]. Mappings are used in place of arrays for efficiency.
3842
mapping(uint256 => mapping(uint256 => uint256)) internal branches;
43+
44+
// A mapping from index => leaf, acting as an array. For example, to access
45+
// the 42nd (0-index) leaf, call leaves[42].
3946
mapping(uint256 => uint256) internal leaves;
4047

4148
// the flagged (see setFlag() and unsetFlag() in Position.sol) positions
@@ -44,6 +51,7 @@ contract SortitionTree {
4451

4552
// the leaf after the rightmost occupied leaf of each stack
4653
uint256 internal rightmostLeaf;
54+
4755
// the empty leaves in each stack
4856
// between 0 and the rightmost occupied leaf
4957
uint256[] internal emptyLeaves;
@@ -52,6 +60,7 @@ contract SortitionTree {
5260
// which is allocated when they first join the pool
5361
// and remains unchanged even if they leave and rejoin the pool.
5462
mapping(address => uint32) internal operatorID;
63+
5564
// The idAddress array records the address corresponding to each ID number.
5665
// The ID number 0 is initialized with a zero address and is not used.
5766
address[] internal idAddress;
@@ -62,22 +71,28 @@ contract SortitionTree {
6271
idAddress.push();
6372
}
6473

65-
// Return the ID number of the given operator address.
66-
// An ID number of 0 means the operator has not been allocated an ID number yet.
74+
/// @notice Return the ID number of the given operator address. An ID number
75+
/// of 0 means the operator has not been allocated an ID number yet.
76+
/// @param operator Address of the operator.
77+
/// @return the ID number of the given operator address
6778
function getOperatorID(address operator) public view returns (uint32) {
6879
return operatorID[operator];
6980
}
7081

71-
// Get the operator address corresponding to the given ID number.
72-
// An empty address means the ID number has not been allocated yet.
82+
/// @notice Get the operator address corresponding to the given ID number. An
83+
/// empty address means the ID number has not been allocated yet.
84+
/// @param id ID of the operator
85+
/// @return the address of the operator
7386
function getIDOperator(uint32 id) public view returns (address) {
7487
return idAddress.length > id ? idAddress[id] : address(0);
7588
}
7689

77-
// Gets the operator addresses corresponding to the given ID numbers.
78-
// An empty address means the ID number has not been allocated yet.
79-
// This function works just like getIDOperator except that it allows to fetch
80-
// operator addresses for multiple IDs in one call.
90+
/// @notice Gets the operator addresses corresponding to the given ID
91+
/// numbers. An empty address means the ID number has not been allocated yet.
92+
/// This function works just like getIDOperator except that it allows to fetch
93+
/// operator addresses for multiple IDs in one call.
94+
/// @param ids the array of the operator ids
95+
/// @return an array of the associated operator addresses
8196
function getIDOperators(uint32[] calldata ids)
8297
public
8398
view
@@ -93,12 +108,15 @@ contract SortitionTree {
93108
return operators;
94109
}
95110

96-
// checks if operator is already registered in the pool
111+
/// @notice Checks if operator is already registered in the pool.
112+
/// @param operator the address of the operator
113+
/// @return whether or not the operator is already registered in the pool
97114
function isOperatorRegistered(address operator) public view returns (bool) {
98115
return getFlaggedLeafPosition(operator) != 0;
99116
}
100117

101-
// Sum the number of operators in each trunk
118+
/// @notice Sum the number of operators in each trunk.
119+
/// @return the number of operators in the pool
102120
function operatorsInPool() public view returns (uint256) {
103121
// Get the number of leaves that might be occupied;
104122
// if `rightmostLeaf` equals `firstLeaf()` the tree must be empty,
@@ -112,12 +130,16 @@ contract SortitionTree {
112130
return (nPossiblyUsedLeaves - nEmptyLeaves);
113131
}
114132

133+
/// @notice Convenience method to return the total weight of the pool
134+
/// @return the total weight of the pool
115135
function totalWeight() public view returns (uint256) {
116136
return root.sumWeight();
117137
}
118138

119-
// Give the operator a new ID number
120-
// Does not check if the operator already has an ID number
139+
/// @notice Give the operator a new ID number.
140+
/// Does not check if the operator already has an ID number.
141+
/// @param operator the address of the operator
142+
/// @return a new ID for that operator
121143
function allocateOperatorID(address operator) internal returns (uint256) {
122144
uint256 id = idAddress.length;
123145

@@ -128,36 +150,51 @@ contract SortitionTree {
128150
return id;
129151
}
130152

153+
/// @notice Inserts an operator into the sortition pool
154+
/// @param operator the address of an operator to insert
155+
/// @param weight how much weight that operator has in the pool
131156
function _insertOperator(address operator, uint256 weight) internal {
132157
require(
133158
!isOperatorRegistered(operator),
134159
"Operator is already registered in the pool"
135160
);
136161

162+
// Fetch the operator's ID, and if they don't have one, allocate them one.
137163
uint256 id = getOperatorID(operator);
138164
if (id == 0) {
139165
id = allocateOperatorID(operator);
140166
}
141167

168+
// Determine which leaf to insert them into
142169
uint256 position = getEmptyLeafPosition();
143170
// Record the block the operator was inserted in
144171
uint256 theLeaf = Leaf.make(operator, block.number, id);
145172

173+
// Update the leaf, and propagate the weight changes all the way up to the
174+
// root.
146175
root = setLeaf(position, theLeaf, weight, root);
147176

148177
// Without position flags,
149178
// the position 0x000000 would be treated as empty
150179
flaggedLeafPosition[operator] = position.setFlag();
151180
}
152181

182+
/// @notice Remove an operator (and their weight) from the pool.
183+
/// @param operator the address of the operator to remove
153184
function _removeOperator(address operator) internal {
154185
uint256 flaggedPosition = getFlaggedLeafPosition(operator);
155186
require(flaggedPosition != 0, "Operator is not registered in the pool");
156187
uint256 unflaggedPosition = flaggedPosition.unsetFlag();
188+
189+
// Update the leaf, and propagate the weight changes all the way up to the
190+
// root.
157191
root = removeLeaf(unflaggedPosition, root);
158192
removeLeafPositionRecord(operator);
159193
}
160194

195+
/// @notice Update an operator's weight in the pool.
196+
/// @param operator the address of the operator to update
197+
/// @param weight the new weight
161198
function updateOperator(address operator, uint256 weight) internal {
162199
require(
163200
isOperatorRegistered(operator),
@@ -169,19 +206,28 @@ contract SortitionTree {
169206
root = updateLeaf(unflaggedPosition, weight, root);
170207
}
171208

209+
/// @notice Helper method to remove a leaf position record for an operator.
210+
/// @param operator the address of the operator to remove the record for
172211
function removeLeafPositionRecord(address operator) internal {
173212
flaggedLeafPosition[operator] = 0;
174213
}
175214

215+
/// @notice Removes the data and weight from a particular leaf.
216+
/// @param position the leaf index to remove
217+
/// @param _root the root node containing the leaf
218+
/// @return the updated root node
176219
function removeLeaf(uint256 position, uint256 _root)
177220
internal
178221
returns (uint256)
179222
{
180223
uint256 rightmostSubOne = rightmostLeaf - 1;
181224
bool isRightmost = position == rightmostSubOne;
182225

226+
// Clears out the data in the leaf node, and then propagates the weight
227+
// changes all the way up to the root.
183228
uint256 newRoot = setLeaf(position, 0, 0, _root);
184229

230+
// Infer if need to fall back on emptyLeaves yet
185231
if (isRightmost) {
186232
rightmostLeaf = rightmostSubOne;
187233
} else {
@@ -190,6 +236,11 @@ contract SortitionTree {
190236
return newRoot;
191237
}
192238

239+
/// @notice Updates the tree to give a particular leaf a new weight.
240+
/// @param position the index of the leaf to update
241+
/// @param weight the new weight
242+
/// @param _root the root node containing the leaf
243+
/// @return the updated root node
193244
function updateLeaf(
194245
uint256 position,
195246
uint256 weight,
@@ -202,6 +253,13 @@ contract SortitionTree {
202253
}
203254
}
204255

256+
/// @notice Places a leaf into a particular position, with a given weight and
257+
/// propagates that change.
258+
/// @param position the index to place the leaf in
259+
/// @param theLeaf the new leaf to place in the position
260+
/// @param leafWeight the weight of the leaf
261+
/// @param _root the root containing the new leaf
262+
/// @return the updated root node
205263
function setLeaf(
206264
uint256 position,
207265
uint256 theLeaf,
@@ -214,6 +272,12 @@ contract SortitionTree {
214272
return (updateTree(position, leafWeight, _root));
215273
}
216274

275+
/// @notice Propagates a weight change at a position through the tree,
276+
/// eventually returning the updated root.
277+
/// @param position the index of leaf to update
278+
/// @param weight the new weight of the leaf
279+
/// @param _root the root node containing the leaf
280+
/// @return the updated root node
217281
function updateTree(
218282
uint256 position,
219283
uint256 weight,
@@ -240,6 +304,10 @@ contract SortitionTree {
240304
return _root.setSlot(childSlot, nodeWeight);
241305
}
242306

307+
/// @notice Retrieves the next available empty leaf position. Tries to fill
308+
/// left to right first, ignoring leaf removals, and then fills
309+
/// most-recent-removals first.
310+
/// @return the position of the empty leaf
243311
function getEmptyLeafPosition() internal returns (uint256) {
244312
uint256 rLeaf = rightmostLeaf;
245313
bool spaceOnRight = (rLeaf + 1) < POOL_CAPACITY;
@@ -255,6 +323,9 @@ contract SortitionTree {
255323
}
256324
}
257325

326+
/// @notice Gets the flagged leaf position for an operator.
327+
/// @param operator the address of the operator
328+
/// @return the leaf position of that operator
258329
function getFlaggedLeafPosition(address operator)
259330
internal
260331
view
@@ -263,13 +334,24 @@ contract SortitionTree {
263334
return flaggedLeafPosition[operator];
264335
}
265336

337+
/// @notice Gets the weight of a leaf at a particular position.
338+
/// @param position the index of the leaf
339+
/// @return the weight of the leaf at that position
266340
function getLeafWeight(uint256 position) internal view returns (uint256) {
267341
uint256 slot = position.slot();
268342
uint256 parent = position.parent();
343+
344+
// A leaf's weight information is stored a 32-bit slot in the branch layer
345+
// directly above the leaf layer. To access it, we calculate that slot and
346+
// parent position, and always know the hard-coded layer index.
269347
uint256 node = branches[LEVELS][parent];
270348
return node.getSlot(slot);
271349
}
272350

351+
/// @notice Picks a leaf given a random index.
352+
/// @param index a number in `[0, _root.totalWeight())` used to decide
353+
/// between leaves
354+
/// @param _root the root of the tree
273355
function pickWeightedLeaf(uint256 index, uint256 _root)
274356
internal
275357
view

0 commit comments

Comments
 (0)