Skip to content

Commit e063413

Browse files
committed
chore(gateway-contracts): ensure there is always a single active context
1 parent f768df3 commit e063413

File tree

11 files changed

+352
-89
lines changed

11 files changed

+352
-89
lines changed

gateway-contracts/contracts/CoprocessorContexts.sol

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -235,15 +235,13 @@ contract CoprocessorContexts is
235235
$.coprocessorContextSuspendedTimePeriod[activeContextId];
236236
$.coprocessorContextDeactivatedBlockTimestamp[activeContextId] = deactivatedBlockTimestamp;
237237

238-
// Set the current active coprocessor context to the suspended state
239-
ContextLifecycle.setSuspended($.coprocessorContextLifecycle, activeContextId);
238+
// Set the new context as active and the current active context as suspended
239+
ContextLifecycle.setActiveAndSuspended(
240+
$.coprocessorContextLifecycle,
241+
preActivationContextId,
242+
activeContextId
243+
);
240244
emit SuspendCoprocessorContext(activeContextId, deactivatedBlockTimestamp);
241-
242-
// Set the new active coprocessor context
243-
// It is mandatory to directly set the new active coprocessor context right after suspending
244-
// the current one in order to avoid having a state where no active context is available,
245-
// which should never happen.
246-
ContextLifecycle.setActive($.coprocessorContextLifecycle, preActivationContextId);
247245
emit ActivateCoprocessorContext(preActivationContextId);
248246
}
249247

@@ -260,25 +258,20 @@ contract CoprocessorContexts is
260258

