Skip to content

Commit a9a42f9

Browse files
author
Beau Shinkle
committed
Merge remote-tracking branch 'origin/main' into comment-sortition-tree
2 parents 14ab79a + 697236f commit a9a42f9

13 files changed

Lines changed: 55833 additions & 937 deletions

README.adoc

Lines changed: 0 additions & 77 deletions
This file was deleted.

README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Sortition Pools
2+
3+
Sortition pool is a logarithmic data structure used to store the pool of
4+
eligible operators weighted by their stakes. In the Keep network the stake
5+
consists of staked KEEP tokens. It allows to select a group of operators based
6+
on the provided pseudo-random seed.
7+
8+
Each privileged application has its own sortition pool and is responsible for
9+
maintaining operator weights up-to-date.
10+
11+
## In-Depth Reading
12+
13+
To familiarize yourself with the sortition pool and it's design, we provide
14+
15+
+ [Building Intuition](docs/building-intuition.md)
16+
+ [Implementation Details](docs/implementation-details.md)
17+
18+
[Building Intuition](docs/building-intuition.md) starts the reader from the
19+
problem description and an easy-to-understand naive solution, and then works
20+
its way up to the current design of the sortition pool through a series of
21+
optimizations.
22+
23+
[Implementation Details](docs/implementation-details.md) builds off of the
24+
knowledge in [Building Intuition](docs/building-intuition.md), and gets into
25+
the finer points about the data structure, data (de)serialization, how
26+
operators join/leave the pool, and how it all comes together to select a full
27+
group.
28+
29+
## Important Facts
30+
31+
+ The max number of operators is `2,097,152`
32+
+ The sortition pool is for general purpose group selection. Feel free to use
33+
or fork it!
34+
+ The sortition pool can be [optimistic](#optimisic-group-selection)! The
35+
on-chain code then is only run in the case that the selection submission is
36+
challenged.
37+
38+
## Safe Use
39+
40+
Miners and other actors that can predict the selection seed (due
41+
to frontrunning the beacon or a public cached seed being used) may be able to
42+
manipulate selection outcomes to some degree by selectively updating the pool.
43+
44+
To mitigate this, applications using sortition pool should lock sortition pool
45+
state before seed used for the new selection is known and should unlock the
46+
pool once the selection process is over, keeping in mind potential timeouts and
47+
result challenges.
48+
49+
## Optimistic Group Selection
50+
51+
When an application (like the [Random
52+
Beacon](https://github.com/keep-network/keep-core/tree/main/solidity/random-beacon#group-creation))
53+
needs a new group, sortition is performed off-chain according to the same
54+
algorithm that would be performed on-chain, and the results are submitted
55+
on-chain.
56+
57+
Then, we enter a challenge period where anyone can claim that the submitted
58+
results are inaccurate. If this happens, the on-chain sortition pool runs the
59+
same group selection with the same seed and validates the results.
60+
61+
If the submission was invalid, the challenger is rewarded and the submitter is
62+
punished, and we can accept another submission. If the submission was valid,
63+
the challenger loses out on their gas, and the submitter is unaffected.

contracts/Branch.sol

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,11 @@
11
pragma solidity 0.8.9;
22

3+
import "./Constants.sol";
4+
35
/// @notice The implicit 8-ary trees of the sortition pool
46
/// rely on packing 8 "slots" of 32-bit values into each uint256.
57
/// The Branch library permits efficient calculations on these slots.
68
library Branch {
7-
////////////////////////////////////////////////////////////////////////////
8-
// Parameters for configuration
9-
10-
// How many bits a position uses per level of the tree;
11-
// each branch of the tree contains 2**SLOT_BITS slots.
12-
uint256 private constant SLOT_BITS = 3;
13-
////////////////////////////////////////////////////////////////////////////
14-
15-
////////////////////////////////////////////////////////////////////////////
16-
// Derived constants, do not touch
17-
uint256 private constant SLOT_COUNT = 2**SLOT_BITS;
18-
uint256 private constant SLOT_WIDTH = 256 / SLOT_COUNT;
19-
uint256 private constant LAST_SLOT = SLOT_COUNT - 1;
20-
uint256 private constant SLOT_MAX = (2**SLOT_WIDTH) - 1;
21-
22-
////////////////////////////////////////////////////////////////////////////
23-
249
/// @notice Calculate the right shift required
2510
/// to make the 32 least significant bits of an uint256
2611
/// be the bits of the `position`th slot
@@ -31,7 +16,7 @@ library Branch {
3116
/// I wish solidity had macros, even C macros.
3217
function slotShift(uint256 position) internal pure returns (uint256) {
3318
unchecked {
34-
return position * SLOT_WIDTH;
19+
return position * Constants.SLOT_WIDTH;
3520
}
3621
}
3722

@@ -43,12 +28,12 @@ library Branch {
4328
returns (uint256)
4429
{
4530
unchecked {
46-
uint256 shiftBits = position * SLOT_WIDTH;
31+
uint256 shiftBits = position * Constants.SLOT_WIDTH;
4732
// Doing a bitwise AND with `SLOT_MAX`
4833
// clears all but the 32 least significant bits.
4934
// Because of the right shift by `slotShift(position)` bits,
5035
// those 32 bits contain the 32 bits in the `position`th slot of `node`.
51-
return (node >> shiftBits) & SLOT_MAX;
36+
return (node >> shiftBits) & Constants.SLOT_MAX;
5237
}
5338
}
5439

@@ -59,7 +44,7 @@ library Branch {
5944
returns (uint256)
6045
{
6146
unchecked {
62-
uint256 shiftBits = position * SLOT_WIDTH;
47+
uint256 shiftBits = position * Constants.SLOT_WIDTH;
6348
// Shifting `SLOT_MAX` left by `slotShift(position)` bits
6449
// gives us a number where all bits of the `position`th slot are set,
6550
// and all other bits are unset.
@@ -71,7 +56,7 @@ library Branch {
7156
// Bitwise ANDing the original `node` with this number
7257
// sets the bits of `position`th slot to zero,
7358
// leaving all other bits unchanged.
74-
return node & ~(SLOT_MAX << shiftBits);
59+
return node & ~(Constants.SLOT_MAX << shiftBits);
7560
}
7661
}
7762

@@ -86,17 +71,17 @@ library Branch {
8671
uint256 weight
8772
) internal pure returns (uint256) {
8873
unchecked {
89-
uint256 shiftBits = position * SLOT_WIDTH;
74+
uint256 shiftBits = position * Constants.SLOT_WIDTH;
9075
// Clear the `position`th slot like in `clearSlot()`.
91-
uint256 clearedNode = node & ~(SLOT_MAX << shiftBits);
76+
uint256 clearedNode = node & ~(Constants.SLOT_MAX << shiftBits);
9277
// Bitwise AND `weight` with `SLOT_MAX`
9378
// to clear all but the 32 least significant bits.
9479
//
9580
// Shift this left by `slotShift(position)` bits
9681
// to obtain a uint256 with all bits unset
9782
// except in the `position`th slot
9883
// which contains the 32-bit value of `weight`.
99-
uint256 shiftedWeight = (weight & SLOT_MAX) << shiftBits;
84+
uint256 shiftedWeight = (weight & Constants.SLOT_MAX) << shiftBits;
10085
// When we bitwise OR these together,
10186
// all other slots except the `position`th one come from the left argument,
10287
// and the `position`th gets filled with `weight` from the right argument.
@@ -107,14 +92,14 @@ library Branch {
10792
/// @notice Calculate the summed weight of all slots in the `node`.
10893
function sumWeight(uint256 node) internal pure returns (uint256 sum) {
10994
unchecked {
110-
sum = node & SLOT_MAX;
95+
sum = node & Constants.SLOT_MAX;
11196
// Iterate through each slot
11297
// by shifting `node` right in increments of 32 bits,
11398
// and adding the 32 least significant bits to the `sum`.
114-
uint256 newNode = node >> SLOT_WIDTH;
99+
uint256 newNode = node >> Constants.SLOT_WIDTH;
115100
while (newNode > 0) {
116-
sum += (newNode & SLOT_MAX);
117-
newNode = newNode >> SLOT_WIDTH;
101+
sum += (newNode & Constants.SLOT_MAX);
102+
newNode = newNode >> Constants.SLOT_WIDTH;
118103
}
119104
return sum;
120105
}
@@ -143,12 +128,12 @@ library Branch {
143128
unchecked {
144129
newIndex = index;
145130
uint256 newNode = node;
146-
uint256 currentSlotWeight = newNode & SLOT_MAX;
131+
uint256 currentSlotWeight = newNode & Constants.SLOT_MAX;
147132
while (newIndex >= currentSlotWeight) {
148133
newIndex -= currentSlotWeight;
149134
slot++;
150-
newNode = newNode >> SLOT_WIDTH;
151-
currentSlotWeight = newNode & SLOT_MAX;
135+
newNode = newNode >> Constants.SLOT_WIDTH;
136+
currentSlotWeight = newNode & Constants.SLOT_MAX;
152137
}
153138
return (slot, newIndex);
154139
}

contracts/Constants.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
pragma solidity 0.8.9;
2+
3+
library Constants {
4+
////////////////////////////////////////////////////////////////////////////
5+
// Parameters for configuration
6+
7+
// How many bits a position uses per level of the tree;
8+
// each branch of the tree contains 2**SLOT_BITS slots.
9+
uint256 constant SLOT_BITS = 3;
10+
uint256 constant LEVELS = 7;
11+
////////////////////////////////////////////////////////////////////////////
12+
13+
////////////////////////////////////////////////////////////////////////////
14+
// Derived constants, do not touch
15+
uint256 constant SLOT_COUNT = 2**SLOT_BITS;
16+
uint256 constant SLOT_WIDTH = 256 / SLOT_COUNT;
17+
uint256 constant LAST_SLOT = SLOT_COUNT - 1;
18+
uint256 constant SLOT_MAX = (2**SLOT_WIDTH) - 1;
19+
uint256 constant POOL_CAPACITY = SLOT_COUNT**LEVELS;
20+
21+
uint256 constant ID_WIDTH = SLOT_WIDTH;
22+
uint256 constant ID_MAX = SLOT_MAX;
23+
24+
uint256 constant BLOCKHEIGHT_WIDTH = 96 - ID_WIDTH;
25+
uint256 constant BLOCKHEIGHT_MAX = (2**BLOCKHEIGHT_WIDTH) - 1;
26+
27+
uint256 constant SLOT_POINTER_MAX = (2**SLOT_BITS) - 1;
28+
uint256 constant LEAF_FLAG = 1 << 255;
29+
30+
uint256 constant WEIGHT_WIDTH = 256 / SLOT_COUNT;
31+
////////////////////////////////////////////////////////////////////////////
32+
}

contracts/Leaf.sol

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,8 @@
11
pragma solidity 0.8.9;
22

3-
library Leaf {
4-
////////////////////////////////////////////////////////////////////////////
5-
// Parameters for configuration
6-
7-
// How many bits a position uses per level of the tree;
8-
// each branch of the tree contains 2**SLOT_BITS slots.
9-
uint256 private constant SLOT_BITS = 3;
10-
////////////////////////////////////////////////////////////////////////////
11-
12-
////////////////////////////////////////////////////////////////////////////
13-
// Derived constants, do not touch
14-
uint256 private constant SLOT_COUNT = 2**SLOT_BITS;
15-
uint256 private constant SLOT_WIDTH = 256 / SLOT_COUNT;
16-
uint256 private constant SLOT_MAX = (2**SLOT_WIDTH) - 1;
17-
18-
uint256 private constant ID_WIDTH = SLOT_WIDTH;
19-
uint256 private constant ID_MAX = SLOT_MAX;
20-
21-
uint256 private constant BLOCKHEIGHT_WIDTH = 96 - ID_WIDTH;
22-
uint256 private constant BLOCKHEIGHT_MAX = (2**BLOCKHEIGHT_WIDTH) - 1;
23-
24-
////////////////////////////////////////////////////////////////////////////
3+
import "./Constants.sol";
254

5+
library Leaf {
266
function make(
277
address _operator,
288
uint256 _creationBlock,
@@ -35,10 +15,11 @@ library Leaf {
3515
uint256 op = uint256(bytes32(bytes20(_operator)));
3616
// Bitwise AND the id to erase
3717
// all but the 32 least significant bits
38-
uint256 uid = _id & ID_MAX;
18+
uint256 uid = _id & Constants.ID_MAX;
3919
// Erase all but the 64 least significant bits,
4020
// then shift left by 32 bits to make room for the id
41-
uint256 cb = (_creationBlock & BLOCKHEIGHT_MAX) << ID_WIDTH;
21+
uint256 cb = (_creationBlock & Constants.BLOCKHEIGHT_MAX) <<
22+
Constants.ID_WIDTH;
4223
// Bitwise OR them all together to get
4324
// [address operator || uint64 creationBlock || uint32 id]
4425
return (op | cb | uid);
@@ -52,12 +33,12 @@ library Leaf {
5233

5334
/// @notice Return the block number the leaf was created in.
5435
function creationBlock(uint256 leaf) internal pure returns (uint256) {
55-
return ((leaf >> ID_WIDTH) & BLOCKHEIGHT_MAX);
36+
return ((leaf >> Constants.ID_WIDTH) & Constants.BLOCKHEIGHT_MAX);
5637
}
5738

5839
function id(uint256 leaf) internal pure returns (uint32) {
5940
// Id is stored in the 32 least significant bits.
6041
// Bitwise AND ensures that we only get the contents of those bits.
61-
return uint32(leaf & ID_MAX);
42+
return uint32(leaf & Constants.ID_MAX);
6243
}
6344
}

0 commit comments

Comments
 (0)