diff --git a/docs/core/state_snapshot.md b/docs/core/state_snapshot.md new file mode 100644 index 00000000..bff5bc11 --- /dev/null +++ b/docs/core/state_snapshot.md @@ -0,0 +1,59 @@ +# Snapshot and Revert in Ethermint + +EVM uses state-reverting exceptions to handle errors. Such an exception will undo all changes made to the state in the current call (and all its sub-calls), and the caller could handle the error and don't propagate. We need to implement the `Snapshot` and `RevertToSnapshot` apis in `StateDB` interfaces to support this feature. + +[go-ethereum implementation](https://github.com/ethereum/go-ethereum/blob/master/core/state/journal.go#L39) manages transient states in memory, and uses a list of journal logs to record all the state modification operations done so far, snapshot is an index in the log list, and to revert to a snapshot it just undo the journal logs after the snapshot index in reversed order. + +Ethermint uses cosmos-sdk's storage api to manage states, fortunately the storage api supports creating cached overlays, it works like this: + +```golang +// create a cached overlay storage on top of ctx storage. +overlayCtx, commit := ctx.CacheContext() +// Modify states using the overlayed storage +err := doCall(overlayCtx) +if err != nil { + return err +} +// commit will write the dirty states into the underlying storage +commit() + +// Now, just drop the overlayCtx and keep using ctx +``` + +And it can be used in a nested way, like this: + +```golang +overlayCtx1, commit1 := ctx.CacheContext() +doCall1(overlayCtx1) +{ + overlayCtx2, commit2 := overlayCtx1.CacheContext() + doCall2(overlayCtx2) + commit2() +} +commit1() +``` + +With this feature, we can use a stake of overlayed contexts to implement nested `Snapshot` and `RevertToSnapshot` calls. + +```golang +type cachedContext struct { + ctx sdk.Context + commit func() +} +var contextStack []cachedContext +func Snapshot() int { + ctx, commit := contextStack.Top().CacheContext() + contextStack.Push(cachedContext{ctx, commit}) + return len(contextStack) - 1 +} +func RevertToSnapshot(int snapshot) { + contextStack = contextStack[:snapshot] +} +func Commit() { + for i := len(contextStack) - 1; i >= 0; i-- { + contextStack[i].commit() + } + contextStack = {} +} +``` +