Skip to content

Commit 81ede4d

Browse files
authored
Add root storage cache (#47)
* Add fixed array for root history * Update _eraseMemberships to upate roots * Improve comments for new fixed array * Update to clarify comments * Add root cache tests and emit root storage event
1 parent 65f9e58 commit 81ede4d

2 files changed

Lines changed: 366 additions & 6 deletions

File tree

src/WakuRlnV2.sol

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
3737
/// @notice The Merkle tree that stores rate commitments of memberships
3838
LazyIMTData public merkleTree;
3939

40+
/// @notice Emitted whenever a new Merkle tree root is stored
41+
/// @param newRoot The newly stored Merkle tree root
42+
event RootStored(uint256 newRoot);
43+
4044
/// @notice Сheck if the idCommitment is valid
4145
/// @param idCommitment The idCommitment of the membership
4246
modifier onlyValidIdCommitment(uint256 idCommitment) {
@@ -57,6 +61,21 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
5761
_;
5862
}
5963

64+
// Fixed-size circular buffer for recent roots
65+
uint8 public constant HISTORY_SIZE = 5;
66+
67+
/// @notice Fixed-size circular buffer storing the most recent HISTORY_SIZE roots
68+
/// @dev Organized as a ring buffer where rootIndex points to the next write position
69+
uint256[HISTORY_SIZE] private recentRoots;
70+
71+
/// @notice Index pointing to the next slot to write in the circular buffer
72+
/// @dev Wraps around using modulo HISTORY_SIZE arithmetic
73+
uint8 private rootIndex;
74+
75+
/// @notice Flag indicating if any roots have been stored yet
76+
/// @dev Used to distinguish empty buffer from buffer with zero values
77+
bool private rootsInitialized;
78+
6079
constructor() {
6180
_disableInitializers();
6281
}
@@ -178,6 +197,51 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
178197
emit MembershipRegistered(idCommitment, rateLimit, index);
179198
}
180199

200+
/// @notice Adds a new root to the history (called after tree update)
201+
function _storeNewRoot(uint256 newRoot) internal {
202+
// Initialize buffer on first call
203+
if (!rootsInitialized) {
204+
recentRoots[0] = newRoot;
205+
rootIndex = 1;
206+
rootsInitialized = true;
207+
} else {
208+
recentRoots[rootIndex] = newRoot;
209+
rootIndex = (rootIndex + 1) % HISTORY_SIZE;
210+
}
211+
212+
emit RootStored(newRoot);
213+
}
214+
215+
/// @notice Returns the list of recent roots, newest first
216+
function getRecentRoots() external view returns (uint256[HISTORY_SIZE] memory ordered) {
217+
// Returns empty array if no roots have been stored yet
218+
if (!rootsInitialized) {
219+
return ordered;
220+
}
221+
222+
uint8 index = rootIndex;
223+
for (uint8 i = 0; i < HISTORY_SIZE; i++) {
224+
// Traverse backwards from most recent
225+
uint8 idx = (index + HISTORY_SIZE - 1 - i) % HISTORY_SIZE;
226+
ordered[i] = recentRoots[idx];
227+
}
228+
}
229+
230+
/// @notice Get the root at a specific position (0 = newest)
231+
function getRootAt(uint8 position) external view returns (uint256) {
232+
require(position < HISTORY_SIZE, "Out of range");
233+
require(rootsInitialized, "No roots yet");
234+
235+
uint8 index = (rootIndex + HISTORY_SIZE - 1 - position) % HISTORY_SIZE;
236+
return recentRoots[index];
237+
}
238+
239+
/// @notice Returns the root of the Merkle tree that stores rate commitments of memberships
240+
/// @return The root of the Merkle tree that stores rate commitments of memberships
241+
function root() public view returns (uint256) {
242+
return LazyIMT.root(merkleTree, MERKLE_TREE_DEPTH);
243+
}
244+
181245
/// @dev Register a membership (internal function)
182246
/// @param idCommitment The idCommitment of the membership
183247
/// @param rateLimit The rate limit of the membership
@@ -189,12 +253,9 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
189253
LazyIMT.insert(merkleTree, rateCommitment);
190254
nextFreeIndex += 1;
191255
}
192-
}
193256

194-
/// @notice Returns the root of the Merkle tree that stores rate commitments of memberships
195-
/// @return The root of the Merkle tree that stores rate commitments of memberships
196-
function root() external view returns (uint256) {
197-
return LazyIMT.root(merkleTree, MERKLE_TREE_DEPTH);
257+
// ✅ After updating or inserting, capture and store the new root
258+
_storeNewRoot(root());
198259
}
199260

200261
/// @notice Returns the Merkle proof that a given membership is in the membership set
@@ -247,15 +308,21 @@ contract WakuRlnV2 is Initializable, OwnableUpgradeable, UUPSUpgradeable, Member
247308
// eraseFromMembershipSet == false means lazy erasure.
248309
// Only erase memberships from the memberships array (consume less gas).
249310
// Merkle tree data will be overwritten when the correspondind index is reused.
311+
bool treeModified = false;
250312
for (uint256 i = 0; i < idCommitmentsToErase.length; i++) {
251313
// Erase the membership from the memberships array in contract storage
252314
uint32 indexToErase = _eraseMembershipLazily(_msgSender(), idCommitmentsToErase[i]);
253315
// Optionally, also erase the rate commitment data from the membership set.
254316
// This does not affect the total rate limit control, or index reusal for new membership registrations.
255317
if (eraseFromMembershipSet) {
256318
LazyIMT.update(merkleTree, 0, indexToErase);
319+
treeModified = true;
257320
}
258321
}
322+
// Record only the final root once per batch full clean-up call to avoid duplicates.
323+
if (treeModified) {
324+
_storeNewRoot(root());
325+
}
259326
}
260327

261328
/// @notice Withdraw any available deposit balance in tokens after a membership is erased

0 commit comments

Comments
 (0)