This repository has been archived by the owner on May 9, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
DisputableStateChannel.sol
484 lines (353 loc) · 28.1 KB
/
DisputableStateChannel.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
//************************************************************************************************
//** This code is part of a reference implementation of State Channels from FunFair
//** It is for reference purposes only. It has not been thoroughly audited
//** DO NOT DEPLOY THIS to mainnet
//************************************************************************************************
import "./StateChannel.sol";
//************************************************************************************************
//** Disputable State Channel
contract DisputableStateChannel is StateChannel {
//************************************************************************************************
//** Indices
// Additional State Channel Close Reasons
uint256 constant CLOSE_CHANNEL_RESOLUTION_NO_DISPUTE = 1;
uint256 constant CLOSE_CHANNEL_P0_TIMED_OUT = 2;
uint256 constant CLOSE_CHANNEL_P1_TIMED_OUT = 3;
uint256 constant CLOSE_CHANNEL_CHALLENGED_WITH_LATER_STATE_P0_PENALTY = 4;
uint256 constant CLOSE_CHANNEL_CHALLENGED_WITH_LATER_STATE_P1_PENALTY = 5;
uint256 constant CLOSE_CHANNEL_CHALLENGED_WITH_DIFFERENT_ACTION_P0_PENALTY = 6;
uint256 constant CLOSE_CHANNEL_CHALLENGED_WITH_DIFFERENT_ACTION_P1_PENALTY = 7;
//************************************************************************************************
//** Data Structures
struct DisputeData {
// dispute information
uint256 startTime;
uint256 initiator;
bytes32 stateContentsHash;
bytes32 actionContentsHash;
uint256 stateNonce;
uint256 openBlock;
uint256 resolutionBlock;
}
//************************************************************************************************
//** Persistent storage
// Dispute Data
mapping (bytes32 => DisputeData) public disputeData;
// Timeout Periods - note that this is in storage for ease of development
uint256[2] timemoutPeriods = [2 hours, 4 hours];
//************************************************************************************************
//** Events
event DisputeInitiatedWithoutAction(bytes32 indexed channelID, address indexed participant0address, address indexed participant1address, State state);
event DisputeInitiatedWithAction(bytes32 indexed channelID, address indexed participant0address, address indexed participant1address, State state, Action action, State proposedNewState);
event DisputeResolvedWithAction(bytes32 indexed channelID, address indexed participant0address, address indexed participant1address, Action action, State newState);
//************************************************************************************************
function validateTimeout(DisputeData memory dispute) internal view returns (bool) {
// has the appropriate amount of time elapsed for a timeout?
uint256 timeoutPeriod;
// Different for P0 and P1
if (dispute.initiator == 0) {
timeoutPeriod = timemoutPeriods[0];
} else {
timeoutPeriod = timemoutPeriods[1];
}
return (now > dispute.startTime + timeoutPeriod);
}
//************************************************************************************************
//************************************************************************************************
//** Dispute Initiation
//************************************************************************************************
//************************************************************************************************
// note that the nonce of a disputed state needs to be strictly greater than that of any previous disputed state
// (and that the initial state nonce is 1, whilst the initial "highest nonce of a previously disputed state" is 0)
//************************************************************************************************
//** Dispute without action
// This can only happen at a point when the channel is finalizable
// it's basically a "do you want to carry on or not"? This can only be called by one of the participants
// it can be resolved with an action from the other party, or by closing with no dispute
// if the only participant that can act on this state is the disputer, then this effectively forces the channel to close as you can't resolve your own dispute
function disputeWithoutAction(bytes memory packedOpenChannelData, State memory state) public {
//************************************************************************************************
// Decode data
StateChannelOpenData memory stateChannelOpenData = abi.decode(packedOpenChannelData, (StateChannelOpenData));
IStateMachine stateMachine = IStateMachine(stateChannelOpenData.stateMachineAddress);
//************************************************************************************************
// Validity Checks
// Is the Open Channel Data valid?
require(stateChannelData[stateChannelOpenData.channelID].packedOpenChannelDataHash == keccak256(packedOpenChannelData), "Invalid State Channel Open Data");
// is the State Channel open and not in dispute
require(stateChannelData[stateChannelOpenData.channelID].channelStatus == STATE_CHANNEL_STATUS_OPEN, "State Channel is not open");
// validate state against channel
ffrequire(validateStateContents(state.contents, stateChannelOpenData));
// does the state machine consider this a valid point to close a channel
require(stateMachine.isStateFinalisable(state.contents.packedStateMachineState), "State is not finalisable");
// is the state correctly co-signed?
bytes32 stateContentsHash = keccak256(abi.encode(state.contents));
require(validateSignature(stateContentsHash, state.signatures[0], stateChannelOpenData.participants[0].participantAddress, stateChannelOpenData.participants[0].signingAddress), "Participant #0 state signature validation failed");
require(validateSignature(stateContentsHash, state.signatures[1], stateChannelOpenData.participants[1].participantAddress, stateChannelOpenData.participants[1].signingAddress), "Participant #1 state signature validation failed");
// was it initiated by one of the participants?
uint256 initiator;
if ((msg.sender == stateChannelOpenData.participants[0].participantAddress) || (msg.sender == stateChannelOpenData.participants[0].signingAddress)) {
initiator = 0;
} else if ((msg.sender == stateChannelOpenData.participants[1].participantAddress) || (msg.sender == stateChannelOpenData.participants[1].signingAddress)) {
initiator = 1;
} else {
require(false, "Dispute initiated by non-participant");
}
// is the state nonce greater than any previous dispute?
require(state.contents.nonce > disputeData[stateChannelOpenData.channelID].stateNonce, "State Nonce not higher than a previous dispute");
//************************************************************************************************
// Open Dispute
stateChannelData[stateChannelOpenData.channelID].channelStatus = STATE_CHANNEL_STATUS_IN_DISPUTE;
DisputeData memory dispute;
dispute.startTime = now;
dispute.initiator = initiator;
dispute.stateContentsHash = stateContentsHash;
dispute.actionContentsHash = 0x0;
dispute.stateNonce = state.contents.nonce;
dispute.openBlock = block.number;
disputeData[stateChannelOpenData.channelID] = dispute;
// Log
emit DisputeInitiatedWithoutAction(stateChannelOpenData.channelID, stateChannelOpenData.participants[0].participantAddress, stateChannelOpenData.participants[1].participantAddress, state);
}
//************************************************************************************************
//** Dispute with action
// if this action doesn't create a finalisable state, the other participant must respond with a new action
// otherwise they can additionally Resolve - Agree and Close Channel
function disputeWithAction(bytes memory packedOpenChannelData, State memory state, Action memory action, State memory proposedNewState) public {
//************************************************************************************************
// Decode data
StateChannelOpenData memory stateChannelOpenData = abi.decode(packedOpenChannelData, (StateChannelOpenData));
IStateMachine stateMachine = IStateMachine(stateChannelOpenData.stateMachineAddress);
//************************************************************************************************
// Validity Checks
// Is the Open Channel Data valid?
require(stateChannelData[stateChannelOpenData.channelID].packedOpenChannelDataHash == keccak256(packedOpenChannelData), "Invalid State Channel Open Data");
// is the State Channel open and not in dispute
require(stateChannelData[stateChannelOpenData.channelID].channelStatus == STATE_CHANNEL_STATUS_OPEN, "State Channel is not open");
// validate state against channel
ffrequire(validateStateContents(state.contents, stateChannelOpenData));
// validate action against state and channel
ffrequire(validateActionContents(action.contents, state.contents, stateChannelOpenData));
// is the action of the correct type
require(action.contents.actionType == ACTION_TYPE_ADVANCE_STATE, "Incorrect Action Type");
// is the state correctly co-signed?
bytes32 stateContentsHash = keccak256(abi.encode(state.contents));
require(validateSignature(stateContentsHash, state.signatures[0], stateChannelOpenData.participants[0].participantAddress, stateChannelOpenData.participants[0].signingAddress), "Participant #0 state signature validation failed");
require(validateSignature(stateContentsHash, state.signatures[1], stateChannelOpenData.participants[1].participantAddress, stateChannelOpenData.participants[1].signingAddress), "Participant #1 state signature validation failed");
// is the state nonce greater than any previous dispute?
require(state.contents.nonce > disputeData[stateChannelOpenData.channelID].stateNonce, "State Nonce not higher than a previous dispute");
// was it initiated by one of the participants?
uint256 initiator;
if ((msg.sender == stateChannelOpenData.participants[0].participantAddress) || (msg.sender == stateChannelOpenData.participants[0].signingAddress)) {
initiator = 0;
} else if ((msg.sender == stateChannelOpenData.participants[1].participantAddress) || (msg.sender == stateChannelOpenData.participants[1].signingAddress)) {
initiator = 1;
} else {
require(false, "Dispute initiated by non-participant");
}
// is the action from the initiator
require(action.contents.participant == initiator, "Action not from dispute initiator");
// is the action correctly signed?
bytes32 actionContentsHash = keccak256(abi.encode(action.contents));
require(validateSignature(actionContentsHash, action.signature, stateChannelOpenData.participants[action.contents.participant].participantAddress, stateChannelOpenData.participants[action.contents.participant].signingAddress), "Action signature validation failed");
// is the new state correctly signed?
bytes32 proposedNewStateContentsHash = keccak256(abi.encode(proposedNewState.contents));
require(validateSignature(proposedNewStateContentsHash, proposedNewState.signatures[action.contents.participant], stateChannelOpenData.participants[action.contents.participant].participantAddress, stateChannelOpenData.participants[action.contents.participant].signingAddress), "Proposed new state signature validation failed");
// advance the state using the state machine
StateContents memory newStateContents;
FFR memory isValid;
(isValid, newStateContents) = advanceState(state.contents, action.contents, stateMachine);
// was the state transition valid?
ffrequire(isValid);
// does the state machine agree with the proposed new state?
require(keccak256(abi.encode()) == proposedNewStateContentsHash, "Proposed State incorrect");
//************************************************************************************************
// Open Dispute
stateChannelData[stateChannelOpenData.channelID].channelStatus = STATE_CHANNEL_STATUS_IN_DISPUTE;
DisputeData memory dispute;
dispute.startTime = now;
dispute.initiator = action.contents.participant;
dispute.stateContentsHash = proposedNewStateContentsHash;
dispute.actionContentsHash = actionContentsHash;
dispute.stateNonce = proposedNewState.contents.nonce;
dispute.openBlock = block.number;
disputeData[stateChannelOpenData.channelID] = dispute;
// Log
emit DisputeInitiatedWithAction(stateChannelOpenData.channelID, stateChannelOpenData.participants[0].participantAddress, stateChannelOpenData.participants[1].participantAddress, state, action, proposedNewState);
}
//************************************************************************************************
//************************************************************************************************
//** Dispute Resolution
//************************************************************************************************
//************************************************************************************************
//************************************************************************************************
//************************************************************************************************
//** Resolve Dispute - Without Action
//************************************************************************************************
//************************************************************************************************
// Confirm the disputed state, provide a new action and state, and re-open the channel
// Can only be called by the counterparty
function resolveDispute_WithAction(bytes memory packedOpenChannelData, State memory state, Action memory action, State memory proposedNewState) public {
//************************************************************************************************
// Decode data
StateChannelOpenData memory stateChannelOpenData = abi.decode(packedOpenChannelData, (StateChannelOpenData));
IStateMachine stateMachine = IStateMachine(stateChannelOpenData.stateMachineAddress);
DisputeData memory dispute = disputeData[stateChannelOpenData.channelID];
//************************************************************************************************
// Validity Checks
// Is the Open Channel Data valid?
require(stateChannelData[stateChannelOpenData.channelID].packedOpenChannelDataHash == keccak256(packedOpenChannelData), "Invalid State Channel Open Data");
// is the State Channel open and in dispute
require(stateChannelData[stateChannelOpenData.channelID].channelStatus == STATE_CHANNEL_STATUS_IN_DISPUTE, "State Channel is not in dispute");
// is the state the one that's in dispute?
require(keccak256(abi.encode(state.contents)) == dispute.stateContentsHash, "Incorrect State");
// validate the action against state and channel
ffrequire(validateActionContents(action.contents, state.contents, stateChannelOpenData));
// is the action of the correct type
require(action.contents.actionType == ACTION_TYPE_ADVANCE_STATE, "Incorrect Action Type");
// is the resolution being made by the counterparty
require(action.contents.participant != dispute.initiator, "Action not from the counterparty");
// is the action correctly co-signed?
require(validateSignature(keccak256(abi.encode(action.contents)), action.signature, stateChannelOpenData.participants[action.contents.participant].participantAddress, stateChannelOpenData.participants[action.contents.participant].signingAddress), "Action signature validation failed");
// is the new state correctly signed?
bytes32 proposedNewStateContentsHash = keccak256(abi.encode(proposedNewState.contents));
require(validateSignature(proposedNewStateContentsHash, proposedNewState.signatures[action.contents.participant], stateChannelOpenData.participants[action.contents.participant].participantAddress, stateChannelOpenData.participants[action.contents.participant].signingAddress), "Proposed new state signature validation failed");
// advance the state using the state machine
StateContents memory newStateContents;
FFR memory isValid;
(isValid, newStateContents) = advanceState(state.contents, action.contents, stateMachine);
// was the state transition valid?
ffrequire(isValid);
// does the state machine agree with the proposed new state?
require(keccak256(abi.encode()) == proposedNewStateContentsHash, "Proposed State incorrect");
//************************************************************************************************
// Resolution Successful - Re-open the channel
stateChannelData[stateChannelOpenData.channelID].channelStatus = STATE_CHANNEL_STATUS_OPEN;
disputeData[stateChannelOpenData.channelID].resolutionBlock = block.number;
// log
emit DisputeResolvedWithAction(stateChannelOpenData.channelID, stateChannelOpenData.participants[0].participantAddress, stateChannelOpenData.participants[1].participantAddress, action, proposedNewState);
}
//************************************************************************************************
//** Resolve Disupute - Agree and close channel
// Accept the state that was proposed in the dispute and close the channel now
// Requires the state to be finalisable
// Can only be called with an action from counterparty
function resolveDispute_AgreeAndCloseChannel(bytes memory packedOpenChannelData, State memory state, Action memory action) public {
//************************************************************************************************
// Decode data
StateChannelOpenData memory stateChannelOpenData = abi.decode(packedOpenChannelData, (StateChannelOpenData));
IStateMachine stateMachine = IStateMachine(stateChannelOpenData.stateMachineAddress);
DisputeData memory dispute = disputeData[stateChannelOpenData.channelID];
//************************************************************************************************
// Validity Checks
// Is the Open Channel Data valid?
require(stateChannelData[stateChannelOpenData.channelID].packedOpenChannelDataHash == keccak256(packedOpenChannelData), "Invalid State Channel Open Data");
// is the State Channel open and in dispute
require(stateChannelData[stateChannelOpenData.channelID].channelStatus == STATE_CHANNEL_STATUS_IN_DISPUTE, "State Channel is not in dispute");
// is the state the one that's in dispute?
require(keccak256(abi.encode(state.contents)) == dispute.stateContentsHash, "Incorrect State");
// does the state machine consider this a valid point to close a channel
require(stateMachine.isStateFinalisable(state.contents.packedStateMachineState), "State is not finalisable");
// validate action against state and channel
ffrequire(validateActionContents(action.contents, state.contents, stateChannelOpenData));
// is the resolution being made by the counterparty
require(action.contents.participant != dispute.initiator, "Action not from the counterparty");
// is the action a action?
require(action.contents.actionType == ACTION_TYPE_CLOSE_CHANNEL, "Incorrect Action Type for Action");
// is the action correctly signed?
require(validateSignature(keccak256(abi.encode(action.contents)), action.signature, stateChannelOpenData.participants[action.contents.participant].participantAddress, stateChannelOpenData.participants[action.contents.participant].signingAddress), "Action signature validation failed");
//************************************************************************************************
// Resolution Successful - Close the Channel
DistributeFundsAndCloseChannel(packedOpenChannelData, state, PENALISE_NONE, CLOSE_CHANNEL_ORDERLY);
}
//************************************************************************************************
//** Resolve Disupute - Timeout
// The counterparty has not successfully resolved the dispute - so they are penalised and the channel is closed
// anyone can call this function
function resolveDispute_Timeout(bytes memory packedOpenChannelData, State memory state) public {
//************************************************************************************************
// Decode data
StateChannelOpenData memory stateChannelOpenData = abi.decode(packedOpenChannelData, (StateChannelOpenData));
DisputeData memory dispute = disputeData[stateChannelOpenData.channelID];
//************************************************************************************************
// Validity Checks
// Is the Open Channel Data valid?
require(stateChannelData[stateChannelOpenData.channelID].packedOpenChannelDataHash == keccak256(packedOpenChannelData), "Invalid State Channel Open Data");
// is the State Channel open and in dispute
require(stateChannelData[stateChannelOpenData.channelID].channelStatus == STATE_CHANNEL_STATUS_IN_DISPUTE, "State Channel is not in dispute");
// is the state the one that's in dispute?
require(keccak256(abi.encode(state.contents)) == dispute.stateContentsHash, "Incorrect State");
// has the correct amount of time elapsed?
require(validateTimeout(dispute), "Too early to claim timeout");
//************************************************************************************************
// Resolution Successful - Close the Channel
DistributeFundsAndCloseChannel(packedOpenChannelData, state, (dispute.initiator == 0) ? PENALISE_P1 : PENALISE_P0, (dispute.initiator == 0) ? CLOSE_CHANNEL_P1_TIMED_OUT : CLOSE_CHANNEL_P0_TIMED_OUT);
}
//************************************************************************************************
//** Resolve Disupute - Challenge with later state
// If the dispute initiator has signed a later state than the one they are disputing, this is a protocol violation, and they are penalised
// anyone can call this function
function resolveDispute_ChallengeWithLaterState(bytes memory packedOpenChannelData, State memory state) public {
//************************************************************************************************
// Decode data
StateChannelOpenData memory stateChannelOpenData = abi.decode(packedOpenChannelData, (StateChannelOpenData));
DisputeData memory dispute = disputeData[stateChannelOpenData.channelID];
//************************************************************************************************
// Validity Checks
// Is the Open Channel Data valid?
require(stateChannelData[stateChannelOpenData.channelID].packedOpenChannelDataHash == keccak256(packedOpenChannelData), "Invalid State Channel Open Data");
// is the State Channel open and in dispute
require(stateChannelData[stateChannelOpenData.channelID].channelStatus == STATE_CHANNEL_STATUS_IN_DISPUTE, "State Channel is not in dispute");
// validate state against channel
ffrequire(validateStateContents(state.contents, stateChannelOpenData));
// is this a later state?
require(state.contents.nonce > dispute.stateNonce, "Nonce is not later than dispute");
// has this state been co-signed
bytes32 stateContentsHash = keccak256(abi.encode(state.contents));
require(validateSignature(stateContentsHash, state.signatures[0], stateChannelOpenData.participants[0].participantAddress, stateChannelOpenData.participants[0].signingAddress), "Participant #0 state signature validation failed");
require(validateSignature(stateContentsHash, state.signatures[1], stateChannelOpenData.participants[1].participantAddress, stateChannelOpenData.participants[1].signingAddress), "Participant #1 state signature validation failed");
//************************************************************************************************
// Resolution Successful - Close the Channel
DistributeFundsAndCloseChannel(packedOpenChannelData, state, (dispute.initiator == 0) ? PENALISE_P0 : PENALISE_P1, (dispute.initiator == 0) ? CLOSE_CHANNEL_CHALLENGED_WITH_LATER_STATE_P0_PENALTY : CLOSE_CHANNEL_CHALLENGED_WITH_LATER_STATE_P1_PENALTY);
}
//************************************************************************************************
//** Resolve Disupute - Challenge with different action
// If the dispute initiator has signed a different action on the state they are disputing, this is a protocol violation, and they are penalised
// anyone can call this function
function resolveDispute_ChallengeWithDifferentAction(bytes memory packedOpenChannelData, State memory state, Action memory action) public {
//************************************************************************************************
// Decode data
StateChannelOpenData memory stateChannelOpenData = abi.decode(packedOpenChannelData, (StateChannelOpenData));
DisputeData memory dispute = disputeData[stateChannelOpenData.channelID];
IStateMachine stateMachine = IStateMachine(stateChannelOpenData.stateMachineAddress);
//************************************************************************************************
// Validity Checks
// Is the Open Channel Data valid?
require(stateChannelData[stateChannelOpenData.channelID].packedOpenChannelDataHash == keccak256(packedOpenChannelData), "Invalid State Channel Open Data");
// is the State Channel open and in dispute
require(stateChannelData[stateChannelOpenData.channelID].channelStatus == STATE_CHANNEL_STATUS_IN_DISPUTE, "State Channel is not in dispute");
// is the state the one that's in dispute?
require(keccak256(abi.encode(state.contents)) == dispute.stateContentsHash, "Incorrect State");
// validate action against state and channel
ffrequire(validateActionContents(action.contents, state.contents, stateChannelOpenData));
// is the action of the correct type?
require(action.contents.actionType == ACTION_TYPE_ADVANCE_STATE, "Incorrect Action Type");
// is the action participant the dispute initiator?
require(action.contents.participant != dispute.initiator, "Action not from the initiator");
// is the action correctly co-signed?
require(validateSignature(keccak256(abi.encode(action.contents)), action.signature, stateChannelOpenData.participants[action.contents.participant].participantAddress, stateChannelOpenData.participants[action.contents.participant].signingAddress), "Action signature validation failed");
// advance the state using the state machine in order to check that the action is valid
StateContents memory newStateContents;
FFR memory isValid;
(isValid, newStateContents) = advanceState(state.contents, action.contents, stateMachine);
// was the state transition valid?
ffrequire(isValid);
// is the action different to the one used to open the dispute
require(keccak256(abi.encode(action.contents)) != dispute.actionContentsHash, "Action not different");
//************************************************************************************************
// Resolution Successful - Close the Channel
DistributeFundsAndCloseChannel(packedOpenChannelData, state, (dispute.initiator == 0) ? PENALISE_P0 : PENALISE_P1, (dispute.initiator == 0) ? CLOSE_CHANNEL_CHALLENGED_WITH_DIFFERENT_ACTION_P0_PENALTY : CLOSE_CHANNEL_CHALLENGED_WITH_DIFFERENT_ACTION_P1_PENALTY);
}
}