docs: state revert logic (#421)
Closes #356 Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
parent
4f61ecb52e
commit
8bcab7be27
59
docs/core/state_snapshot.md
Normal file
59
docs/core/state_snapshot.md
Normal file
@ -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 = {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in New Issue
Block a user