1+ // SPDX-License-Identifier: MIT
2+ pragma solidity ^ 0.8.20 ;
3+
4+ import "./ValidatorFactory.sol " ;
5+
6+ /**
7+ * @title TransactionManager
8+ * @dev Optimistic consensus system with LLM validation and validator signatures
9+ * Features:
10+ * - Transaction proposal with optimistic execution
11+ * - Mock LLM validation (deterministic hash-based)
12+ * - ECDSA signature verification with 3/5 consensus
13+ * - Challenge period for disputes
14+ */
15+ contract TransactionManager {
16+ // Events
17+ event ProposalSubmitted (bytes32 indexed proposalId , string transaction , address indexed submitter );
18+ event ProposalOptimisticallyApproved (bytes32 indexed proposalId );
19+ event ProposalChallenged (bytes32 indexed proposalId , address indexed challenger );
20+ event ProposalFinalized (bytes32 indexed proposalId , bool approved );
21+ event ValidatorSigned (bytes32 indexed proposalId , address indexed validator );
22+ event LLMValidationResult (bytes32 indexed proposalId , bool isValid );
23+
24+ // State variables
25+ ValidatorFactory public validatorFactory;
26+
27+ mapping (bytes32 => Proposal) public proposals;
28+ mapping (bytes32 => mapping (address => bool )) public hasValidatorSigned;
29+ mapping (bytes32 => address []) public proposalSigners;
30+ uint256 public proposalCount;
31+
32+ // Constants
33+ uint256 public constant CHALLENGE_PERIOD = 50 ; // blocks
34+ uint256 public constant REQUIRED_SIGNATURES = 3 ; // 3 out of 5 validators
35+ uint256 public constant VALIDATOR_SET_SIZE = 5 ; // top 5 validators for consensus
36+
37+ // Proposal states
38+ enum ProposalState {
39+ Proposed, // Just submitted
40+ OptimisticApproved, // Enough signatures, optimistically approved
41+ Challenged, // Someone challenged the proposal
42+ Finalized, // Final decision made
43+ Reverted // Proposal was invalid/rejected
44+ }
45+
46+ // Structs
47+ struct Proposal {
48+ bytes32 proposalId;
49+ string transaction;
50+ address proposer;
51+ uint256 blockNumber;
52+ uint256 challengeDeadline;
53+ ProposalState state;
54+ address challenger;
55+ uint256 signatureCount;
56+ bool llmValidation;
57+ bool executed;
58+ address [] selectedValidators; // validators selected for this proposal
59+ }
60+
61+ constructor (address _validatorFactory ) {
62+ validatorFactory = ValidatorFactory (_validatorFactory);
63+ }
64+
65+ /**
66+ * @dev Submit a proposal for consensus
67+ * @param transaction Transaction string to be validated (e.g., "Approve loan for user X based on LLM analysis")
68+ * @return proposalId Unique identifier for the proposal
69+ */
70+ function submitProposal (string calldata transaction ) external returns (bytes32 proposalId ) {
71+ require (bytes (transaction).length > 0 , "Empty transaction " );
72+
73+ proposalId = keccak256 (abi.encodePacked (transaction, block .timestamp , msg .sender ));
74+ require (proposals[proposalId].proposalId == bytes32 (0 ), "Proposal already exists " );
75+
76+ // Get top validators for this proposal
77+ address [] memory topValidators = _getTopValidators ();
78+ require (topValidators.length >= REQUIRED_SIGNATURES, "Not enough validators " );
79+
80+ // Perform mock LLM validation
81+ bool llmResult = _mockLLMValidation (transaction);
82+
83+ proposals[proposalId] = Proposal ({
84+ proposalId: proposalId,
85+ transaction: transaction,
86+ proposer: msg .sender ,
87+ blockNumber: block .number ,
88+ state: ProposalState.Proposed,
89+ challengeDeadline: block .number + CHALLENGE_PERIOD,
90+ challenger: address (0 ),
91+ signatureCount: 0 ,
92+ llmValidation: llmResult,
93+ executed: false ,
94+ selectedValidators: topValidators
95+ });
96+
97+ proposalCount++ ;
98+
99+ emit ProposalSubmitted (proposalId, transaction, msg .sender );
100+ emit LLMValidationResult (proposalId, llmResult);
101+
102+ return proposalId;
103+ }
104+
105+ /**
106+ * @dev Validators sign a proposal using ECDSA signatures
107+ * @param proposalId Proposal identifier
108+ * @param signature ECDSA signature of the proposal hash
109+ */
110+ function signProposal (bytes32 proposalId , bytes calldata signature ) external {
111+ Proposal storage proposal = proposals[proposalId];
112+ require (proposal.proposalId != bytes32 (0 ), "Proposal not found " );
113+ require (proposal.state == ProposalState.Proposed, "Invalid proposal state " );
114+ require (block .number <= proposal.challengeDeadline, "Challenge period expired " );
115+ require (! hasValidatorSigned[proposalId][msg .sender ], "Already signed " );
116+
117+ // Verify that sender is one of the selected validators for this proposal
118+ require (_isSelectedValidator (proposalId, msg .sender ), "Not a selected validator " );
119+
120+ // Verify the signature
121+ bytes32 messageHash = _getProposalHash (proposalId, proposal.transaction);
122+ address recoveredSigner = _recoverSigner (messageHash, signature);
123+ require (recoveredSigner == msg .sender , "Invalid signature " );
124+
125+ // Record the signature
126+ hasValidatorSigned[proposalId][msg .sender ] = true ;
127+ proposalSigners[proposalId].push (msg .sender );
128+ proposal.signatureCount++ ;
129+
130+ emit ValidatorSigned (proposalId, msg .sender );
131+
132+ // Check if we have enough signatures for optimistic approval
133+ if (proposal.signatureCount >= REQUIRED_SIGNATURES && proposal.llmValidation) {
134+ proposal.state = ProposalState.OptimisticApproved;
135+ proposal.executed = true ;
136+ emit ProposalOptimisticallyApproved (proposalId);
137+ }
138+ }
139+
140+ /**
141+ * @dev Challenge a proposal during the challenge period
142+ * @param proposalId Proposal identifier
143+ */
144+ function challengeProposal (bytes32 proposalId ) external {
145+ Proposal storage proposal = proposals[proposalId];
146+ require (proposal.proposalId != bytes32 (0 ), "Proposal not found " );
147+ require (proposal.state == ProposalState.OptimisticApproved, "Cannot challenge " );
148+ require (block .number <= proposal.challengeDeadline, "Challenge period expired " );
149+ require (validatorFactory.isActiveValidator (msg .sender ), "Not a validator " );
150+
151+ proposal.state = ProposalState.Challenged;
152+ proposal.challenger = msg .sender ;
153+ proposal.executed = false ; // Revert optimistic execution
154+
155+ emit ProposalChallenged (proposalId, msg .sender );
156+ }
157+
158+ /**
159+ * @dev Finalize a proposal after challenge period or after challenge resolution
160+ * @param proposalId Proposal identifier
161+ */
162+ function finalizeProposal (bytes32 proposalId ) external {
163+ Proposal storage proposal = proposals[proposalId];
164+ require (proposal.proposalId != bytes32 (0 ), "Proposal not found " );
165+ require (block .number > proposal.challengeDeadline, "Challenge period not ended " );
166+
167+ bool approved = false ;
168+
169+ if (proposal.state == ProposalState.OptimisticApproved) {
170+ // No challenge during period - approve
171+ approved = true ;
172+ proposal.executed = true ;
173+ } else if (proposal.state == ProposalState.Challenged) {
174+ // Resolve challenge based on signatures and LLM validation
175+ approved = proposal.signatureCount >= REQUIRED_SIGNATURES && proposal.llmValidation;
176+ proposal.executed = approved;
177+ }
178+
179+ proposal.state = approved ? ProposalState.Finalized : ProposalState.Reverted;
180+
181+ emit ProposalFinalized (proposalId, approved);
182+ }
183+
184+ /**
185+ * @dev Mock LLM validation using deterministic function
186+ * @param transaction Transaction string to validate
187+ * @return isValid Whether the transaction is valid (based on hash even/odd)
188+ */
189+ function _mockLLMValidation (string memory transaction ) internal pure returns (bool isValid ) {
190+ bytes32 hash = keccak256 (abi.encodePacked (transaction));
191+ // Return true if hash is even (last bit is 0)
192+ return (uint256 (hash) % 2 == 0 );
193+ }
194+
195+ /**
196+ * @dev Get top validators for consensus
197+ * @return validators Array of top validator addresses
198+ */
199+ function _getTopValidators () internal view returns (address [] memory ) {
200+ uint256 validatorCount = validatorFactory.getValidatorCount ();
201+ if (validatorCount == 0 ) {
202+ return new address [](0 );
203+ }
204+
205+ uint256 count = validatorCount < VALIDATOR_SET_SIZE ? validatorCount : VALIDATOR_SET_SIZE;
206+ (address [] memory topValidators ,) = validatorFactory.getTopNValidators (count);
207+ return topValidators;
208+ }
209+
210+ /**
211+ * @dev Check if address is a selected validator for the proposal
212+ * @param proposalId Proposal identifier
213+ * @param validator Validator address
214+ * @return isSelected Whether the validator was selected for this proposal
215+ */
216+ function _isSelectedValidator (bytes32 proposalId , address validator ) internal view returns (bool isSelected ) {
217+ address [] memory selectedValidators = proposals[proposalId].selectedValidators;
218+ for (uint256 i = 0 ; i < selectedValidators.length ; i++ ) {
219+ if (selectedValidators[i] == validator) {
220+ return true ;
221+ }
222+ }
223+ return false ;
224+ }
225+
226+ /**
227+ * @dev Create proposal hash for signature verification
228+ * @param proposalId Proposal identifier
229+ * @param transaction Transaction string
230+ * @return hash Message hash for signing
231+ */
232+ function _getProposalHash (bytes32 proposalId , string memory transaction ) internal pure returns (bytes32 ) {
233+ return keccak256 (abi.encodePacked (
234+ "\x19Ethereum Signed Message:\n32 " ,
235+ keccak256 (abi.encodePacked (proposalId, transaction))
236+ ));
237+ }
238+
239+ /**
240+ * @dev Recover signer from signature
241+ * @param messageHash Message hash
242+ * @param signature ECDSA signature
243+ * @return signer Recovered signer address
244+ */
245+ function _recoverSigner (bytes32 messageHash , bytes memory signature ) internal pure returns (address ) {
246+ require (signature.length == 65 , "Invalid signature length " );
247+
248+ bytes32 r;
249+ bytes32 s;
250+ uint8 v;
251+
252+ assembly {
253+ r := mload (add (signature, 32 ))
254+ s := mload (add (signature, 64 ))
255+ v := byte (0 , mload (add (signature, 96 )))
256+ }
257+
258+ if (v < 27 ) {
259+ v += 27 ;
260+ }
261+
262+ require (v == 27 || v == 28 , "Invalid signature v value " );
263+
264+ return ecrecover (messageHash, v, r, s);
265+ }
266+
267+ // ==================== VIEW FUNCTIONS ====================
268+
269+ /**
270+ * @dev Get proposal information
271+ * @param proposalId Proposal identifier
272+ * @return transaction Transaction string
273+ * @return proposer Proposal submitter
274+ * @return blockNumber Block number when submitted
275+ * @return state Current proposal state
276+ * @return challengeDeadline Challenge deadline
277+ * @return challenger Address of challenger
278+ * @return signatureCount Number of validator signatures
279+ * @return llmValidation LLM validation result
280+ * @return executed Whether proposal was executed
281+ */
282+ function getProposal (bytes32 proposalId ) external view returns (
283+ string memory transaction ,
284+ address proposer ,
285+ uint256 blockNumber ,
286+ ProposalState state ,
287+ uint256 challengeDeadline ,
288+ address challenger ,
289+ uint256 signatureCount ,
290+ bool llmValidation ,
291+ bool executed
292+ ) {
293+ Proposal storage proposal = proposals[proposalId];
294+ return (
295+ proposal.transaction,
296+ proposal.proposer,
297+ proposal.blockNumber,
298+ proposal.state,
299+ proposal.challengeDeadline,
300+ proposal.challenger,
301+ proposal.signatureCount,
302+ proposal.llmValidation,
303+ proposal.executed
304+ );
305+ }
306+
307+ /**
308+ * @dev Get selected validators for a proposal
309+ * @param proposalId Proposal identifier
310+ * @return validators Array of selected validator addresses
311+ */
312+ function getProposalValidators (bytes32 proposalId ) external view returns (address [] memory ) {
313+ return proposals[proposalId].selectedValidators;
314+ }
315+
316+ /**
317+ * @dev Get signers for a proposal
318+ * @param proposalId Proposal identifier
319+ * @return signers Array of addresses that signed the proposal
320+ */
321+ function getProposalSigners (bytes32 proposalId ) external view returns (address [] memory ) {
322+ return proposalSigners[proposalId];
323+ }
324+
325+ /**
326+ * @dev Check if proposal is approved and executed
327+ * @param proposalId Proposal identifier
328+ * @return approved Whether the proposal is approved and executed
329+ */
330+ function isProposalApproved (bytes32 proposalId ) external view returns (bool ) {
331+ Proposal storage proposal = proposals[proposalId];
332+ return proposal.state == ProposalState.OptimisticApproved ||
333+ (proposal.state == ProposalState.Finalized && proposal.executed);
334+ }
335+
336+ /**
337+ * @dev Test the mock LLM validation function
338+ * @param transaction Transaction string to test
339+ * @return isValid Result of mock LLM validation
340+ */
341+ function testLLMValidation (string calldata transaction ) external pure returns (bool ) {
342+ return _mockLLMValidation (transaction);
343+ }
344+
345+ /**
346+ * @dev Get current validator count
347+ * @return count Number of active validators
348+ */
349+ function getValidatorCount () external view returns (uint256 ) {
350+ return validatorFactory.getValidatorCount ();
351+ }
352+
353+ /**
354+ * @dev Get top validators currently selected for consensus
355+ * @return validators Array of top validator addresses
356+ */
357+ function getCurrentTopValidators () external view returns (address [] memory ) {
358+ return _getTopValidators ();
359+ }
360+ }
0 commit comments