Skip to content

Commit 0225de2

Browse files
GCdePaulaclaude
andcommitted
feat!: extract ITask from ITournament and add the Safety Gate
Introduce a minimal task abstraction (ITask/ITaskSpawner) that decouples DaveConsensus from the proof system: - ITournament now extends ITask; result() projects arbitrationResult without the winner commitment, and cleanup() recovers bonds. - ITournamentFactory is replaced by ITaskSpawner. - DaveConsensus spawns tasks and settles on result(); canSettle reports the final machine state instead of the winner commitment. Add the Safety Gate, a delay-only middleware task: - SafetyGateTask gates an inner task behind unanimous sentry votes, with a permissionless fallback timer for liveness. - SafetyGateTaskSpawner wraps an inner spawner; the security council can rotate the sentry set, effective from the next spawned task. - Deployment scripts wire DaveAppFactory through the gate spawner. Tasks implement ERC-165; the ISafetyGateTask interface id is pinned by a guard test for offchain gate detection. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2a0b4c2 commit 0225de2

22 files changed

Lines changed: 1489 additions & 103 deletions

cartesi-rollups/contracts/script/Deployment.s.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ contract DeploymentScript is BaseDeploymentScript {
1414

1515
address inputBox = _loadDeployment(".", "InputBox");
1616
address appFactory = _loadDeployment(".", "ApplicationFactory");
17-
address tournamentFactory = _loadDeployment(".", "MultiLevelTournamentFactory");
17+
address taskSpawner = _loadDeployment(".", "SafetyGateTaskSpawner");
1818

1919
vmSafe.startBroadcast();
2020

2121
_storeDeployment(
2222
type(DaveAppFactory).name,
23-
_create2(type(DaveAppFactory).creationCode, abi.encode(inputBox, appFactory, tournamentFactory))
23+
_create2(type(DaveAppFactory).creationCode, abi.encode(inputBox, appFactory, taskSpawner))
2424
);
2525

2626
vmSafe.stopBroadcast();

cartesi-rollups/contracts/src/DaveAppFactory.sol

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {IApplication} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicatio
1414
import {IApplicationFactory} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApplicationFactory.sol";
1515
import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol";
1616

17-
import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol";
17+
import {ITaskSpawner} from "prt-contracts/ITaskSpawner.sol";
1818
import {Machine} from "prt-contracts/types/Machine.sol";
1919

2020
import {DaveConsensus} from "./DaveConsensus.sol";
@@ -24,14 +24,14 @@ import {IDaveConsensus} from "./IDaveConsensus.sol";
2424
contract DaveAppFactory is IDaveAppFactory {
2525
IInputBox immutable INPUT_BOX;
2626
IApplicationFactory immutable APP_FACTORY;
27-
ITournamentFactory immutable TOURNAMENT_FACTORY;
27+
ITaskSpawner immutable TASK_SPAWNER;
2828

2929
IOutputsMerkleRootValidator constant NO_VALIDATOR = IOutputsMerkleRootValidator(address(0));
3030

31-
constructor(IInputBox inputBox, IApplicationFactory appFactory, ITournamentFactory tournamentFactory) {
31+
constructor(IInputBox inputBox, IApplicationFactory appFactory, ITaskSpawner taskSpawner) {
3232
INPUT_BOX = inputBox;
3333
APP_FACTORY = appFactory;
34-
TOURNAMENT_FACTORY = tournamentFactory;
34+
TASK_SPAWNER = taskSpawner;
3535
}
3636

3737
function newDaveApp(bytes32 templateHash, WithdrawalConfig calldata withdrawalConfig, bytes32 salt)
@@ -81,7 +81,7 @@ contract DaveAppFactory is IDaveAppFactory {
8181
returns (DaveConsensus)
8282
{
8383
Machine.Hash initialMachineStateHash = Machine.Hash.wrap(templateHash);
84-
return new DaveConsensus{salt: salt}(INPUT_BOX, appContract, TOURNAMENT_FACTORY, initialMachineStateHash);
84+
return new DaveConsensus{salt: salt}(INPUT_BOX, appContract, TASK_SPAWNER, initialMachineStateHash);
8585
}
8686

8787
/// @notice Calculates the address of an application contract.
@@ -106,8 +106,7 @@ contract DaveAppFactory is IDaveAppFactory {
106106
salt,
107107
keccak256(
108108
abi.encodePacked(
109-
type(DaveConsensus).creationCode,
110-
abi.encode(INPUT_BOX, appContract, TOURNAMENT_FACTORY, templateHash)
109+
type(DaveConsensus).creationCode, abi.encode(INPUT_BOX, appContract, TASK_SPAWNER, templateHash)
111110
)
112111
)
113112
);

cartesi-rollups/contracts/src/DaveConsensus.sol

Lines changed: 34 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,17 @@ import {LibKeccak256} from "cartesi-rollups-contracts-3.0.0/src/library/LibKecca
1616
import {LibMath} from "cartesi-rollups-contracts-3.0.0/src/library/LibMath.sol";
1717

1818
import {IDataProvider} from "prt-contracts/IDataProvider.sol";
19-
import {ITournament} from "prt-contracts/ITournament.sol";
20-
import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol";
19+
import {ITask} from "prt-contracts/ITask.sol";
20+
import {ITaskSpawner} from "prt-contracts/ITaskSpawner.sol";
2121

2222
import {Machine} from "prt-contracts/types/Machine.sol";
23-
import {Tree} from "prt-contracts/types/Tree.sol";
2423

2524
import {EmulatorConstants} from "step/src/EmulatorConstants.sol";
2625
import {Memory} from "step/src/Memory.sol";
2726

2827
import {IDaveConsensus} from "./IDaveConsensus.sol";
2928

30-
/// @notice Consensus contract with Dave tournaments.
29+
/// @notice Consensus contract with Dave tasks.
3130
///
3231
/// @notice This contract validates only one application,
3332
/// which read inputs from the InputBox contract.
@@ -60,8 +59,8 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
6059
/// @notice The application contract
6160
address immutable _APP_CONTRACT;
6261

63-
/// @notice The contract used to instantiate tournaments
64-
ITournamentFactory immutable _TOURNAMENT_FACTORY;
62+
/// @notice The contract used to instantiate tasks
63+
ITaskSpawner immutable _TASK_SPAWNER;
6564

6665
/// @notice Deployment block number
6766
uint256 immutable _DEPLOYMENT_BLOCK_NUMBER = block.number;
@@ -75,8 +74,8 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
7574
/// @notice Input index (exclusive) upper bound of the current sealed epoch
7675
uint256 _inputIndexUpperBound;
7776

78-
/// @notice Current sealed epoch tournament
79-
ITournament _tournament;
77+
/// @notice Current sealed epoch task
78+
ITask _task;
8079

8180
/// @notice Settled output trees' merkle root hash
8281
mapping(bytes32 => bool) _outputsMerkleRoots;
@@ -87,30 +86,30 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
8786
constructor(
8887
IInputBox inputBox,
8988
address appContract,
90-
ITournamentFactory tournamentFactory,
89+
ITaskSpawner taskSpawner,
9190
Machine.Hash initialMachineStateHash
9291
) {
9392
// Initialize immutable variables
9493
_INPUT_BOX = inputBox;
9594
_APP_CONTRACT = appContract;
96-
_TOURNAMENT_FACTORY = tournamentFactory;
97-
emit ConsensusCreation(inputBox, appContract, tournamentFactory);
95+
_TASK_SPAWNER = taskSpawner;
96+
emit ConsensusCreation(inputBox, appContract, taskSpawner);
9897

9998
// Initialize first sealed epoch
10099
uint256 inputIndexUpperBound = inputBox.getNumberOfInputs(appContract);
101100
_inputIndexUpperBound = inputIndexUpperBound;
102-
ITournament tournament = tournamentFactory.instantiate(initialMachineStateHash, this);
103-
_tournament = tournament;
104-
emit EpochSealed(0, 0, inputIndexUpperBound, initialMachineStateHash, bytes32(0), tournament);
101+
ITask task = taskSpawner.spawn(initialMachineStateHash, this);
102+
_task = task;
103+
emit EpochSealed(0, 0, inputIndexUpperBound, initialMachineStateHash, bytes32(0), task);
105104
}
106105

107106
function canSettle()
108107
external
109108
view
110109
override
111-
returns (bool isFinished, uint256 epochNumber, Tree.Node winnerCommitment)
110+
returns (bool isFinished, uint256 epochNumber, Machine.Hash finalState)
112111
{
113-
(isFinished, winnerCommitment,) = _tournament.arbitrationResult();
112+
(isFinished, finalState) = _task.result();
114113
epochNumber = _epochNumber;
115114
}
116115

@@ -119,14 +118,14 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
119118
override
120119
notForeclosed(_APP_CONTRACT)
121120
{
122-
// Check tournament settlement
121+
// Check task settlement
123122
require(epochNumber == _epochNumber, IncorrectEpochNumber(epochNumber, _epochNumber));
124123

125-
// Check tournament finished
126-
(bool isFinished,, Machine.Hash finalMachineStateHash) = _tournament.arbitrationResult();
124+
// Check task finished
125+
(bool isFinished, Machine.Hash finalMachineStateHash) = _task.result();
127126
require(isFinished, TournamentNotFinishedYet());
128-
ITournament oldTournament = _tournament;
129-
_tournament = ITournament(address(0));
127+
ITask oldTask = _task;
128+
_task = ITask(address(0));
130129

131130
// Check outputs Merkle root
132131
_validateOutputTree(finalMachineStateHash, outputsMerkleRoot, proof);
@@ -138,36 +137,26 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
138137
_outputsMerkleRoots[outputsMerkleRoot] = true;
139138
_lastFinalizedMachineStateHash = finalMachineStateHash;
140139

141-
// Start new tournament
142-
_tournament = _TOURNAMENT_FACTORY.instantiate(finalMachineStateHash, this);
140+
// Start new task
141+
_task = _TASK_SPAWNER.spawn(finalMachineStateHash, this);
143142

144143
emit EpochSealed(
145-
_epochNumber,
146-
_inputIndexLowerBound,
147-
_inputIndexUpperBound,
148-
finalMachineStateHash,
149-
outputsMerkleRoot,
150-
_tournament
144+
_epochNumber, _inputIndexLowerBound, _inputIndexUpperBound, finalMachineStateHash, outputsMerkleRoot, _task
151145
);
152146

153-
oldTournament.tryRecoveringBond();
147+
_tryCleanup(oldTask);
154148
}
155149

156150
function getCurrentSealedEpoch()
157151
external
158152
view
159153
override
160-
returns (
161-
uint256 epochNumber,
162-
uint256 inputIndexLowerBound,
163-
uint256 inputIndexUpperBound,
164-
ITournament tournament
165-
)
154+
returns (uint256 epochNumber, uint256 inputIndexLowerBound, uint256 inputIndexUpperBound, ITask task)
166155
{
167156
epochNumber = _epochNumber;
168157
inputIndexLowerBound = _inputIndexLowerBound;
169158
inputIndexUpperBound = _inputIndexUpperBound;
170-
tournament = _tournament;
159+
task = _task;
171160
}
172161

173162
function getInputBox() external view override returns (IInputBox) {
@@ -178,8 +167,8 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
178167
return _APP_CONTRACT;
179168
}
180169

181-
function getTournamentFactory() external view override returns (ITournamentFactory) {
182-
return _TOURNAMENT_FACTORY;
170+
function getTaskSpawner() external view override returns (ITaskSpawner) {
171+
return _TASK_SPAWNER;
183172
}
184173

185174
function provideMerkleRootOfInput(uint256 inputIndexWithinEpoch, bytes calldata input)
@@ -250,6 +239,12 @@ contract DaveConsensus is IDaveConsensus, ERC165, ApplicationChecker {
250239
require(machineStateHash == allegedStateHash, InvalidOutputsMerkleRootProof(finalMachineStateHash));
251240
}
252241

242+
/// @dev Best-effort: settlement must never be blocked by a task whose
243+
/// cleanup reverts.
244+
function _tryCleanup(ITask task) internal {
245+
try task.cleanup() returns (bool) {} catch {}
246+
}
247+
253248
modifier onlyValidAppContract(address appContract) {
254249
_ensureAppContractIsValid(appContract);
255250
_;

cartesi-rollups/contracts/src/IDaveConsensus.sol

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ import {IApplicationChecker} from "cartesi-rollups-contracts-3.0.0/src/dapp/IApp
1111
import {IInputBox} from "cartesi-rollups-contracts-3.0.0/src/inputs/IInputBox.sol";
1212

1313
import {IDataProvider} from "prt-contracts/IDataProvider.sol";
14-
import {ITournament} from "prt-contracts/ITournament.sol";
15-
import {ITournamentFactory} from "prt-contracts/ITournamentFactory.sol";
14+
import {ITask} from "prt-contracts/ITask.sol";
15+
import {ITaskSpawner} from "prt-contracts/ITaskSpawner.sol";
1616

1717
import {Machine} from "prt-contracts/types/Machine.sol";
18-
import {Tree} from "prt-contracts/types/Tree.sol";
1918

20-
/// @notice Consensus contract with Dave tournaments.
19+
/// @notice Consensus contract with Dave tasks.
2120
///
2221
/// @notice This contract validates only one application,
2322
/// which read inputs from the InputBox contract.
@@ -43,31 +42,33 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica
4342
/// @notice Consensus contract was created
4443
/// @param inputBox the input box contract
4544
/// @param appContract the application contract
46-
/// @param tournamentFactory the tournament factory contract
47-
event ConsensusCreation(IInputBox inputBox, address appContract, ITournamentFactory tournamentFactory);
45+
/// @param taskSpawner the task spawner contract
46+
event ConsensusCreation(IInputBox inputBox, address appContract, ITaskSpawner taskSpawner);
4847

4948
/// @notice An epoch was sealed
5049
/// @param epochNumber the sealed epoch number
5150
/// @param inputIndexLowerBound the input index (inclusive) lower bound in the sealed epoch
5251
/// @param inputIndexUpperBound the input index (exclusive) upper bound in the sealed epoch
5352
/// @param initialMachineStateHash the initial machine state hash
5453
/// @param outputsMerkleRoot the Merkle root hash of the outputs tree
55-
/// @param tournament the sealed epoch tournament contract
54+
/// @param task the sealed epoch task contract
5655
event EpochSealed(
5756
uint256 epochNumber,
5857
uint256 inputIndexLowerBound,
5958
uint256 inputIndexUpperBound,
6059
Machine.Hash initialMachineStateHash,
6160
bytes32 outputsMerkleRoot,
62-
ITournament tournament
61+
ITask task
6362
);
6463

6564
/// @notice Received epoch number is different from actual
6665
/// @param received The epoch number received as argument
6766
/// @param actual The actual epoch number in storage
6867
error IncorrectEpochNumber(uint256 received, uint256 actual);
6968

70-
/// @notice Tournament is not finished yet
69+
/// @notice Task is not finished yet
70+
/// @dev Kept under its historical name: downstream nodes classify
71+
/// settle reverts by error name.
7172
error TournamentNotFinishedYet();
7273

7374
/// @notice Hash of received input blob is different from stored on-chain
@@ -97,29 +98,24 @@ interface IDaveConsensus is IDataProvider, IOutputsMerkleRootValidator, IApplica
9798
/// @notice Get the address of the application contract.
9899
function getApplicationContract() external view returns (address);
99100

100-
/// @notice Get the tournament factory contract used to instantiate root tournaments.
101-
function getTournamentFactory() external view returns (ITournamentFactory);
101+
/// @notice Get the task spawner contract used to instantiate root tasks.
102+
function getTaskSpawner() external view returns (ITaskSpawner);
102103

103-
/// @notice Get the current sealed epoch number, boundaries, and tournament.
104+
/// @notice Get the current sealed epoch number, boundaries, and task.
104105
/// @param epochNumber The epoch number
105106
/// @param inputIndexLowerBound The epoch input index (inclusive) lower bound
106107
/// @param inputIndexUpperBound The epoch input index (exclusive) upper bound
107-
/// @param tournament The tournament that will decide the post-epoch state
108+
/// @param task The task that will decide the post-epoch state
108109
function getCurrentSealedEpoch()
109110
external
110111
view
111-
returns (
112-
uint256 epochNumber,
113-
uint256 inputIndexLowerBound,
114-
uint256 inputIndexUpperBound,
115-
ITournament tournament
116-
);
112+
returns (uint256 epochNumber, uint256 inputIndexLowerBound, uint256 inputIndexUpperBound, ITask task);
117113

118114
/// @notice Check whether the current sealed epoch can be settled.
119-
/// @return isFinished Whether the current sealed epoch tournament has finished yet
115+
/// @return isFinished Whether the current sealed epoch task has finished yet
120116
/// @return epochNumber The current sealed epoch number
121-
/// @return winnerCommitment If the tournament has finished, the winning commitment
122-
function canSettle() external view returns (bool isFinished, uint256 epochNumber, Tree.Node winnerCommitment);
117+
/// @return finalState If the task has finished, the final machine state
118+
function canSettle() external view returns (bool isFinished, uint256 epochNumber, Machine.Hash finalState);
123119

124120
/// @notice Settle the current sealed epoch.
125121
/// @param epochNumber The current sealed epoch number (used to avoid race conditions)

0 commit comments

Comments
 (0)