261259
/**
262260
* @dev See {ICoprocessorContexts-forceUpdateContextToStatus}.
263-
* ⚠️ This function should be used with caution as it can lead to unexpected behaviors if not
264-
* used correctly. See the interface documentation for more details. ⚠️
265261
*/
266262
function forceUpdateContextToStatus(
267263
uint256 contextId,
268264
ContextStatus status
269265
) external virtual onlyGatewayOwner ensureContextInitialized(contextId) {
270266
CoprocessorContextsStorage storage $ = _getCoprocessorContextsStorage();
271267
if (status == ContextStatus.Active) {
272-
ContextLifecycle.setActive($.coprocessorContextLifecycle, contextId);
273-
emit ActivateCoprocessorContext(contextId);
268+
// Get the current active context ID
269+
uint256 activeContextId = getActiveCoprocessorContextId();
274270

275-
// Set the suspended block timestamp to the current block timestamp (i.e., the status update
276-
// is immediate)
277-
// ⚠️ This should be used with caution as it will create a state where no context is active,
278-
// which should never happen. Please make sure to activate a context right after. ⚠️
279-
} else if (status == ContextStatus.Suspended) {
280-
ContextLifecycle.setSuspended($.coprocessorContextLifecycle, contextId);
281-
emit SuspendCoprocessorContext(contextId, block.timestamp);
271+
// Set the new context as active and the current active context as suspended
272+
ContextLifecycle.setActiveAndSuspended($.coprocessorContextLifecycle, contextId, activeContextId);
273+
emit SuspendCoprocessorContext(activeContextId, block.timestamp);
274+
emit ActivateCoprocessorContext(contextId);
282275
} else if (status == ContextStatus.Deactivated) {
283276
ContextLifecycle.setDeactivated($.coprocessorContextLifecycle, contextId);
284277
emit DeactivateCoprocessorContext(contextId);
@@ -307,17 +300,14 @@ contract CoprocessorContexts is
307300

308301
CoprocessorContextsStorage storage $ = _getCoprocessorContextsStorage();
309302

310-
// Suspend the (problematic) active coprocessor context
311-
ContextLifecycle.setSuspended($.coprocessorContextLifecycle, activeContextId);
303+
// Re-activate the suspended coprocessor context and suspend the (problematic) active context
304+
ContextLifecycle.setActiveAndSuspended($.coprocessorContextLifecycle, suspendedContextId, activeContextId);
312305

313306
// Define the deactivation block timestamp for the current active coprocessor context
314307
uint256 deactivatedBlockTimestamp = block.timestamp + suspendedTimePeriod;
315308
$.coprocessorContextDeactivatedBlockTimestamp[activeContextId] = deactivatedBlockTimestamp;
316309

317310
emit SuspendCoprocessorContext(activeContextId, deactivatedBlockTimestamp);
318-
319-
// Re-activate the suspended coprocessor context
320-
ContextLifecycle.setActive($.coprocessorContextLifecycle, suspendedContextId);
321311
emit ActivateCoprocessorContext(suspendedContextId);
322312
}
323313

gateway-contracts/contracts/interfaces/ICoprocessorContexts.sol

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -232,19 +232,14 @@ interface ICoprocessorContexts {
232232

233233
/**
234234
* @notice Manually force the status update of a coprocessor context.
235-
* ⚠️ This function should be used with caution as it can lead to unexpected behaviors if not
236-
* used correctly. ⚠️
237-
* Hence, prior to using this function, the caller should make sure that:
238-
* - the status update is not against any of the lifecycle's rules (else it will revert)
239-
* - the usually expected requirements (in the whole protocol) for the status update are met
240-
* Additionally:
241-
* - this function expects the context to already have been added and thus will revert if the
242-
* targeted status does not reflect that
243-
* - if a status update needs to be associated to a block timestamp, the current block timestamp
244-
* will be used (i.e., the status update is immediate)
235+
* This function reverts if
236+
* - the status update is against any of the lifecycle's rules
237+
* - the context has not been added yet
245238
* The following context status updates are only possible through this function:
246239
* - Compromised
247240
* - Destroyed
241+
* Additionally, if a status update needs to be associated to a block timestamp, the current
242+
* block timestamp is used (i.e., the status update is immediate)
248243
* @param contextId The ID of the coprocessor context to update.
249244
* @param status The status to update the coprocessor context to.
250245
*/

gateway-contracts/contracts/libraries/ContextLifecycle.sol

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ library ContextLifecycle {
4848
*/
4949
error PreActivationContextOngoing(uint256 preActivationContextId);
5050

51+
/**
52+
* @notice Error indicating that an active context is ongoing.
53+
*/
54+
error ActiveContextOngoing(uint256 activeContextId);
55+
5156
/**
5257
* @notice Error indicating that a suspended context is ongoing.
5358
*/
@@ -170,7 +175,7 @@ library ContextLifecycle {
170175
) internal onlyNonNullContextId(contextId) {
171176
// This function should only be called if there is no active context yet
172177
if ($.activeContextId != 0) {
173-
revert ContextIsActive($.activeContextId);
178+
revert ActiveContextOngoing($.activeContextId);
174179
}
175180

176181
$.contextStatuses[contextId] = ContextStatus.Active;
@@ -182,12 +187,18 @@ library ContextLifecycle {
182187
* @dev There should only be one active context at a time.
183188
* @param contextId The ID of the context to set as active.
184189
*/
185-
function setActive(ContextLifecycleStorage storage $, uint256 contextId) internal onlyNonNullContextId(contextId) {
190+
function _setActive(ContextLifecycleStorage storage $, uint256 contextId) internal onlyNonNullContextId(contextId) {
186191
// Only a pre-activated or suspended context can be set as active
187192
if (!isPreActivation($, contextId) && !isSuspended($, contextId)) {
188193
revert ContextNotPreActivatedOrSuspended(contextId);
189194
}
190195

196+
// There should only be one active context at a time. The old active context must be suspended
197+
// first before a new active context can be set.
198+
if ($.activeContextId != 0) {
199+
revert ActiveContextOngoing($.activeContextId);
200+
}
201+
191202
$.contextStatuses[contextId] = ContextStatus.Active;
192203
$.activeContextId = contextId;
193204

@@ -205,13 +216,13 @@ library ContextLifecycle {
205216
/**
206217
* @notice Sets the context as suspended.
207218
* ⚠️ This function should be used with caution as it can lead to unexpected behaviors if not
208-
* used correctly. ⚠️
219+
* used correctly. Use `setActiveAndSuspended` instead. ⚠️
209220
* A suspended context is expected to always be followed by a context activation in order to
210221
* avoid having a state where no active context is available, which should never happen.
211222
* @dev There can only be one suspended context at a time.
212223
* @param contextId The ID of the context to set as suspended.
213224
*/
214-
function setSuspended(
225+
function _setSuspended(
215226
ContextLifecycleStorage storage $,
216227
uint256 contextId
217228
) internal onlyNonNullContextId(contextId) {
@@ -227,6 +238,22 @@ library ContextLifecycle {
227238
$.activeContextId = 0;
228239
}
229240

241+
/**
242+
* @notice Sets the contexts as active and suspended.
243+
* @dev This function should be favored over setting the contexts separately as it ensures that
244+
* there is always a (single) active context.
245+
* @param contextIdToActivate The ID of the context to set as active.
246+
* @param contextIdToSuspend The ID of the context to set as suspended.
247+
*/
248+
function setActiveAndSuspended(
249+
ContextLifecycleStorage storage $,
250+
uint256 contextIdToActivate,
251+
uint256 contextIdToSuspend
252+
) internal {
253+
_setSuspended($, contextIdToSuspend);
254+
_setActive($, contextIdToActivate);
255+
}
256+
230257
/**
231258
* @notice Sets the context as deactivated.
232259
* @dev There can be multiple deactivated contexts at the same time.

gateway-contracts/contracts/mocks/CoprocessorContextsMock.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ contract CoprocessorContextsMock {
6161
function forceUpdateContextToStatus(uint256 contextId, ContextStatus status) external {
6262
uint256 deactivatedBlockTimestamp;
6363

64-
emit ActivateCoprocessorContext(contextId);
65-
6664
emit SuspendCoprocessorContext(contextId, deactivatedBlockTimestamp);
6765

66+
emit ActivateCoprocessorContext(contextId);
67+
6868
emit DeactivateCoprocessorContext(contextId);
6969

7070
emit CompromiseCoprocessorContext(contextId);
-137 Bytes
Loading

gateway-contracts/docs/getting-started/contracts/coprocessor_contexts.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ Additionally:
9191
- in case of emergency (ex: bad software update), the owner can directly move a context from `suspended` back to `active`, which deactivates the context with issues.
9292
- an `active` context cannot be set to `compromised`,`deactivated` or `destroyed`, in order to ensure that the fhevm Gateway always has one active context.
9393

94-
The complete lifecycle of a coprocessor context is represented in the following diagram:
94+
The complete lifecycle of a coprocessor context is represented in the following diagram, with the following legend:
95+
96+
- _Red_: Automated status update
97+
- _Black_: Manual only status update
9598

9699
![Context lifecycle](../../.assets/lifecycle_contexts.png)
97100

0 commit comments

Comments
 (0)