This repository has been archived by the owner on Apr 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 566
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Closes #356 Co-authored-by: Federico Kunze Küllmer <[email protected]>
- Loading branch information
Showing
1 changed file
with
59 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = {} | ||
} | ||
``` | ||
|