Channel checkpointing like this is quite elegant, as it can be used to accomplish the same thing as respond
, respondWithAlternative
, and refute
. IE. you could have an even smaller ForceMove protocol than the current one, with just forceMove
, checkpoint
and conclude
.
On top of that, consider the following channel with participants [Alice, Eve]. Suppose they each hold states 99 and 100
- Eve maliciously calls
forceMove(s10)
with an old state. - Alice calls
refute(s99)
in transaction tx1 - Eve sees tx2 and front-runs a transaction
tx2
containingrespond(s11)
- tx1 reverts, since the channel is now open
- Eve then repeats this with turn 11,12,…
In the current ForceMove protocol, this lets Eve block the channel for a duration roughly equal to the latest turn number times the challenge expiration duration. With checkpoint
, Alice can stop this attack by calling checkpoint([s99, s100])
.
It might also be worth modifying the requirements for refute
to increment Eve’s turnNumberRecord, and then clear the challenge if it exists. This would mean that tx1 would not revert in 4, and Alice would not have to subsequently call checkpoint
in a second transaction to block the above attack.
There is still benefit to having respond
and refute
:
- when calling
respond
, you only have to check one valid transition and signature, so it’s more gas-efficient than callingcheckpoint
- when calling
refute
, the adjudicator has the option to penalize the challenger
The checkpoint
method could be written to detect these two scenarios and react accordingly, but that would complicate the implementation and would not be as gas-efficient as the bespoke functions.