Pleistarchus is the successor of Leonidas. It will enforce the same logic rules, but take different routes for doing so. Beyond that, he is the "faster" rollup, so other optimisations will fall under this for tracking purposes.
Directly Sequencer Related
Other optimisation tasks
Commit to the committee
In our case, we alter how the validator set and the committees are stored to reduce the overheads.
In Leonidas, a full list of addresses is used to identify the committee in each epoch. This is super convenient but have a gigantic cost as we end up writing to a LOT of storage.
To reduce this cost, one approach would be to compute the full committee (in memory) and then just insert the hash into the Epoch struct. That way, our storage cost is not dependent directly on the number of members in the committee.
However, when we need to use that information, we will then need either to:
- Provide the committee, and match the hash
- Compute the committee again and see the matching hash
Before deciding, consider that the swap-or-not shuffle that we use for our sampling is not gas-efficient, it is very costly - doing one index computation is currently (in our unoptimised version) more expensive than an sstore 😬.
Our only real option is to provide the committee. Again, we actually have a few options, but consider looking at kicking it up a notch for the advanced variant.
By providing the data, we need to extend the calldata with 32 bytes for every member of the committee and compute a hash over it. It might be a non-trivial amount, but still much cheaper than computing it again.
Validator Set Snapshots
Then, we run into a new problem: How do we compute the committee after the fact.
As you might know if you have looked at the Leonidas code, the validator set CAN change while the epoch committee is stable. This comes from the fact that we compute the committee and store it for later usage, before we would alter the validator set (which would impact the committee when sampling). This means that if you are trying to compute the committee using a different validator set (even with same seeds) you will end up with a different committee!
To solve this issue, we need to have validator snapshots. Essentially, for every epoch that happened in the past, we want to be able to look up who was in the validator set.
This can be handled in a couple of ways:
- Deal with it off-chain
- Have on-chain snapshots
I think for the case of simplicity, we should go for the on-chain snapshots. One approach would be to build merkle trees, but I think we can do much simpler by taking an approach similar to snapshot tokens.
First, we keep track of a list of snapshots for the validator set size.
struct EpochSizeSnapshot {
uint128 size;
uint96 epochNumber;
}
EpochSizeSnapshot[] public sizeSnapshots;
Say we have a list of these, and append at the end, to get for the current snapshot, simple read the latest snapshot. If you need to read for an epoch in the past, go back until you find the first epochNumber that is <= to the value you search for (binary search and a hint for efficiency).
Beyond this, say that for every index, we have a list of validator snapshots:
struct ValidatorSnapshot{
address validator;
uint96 epochNumber;
}
mapping(uint256 index => ValidatorSnapshot[] snapshots) public validators;
Using this, we have all the data we need!
When computing the committee to setup the epoch we can simply use the last snapshots. And when we need it later, we just take the values from the snapshots that match up.
The latter might be quite expensive, so the good thing is that we don't actually need to do it within a transaction, we can do that fully off-chain, and then we just provide the result (the committee) to our other function later.
Kick it up a notch
Efficient Data Provision
When providing the members of the committee, we could "abuse" that we will already be providing signatures for at least a subset of them, $\dfrac{2}{3}$. So we could practically provide just that missing $\dfrac{1}{3}$ as addresses, and recover the rest from the signatures. This requires quite a bit of fiddling with input params and encoding so lets safe that for now.
Pleistarchus is the successor of Leonidas. It will enforce the same logic rules, but take different routes for doing so. Beyond that, he is the "faster" rollup, so other optimisations will fall under this for tracking purposes.
Directly Sequencer Related
Other optimisation tasks
Commit to the committee
In our case, we alter how the validator set and the committees are stored to reduce the overheads.
In Leonidas, a full list of addresses is used to identify the
committeein eachepoch. This is super convenient but have a gigantic cost as we end up writing to a LOT of storage.To reduce this cost, one approach would be to compute the full committee (in memory) and then just insert the hash into the
Epochstruct. That way, our storage cost is not dependent directly on the number of members in the committee.However, when we need to use that information, we will then need either to:
Before deciding, consider that the
swap-or-notshuffle that we use for our sampling is not gas-efficient, it is very costly - doing one index computation is currently (in our unoptimised version) more expensive than ansstore😬.Our only real option is to provide the committee. Again, we actually have a few options, but consider looking at kicking it up a notch for the advanced variant.
By providing the data, we need to extend the calldata with
32 bytesfor every member of the committee and compute a hash over it. It might be a non-trivial amount, but still much cheaper than computing it again.Validator Set Snapshots
Then, we run into a new problem: How do we compute the committee after the fact.
As you might know if you have looked at the
Leonidascode, the validator set CAN change while the epoch committee is stable. This comes from the fact that we compute the committee and store it for later usage, before we would alter the validator set (which would impact the committee when sampling). This means that if you are trying to compute the committee using a different validator set (even with same seeds) you will end up with a different committee!To solve this issue, we need to have validator snapshots. Essentially, for every epoch that happened in the past, we want to be able to look up who was in the validator set.
This can be handled in a couple of ways:
I think for the case of simplicity, we should go for the on-chain snapshots. One approach would be to build merkle trees, but I think we can do much simpler by taking an approach similar to snapshot tokens.
First, we keep track of a list of snapshots for the validator set size.
Say we have a list of these, and append at the end, to get for the current snapshot, simple read the latest snapshot. If you need to read for an epoch in the past, go back until you find the first
epochNumberthat is<=to the value you search for (binary search and a hint for efficiency).Beyond this, say that for every index, we have a list of validator snapshots:
Using this, we have all the data we need!
When computing the committee to setup the epoch we can simply use the last snapshots. And when we need it later, we just take the values from the snapshots that match up.
The latter might be quite expensive, so the good thing is that we don't actually need to do it within a transaction, we can do that fully off-chain, and then we just provide the result (the committee) to our other function later.
Kick it up a notch
Efficient Data Provision
When providing the members of the committee, we could "abuse" that we will already be providing signatures for at least a subset of them,$\dfrac{2}{3}$ . So we could practically provide just that missing $\dfrac{1}{3}$ as addresses, and recover the rest from the signatures. This requires quite a bit of fiddling with input params and encoding so lets safe that for now.