Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prune state history on LC contract #1670

Conversation

alysiahuggins
Copy link
Contributor

added functionality to prune the state history using a FIFO approach and added two additional tests

Closes #1541

This PR:

  • ensures that there is only ever 10 days worth of state history stored by deleting the first entries first and storing the first index to use when traversing the state history

This PR does not:

  • truncate the array length to be no greater than 10 days worth of state updates

Key places to review:

FIFO approach to pruning and the fact that the array continues to grow though one only ever interacts with the 10 days worth of state updates due to storing the stateHistory first index

How to test this PR:

forge test --match-contract LightClient_StateUpdatesTest --ffi -vvv

Things tested

  • updating max state history allowed
  • dealing with state updates greater than the max state history allowed

Copy link
Member

@ImJeremyHe ImJeremyHe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What confuses me is that why not just override the old values with the new values? Does that consume more gas?

Comment on lines 410 to 411
stateUpdateBlockNumbers.push(blockNumber);
hotShotCommitments.push(HotShotCommitment(state.blockHeight, state.blockCommRoot));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we will just push the new elements into the array, why bother deleting the first one?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so that the storage is emptied and interacting with the array will consume less gas.

The goal is to maintain a history that isn't larger than maxStateHistoryAllowed. So i delete the first element and increment stateHistoryFirstIndex, so that the history is kept within this bound. The amount if storage required is a predictable and we also get a gas refund with the delete.

@alysiahuggins alysiahuggins changed the title DRAFT PR | prune state history on LC contract prune state history on LC contract Jul 10, 2024
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
function updateStateHistory(uint256 blockNumber, LightClientState memory state) internal {
if (maxStateHistoryAllowed == 0) revert InvalidMaxStateHistory();

if (stateHistoryCount == maxStateHistoryAllowed) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maxStateHistoryAllowed name is a bit misleading right now because if the setter setMaxStateHIstoryAllowed is used to reduce it then size of the history that is currently larger it won't be reduced because we only ever remove an element if we also add one. Unless I'm misunderstanding something.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really doubt we ever want to set a smaller state history after deployment though so because all that it allows us to do is reclaim some gas. So maybe we can "fix it" by not allowing setting a lower state history?

We could also have functionality to prune multiple entries but the problem with that is that we need to then have potential issues where calls can run out of gas if the iteration is too long.

One way to resolve this is also to allow for a few (at least 2) pruning operations during an update. This makes the gas usage bounded and still allows the history to shrink.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your understanding is correct, it does not considering the reduction of the max state history allowed. Would you allow me to add the ability to prune +1 elements at the same time? and if so, I can make it so that the maxStateHistoryAllowed can be reduced. Otherwise, i can restrict modifications to only increasing the max state history allowed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think any changes here are somewhat problematic because it potentially affects the result of challenges in the rollups. Increasing the history size definitely seems less problematic though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah true - also this light client may be deployed on L2s to support L3s - Would there be a need to shorten the maxStateHistoryAllowed in those instances?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we hardcode the maxStateHistoryAllowed in the _initializeState method but considering that the light client would also deploy on L2s to support L3s. Should it be configurable at deployment?

Copy link
Collaborator

@sveitser sveitser Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L2s are also a lot cheaper so I wonder if it would be acceptable to default to not pruning the history on L2s.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another alternative would be to use block.timestamp instead of block numbers. That way we could at least set a default that should keep working on L2s as long as their timestamps are correct. I'm currently not 100% clear on how trustworthy block.timestamp is, especially on L2s. I would hope that validators would only tolerate small deviations though.

Copy link
Collaborator

@sveitser sveitser Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the answer below it sounds like the timestamp is pretty safe to use on L1 after the merge. Wdyt @alysiahuggins ?

https://ethereum.stackexchange.com/a/140818

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has been addressed in a previous commit

…ements-from-stateupdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
/// @notice set Max Block States allowed
/// @param historySeconds The maximum duration worth of state history updates to store based on
/// the block timestamp
function setMaxStateHistoryDuration(uint32 historySeconds) public onlyOwner {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I think we previously discussed I think we should not allow lowering the value here since we currently don't have code to actually reduce the size in that case and it might have unintended consequences for rollups.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, do we want to provide the ability to lower the value or do we think there's a little chance that will be necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we spoke offline and agreed that it's out of scope for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorted here 703546f

}

// We don't consider the lag time for the first two updates
if (i < 2) {
break;
}

// We've reached the first recorded block
// i >= 2 because we normally clear out the array from a FIFO approach
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outdated comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated comments 7720ab0

utils/src/deployer.rs Outdated Show resolved Hide resolved
…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
@@ -232,8 +232,8 @@ Solution: `export FOUNDRY_PROFILE=default`
## LightClient Contract Deployment

```bash
forge script contracts/script/LightClient.s.sol:DeployLightClientContractScript $numBlocksPerEpoch $numInitValidators \
--sig 'run(uint32, uint32)' \
forge script contracts/script/LightClient.s.sol:DeployLightClientContractScript $numBlocksPerEpoch $numInitValidators $maxHistorySeconds \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still use the old name maxHistorySeconds here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -7,7 +7,7 @@ import { LightClient as LC } from "../src/LightClient.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract DeployLightClientContractScript is Script {
function run(uint32 numBlocksPerEpoch, uint32 numInitValidators)
function run(uint32 numBlocksPerEpoch, uint32 numInitValidators, uint32 maxHistorySeconds)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
function run(uint32 numBlocksPerEpoch, uint32 numInitValidators, uint32 maxHistorySeconds)
function run(uint32 numBlocksPerEpoch, uint32 numInitValidators, uint32 stateHistoryRetentionPeriod)

Appears elsewhere too, need to do search/replace.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, sorted 33663d7

@sveitser sveitser self-requested a review August 22, 2024 17:06
Copy link
Collaborator

@sveitser sveitser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM missing some renames.

…pdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data
@alysiahuggins alysiahuggins merged commit 68db136 into main Aug 22, 2024
15 checks passed
@alysiahuggins alysiahuggins deleted the 1541-escape-hatch-purge-elements-from-stateupdates-and-hotshotcommitments-arrays-once-it-has-over-10-days-of-worth-of-data branch August 22, 2024 19:55
Copy link
Contributor

@alxiong alxiong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: instead of storing the timestamp for every update, why can't we simply store a single value oldest_timestamp which gets updated whenever we evict an element from the FIFO queue?

Comment on lines +12 to +14
optimizer = true
optimizer_runs = 200 # Increasing the number of runs saves gas but increases the size of the contract
viaIR = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this necessary? it's making the compilation really slow

IMO, we usually can resolve the "stack too deep" through some refactoring. e.g. various ways to reduce local variables on the stack.

Comment on lines +135 to +139
struct StateHistoryCommitment {
uint64 l1BlockHeight;
uint64 l1BlockTimestamp;
HotShotCommitment hotShotCommitment;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a benefit to declare this as a new struct rather than just modify the old HotShotCommitment struct with two new fields?

more complicated nested structs lead to larger memory layout.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants