zeko-labs / zeko

Zeko: zk-Rollup for Mina, a succinct blockchain
https://zeko.io
Apache License 2.0
17 stars 2 forks source link

Delay withdrawals #158

Open L-as opened 1 month ago

L-as commented 1 month ago

Important for a variety of reasons, all of them come down to rolling back disaster events (hacks/DA failure/etc.).

L-as commented 2 weeks ago

We run into an issue that's very similar to what's in #176 .

We need to record when a withdrawal happened, such that rather than cancelling it after X time, we can process it.

If we do "UTXOs", i.e. deletable account, this is again much easier. If we for each withdrawal create a new account, we can record in the account when the withdrawal was committed.

Even without deletable accounts, we can do this, but it's harder with the current design.

L-as commented 2 weeks ago

https://github.com/zeko-labs/zeko/issues/176#issuecomment-2189891387

We can do the same thing in the other direction and store in the merkle tree leafs the recipient, the amount, and the time. Since the time is the same for a group of withdrawals committed from L2 to L1 at the same time, we could share the time between all of them.

L-as commented 2 weeks ago

The core motivation is that we want the ability to pause withdrawals. We'd do this with a dedicated field in the outer account that is true when passed. Processing the withdrawal would set a precondition on this state field, such that it would fail (and waste the fees unfortunately) if it gets paused. Since we want the ability to fix bugs in the circuit if this happens, we want to undo the withdrawal entirely. We could make the field a version field instead, such that if the version mismatches, we move the funds back to the outer account.

L-as commented 1 week ago

We wish to have delayed withdrawals, such that after a withdrawal posted to the inner account is registered on the outer account by way of commit, there should be some minimum delay before it can be processed, and if by that point the emergency pause (#181) has been enabled, the withdrawal should be entirely cancelled.

Thus, if a hack occurs where someone does an illegal transition of the ledger state to something where they have more MINA than they should, we should be able to do an emergency pause and remedy the situation.


Consider how this information is transported outside. The action state of the inner account is transferred as is to a field of the outer account.

We could use an approach similar to what we did with deposits (#176): We could mark when an action was submitted by having the sequencer submit a special action that contains the slot range for the commit account update. Then that slot range would be implicitly associated with each withdrawal before it until the last such action.

A special field in the outer account could indicate whether it's paused, and if so, we could disallow the withdrawal. This alone, in fact, is enough to completely undo the withdrawal, since if we force a change of the inner ledger, we would have a new inner account action state, which would be synchronised to the outer account.

That is, since withdrawals are in some sense stateless, we needn't do anything more.

However, there is a bigger problem: As with deposits, say the sequencer is forced to set a slot range that is at most X slots big, that information is included in the action then, and affects the inner ledger hash. If that commit transaction then fails (due to rollback, censure, etc.), we are in a situation where we might not be able to resubmit a new commit transaction with the original slot range precondition.

That would mean that all of the txn snark work we've done assuming this will go through will go to waste, since that slot range precondition would have been part of the inner ledger hash.

Users could also possibly match on the action state that contains this slot range precondition. Though this is easy to deny by having the sequencer reject such commands in the first place. But this does not solve the first, bigger, problem.

Fundamentally, this approach can not work. It can not work if we are to post the slot range precondition to the inner ledger before we are entirely sure that it will pass.

Consider this: This information is never used on the inner ledger, thus we shouldn't need to put it there. We originally considered putting it there as an optimization.

There is another place we can store this information: The outer action state! We can extend the Committed action as described in #176 such that it contains the inner action state at the time of the commit.

Then, with withdrawing, we can find a slot range associated with the withdrawal by matching on the action state and finding a Committed action where the associated inner action state contains our withdrawal. We can not find one where the slot range is earlier than it should be, meaning that if you forget to process your withdrawal, you need only know some recent Committed action rather than going back to the original one for your withdrawal.

We could solve #177 at the same time by removing the inner action state from the field, but this would prevent emergency changes to the inner ledger from working, since even if you changed the inner action state, that would not be reflected in the historical actions, which are unundoable.

With this system, however, even if there is some Committed action with some inner action state that is no longer valid, there is no way of using it, since it would not be a prefix of the inner action state recorded in the field of the outer account.