Skip to content

Commit

Permalink
messy, working first version that allows for precompile reversion
Browse files Browse the repository at this point in the history
  • Loading branch information
Unique-Divine committed Oct 26, 2024
1 parent 295a2d9 commit 80dd2d7
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 25 deletions.
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ require (
replace (
cosmossdk.io/api => cosmossdk.io/api v0.3.1

github.com/CosmWasm/wasmd => github.com/NibiruChain/wasmd v0.44.0-nibiru
github.com/cosmos/cosmos-sdk => github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru

github.com/cosmos/iavl => github.com/cosmos/iavl v0.20.0

github.com/ethereum/go-ethereum => github.com/NibiruChain/go-ethereum v1.10.27-nibiru
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/ChainSafe/go-schnorrkel v1.0.0 h1:3aDA67lAykLaG1y3AOjs88dMxC88PgUuHRrLeDnvGIM=
github.com/ChainSafe/go-schnorrkel v1.0.0/go.mod h1:dpzHYVxLZcp8pjlV+O+UR8K0Hp/z7vcchBSbMBEhCw4=
github.com/CosmWasm/wasmd v0.44.0 h1:2sbcoCAvfjCs1O0SWt53xULKjkV06dbSFthEViIC6Zg=
github.com/CosmWasm/wasmd v0.44.0/go.mod h1:tDyYN050qUcdd7LOxGeo2e185sEShyO3nJGl2Cf59+k=
github.com/CosmWasm/wasmvm v1.5.5 h1:XlZI3xO5iUhiBqMiyzsrWEfUtk5gcBMNYIdHnsTB+NI=
github.com/CosmWasm/wasmvm v1.5.5/go.mod h1:Q0bSEtlktzh7W2hhEaifrFp1Erx11ckQZmjq8FLCyys=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
Expand All @@ -237,8 +235,12 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/NibiruChain/collections v0.5.0 h1:33pXpVTe1PK/tfdZlAJF1JF7AdzGNARG+iL9G/z3X7k=
github.com/NibiruChain/collections v0.5.0/go.mod h1:43L6yjuF0BMre/mw4gqn/kUOZz1c2Y3huZ/RQfBFrOQ=
github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru h1:PgFpxDe+7+OzWHs4zXlml5j2i9sGq2Zpd3ndYQG29/0=
github.com/NibiruChain/cosmos-sdk v0.47.11-nibiru/go.mod h1:ADjORYzUQqQv/FxDi0H0K5gW/rAk1CiDR3ZKsExfJV0=
github.com/NibiruChain/go-ethereum v1.10.27-nibiru h1:o6lRFt57izoYwzN5cG8tnnBtJcaO3X7MjjN7PGGNCFg=
github.com/NibiruChain/go-ethereum v1.10.27-nibiru/go.mod h1:kvvL3nDceUcB+1qGUBAsVf5dW23RBR77fqxgx2PGNrQ=
github.com/NibiruChain/wasmd v0.44.0-nibiru h1:b+stNdbMFsl0+o4KedXyF83qRnEpB/jCiTGZZgv2h2U=
github.com/NibiruChain/wasmd v0.44.0-nibiru/go.mod h1:inrbdsixQ0Kdu4mFUg1u7fn3XPOEkzqieGv0H/gR0ck=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
Expand Down Expand Up @@ -426,8 +428,6 @@ github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAK
github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/cosmos-sdk v0.47.11 h1:0Qx7eORw0RJqPv+mvDuU8NQ1LV3nJJKJnPoYblWHolc=
github.com/cosmos/cosmos-sdk v0.47.11/go.mod h1:ADjORYzUQqQv/FxDi0H0K5gW/rAk1CiDR3ZKsExfJV0=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
Expand Down
9 changes: 6 additions & 3 deletions x/evm/precompile/precompile.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ type OnRunStartResult struct {
// SnapshotBeforeRun captures the state before precompile execution to enable
// proper state reversal if the call fails or if [statedb.JournalChange]
// is reverted in general.
SnapshotBeforeRun statedb.PrecompileSnapshotBeforeRun
SnapshotBeforeRun statedb.PrecompileCalled
}

// OnRunStart prepares the execution environment for a precompiled contract call.
Expand Down Expand Up @@ -194,6 +194,7 @@ func OnRunStart(
return
}
cacheCtx, snapshot := stateDB.CacheCtxForPrecompile(contract.Address())
stateDB.SavePrecompileSnapshotToJournal(contract.Address(), snapshot)
if err = stateDB.CommitCacheCtx(); err != nil {
return res, fmt.Errorf("error committing dirty journal entries: %w", err)
}
Expand Down Expand Up @@ -221,10 +222,12 @@ func OnRunStart(
// - Multiple precompiles are called within a single transaction
func OnRunEnd(
stateDB *statedb.StateDB,
snapshot statedb.PrecompileSnapshotBeforeRun,
snapshot statedb.PrecompileCalled,
precompileAddr gethcommon.Address,
) error {
return stateDB.SavePrecompileSnapshotToJournal(precompileAddr, snapshot)
// TODO: UD-DEBUG: Not needed because it's been added to start.
// return stateDB.SavePrecompileSnapshotToJournal(precompileAddr, snapshot)
return nil
}

var precompileMethodIsTxMap map[PrecompileMethod]bool = map[PrecompileMethod]bool{
Expand Down
62 changes: 62 additions & 0 deletions x/evm/precompile/test/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package test

import (
"encoding/json"
"math/big"
"os"
"os/exec"
"path"
"strings"

serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasm "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/ethereum/go-ethereum/core/vm"
Expand Down Expand Up @@ -315,3 +318,62 @@ func IncrementWasmCounterWithExecuteMulti(
s.Require().NotEmpty(ethTxResp.Ret)
return evmObj
}

func IncrementWasmCounterWithExecuteMultiViaVMCall(
s *suite.Suite,
deps *evmtest.TestDeps,
wasmContract sdk.AccAddress,
times uint,
finalizeTx bool,
evmObj *vm.EVM,
) error {
msgArgsBz := []byte(`
{
"increment": {}
}
`)

// Parse funds argument.
var funds []precompile.WasmBankCoin // blank funds
fundsJson, err := json.Marshal(funds)
s.NoErrorf(err, "fundsJson: %s", fundsJson)
err = json.Unmarshal(fundsJson, &funds)
s.Require().NoError(err, "fundsJson %s, funds %s", fundsJson, funds)

// The "times" arg determines the number of messages in the executeMsgs slice
executeMsgs := []struct {
ContractAddr string `json:"contractAddr"`
MsgArgs []byte `json:"msgArgs"`
Funds []precompile.WasmBankCoin `json:"funds"`
}{
{wasmContract.String(), msgArgsBz, funds},
}
if times == 0 {
executeMsgs = executeMsgs[:0] // force empty
} else {
for i := uint(1); i < times; i++ {
executeMsgs = append(executeMsgs, executeMsgs[0])
}
}
s.Require().Len(executeMsgs, int(times)) // sanity check assertion

callArgs := []any{
executeMsgs,
}
input, err := embeds.SmartContract_Wasm.ABI.Pack(
string(precompile.WasmMethod_executeMulti),
callArgs...,
)
s.Require().NoError(err)

contract := precompile.PrecompileAddr_Wasm
leftoverGas := serverconfig.DefaultEthCallGasLimit
_, _, err = evmObj.Call(
vm.AccountRef(deps.Sender.EthAddr),
contract,
input,
leftoverGas,
big.NewInt(0),
)
return err
}
22 changes: 16 additions & 6 deletions x/evm/statedb/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,13 @@ func (ch accessListAddSlotChange) Dirtied() *common.Address {
// ------------------------------------------------------
// PrecompileSnapshotBeforeRun

// PrecompileSnapshotBeforeRun: Precompiles can alter persistent storage of other
// PrecompileCalled: Precompiles can alter persistent storage of other
// modules. These changes to persistent storage are not reverted by a `Revert` of
// [JournalChange] by default, as it generally manages only changes to accounts
// and Bank balances for ether (NIBI).
//
// As a workaround to make state changes from precompiles reversible, we store
// [PrecompileSnapshotBeforeRun] snapshots that sync and record the prior state
// [PrecompileCalled] snapshots that sync and record the prior state
// of the other modules, allowing precompile calls to truly be reverted.
//
// As a simple example, suppose that a transaction calls a precompile.
Expand All @@ -356,23 +356,33 @@ func (ch accessListAddSlotChange) Dirtied() *common.Address {
// state to a in-memory snapshot recorded on the StateDB journal.
// 3. This could cause a problem where changes to the rest of the blockchain state
// are still in effect following the reversion in the EVM state DB.
type PrecompileSnapshotBeforeRun struct {
type PrecompileCalled struct {
MultiStore store.CacheMultiStore
Events sdk.Events
Precompile common.Address
}

var _ JournalChange = PrecompileSnapshotBeforeRun{}
var _ JournalChange = PrecompileCalled{}

func (ch PrecompileSnapshotBeforeRun) Revert(s *StateDB) {
func (ch PrecompileCalled) Revert(s *StateDB) {
// TEMP: trying something
// If the wasm state is not in the cacheCtx,
// s.CommitCacheCtx()

// Old Code
s.cacheCtx = s.cacheCtx.WithMultiStore(ch.MultiStore)
// Rewrite the `writeCacheCtxFn` using the same logic as sdk.Context.CacheCtx
s.writeToCommitCtxFromCacheCtx = func() {
s.ctx.EventManager().EmitEvents(ch.Events)
// TODO: UD-DEBUG: Overwriting events might fix an issue with
// appending too many
// s.ctx.WithEventManager(
// sdk.NewEventManager().EmitEvents(ch.Events),
// )
ch.MultiStore.Write()
}
}

func (ch PrecompileSnapshotBeforeRun) Dirtied() *common.Address {
func (ch PrecompileCalled) Dirtied() *common.Address {
return &ch.Precompile
}
70 changes: 64 additions & 6 deletions x/evm/statedb/journal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import (
"strings"
"testing"

"github.com/MakeNowJust/heredoc/v2"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/core/vm"

serverconfig "github.com/NibiruChain/nibiru/v2/app/server/config"
"github.com/NibiruChain/nibiru/v2/x/common"
"github.com/NibiruChain/nibiru/v2/x/common/testutil/testapp"
"github.com/NibiruChain/nibiru/v2/x/evm"
"github.com/NibiruChain/nibiru/v2/x/evm/embeds"
Expand Down Expand Up @@ -147,23 +149,79 @@ func (s *Suite) TestComplexJournalChanges() {
stateDB, ok = evmObj.StateDB.(*statedb.StateDB)
s.Require().True(ok, "error retrieving StateDB from the EVM")

s.T().Log("Expect exactly 1 dirty journal entry for the precompile snapshot")
if stateDB.DirtiesCount() != 1 {
s.T().Log("Expect exactly 0 dirty journal entry for the precompile snapshot")
if stateDB.DirtiesCount() != 0 {
debugDirtiesCountMismatch(stateDB, s.T())
s.FailNow("expected 1 dirty journal changes")
s.FailNow("expected 0 dirty journal changes")
}

s.T().Log("Expect no change since the StateDB has not been committed")
test.AssertWasmCounterState(
&s.Suite, deps, wasmContract, 7, // 7 = 7 + 0
)

s.T().Log("Expect change after the StateDB gets committed")
err = stateDB.Commit()
s.Require().NoError(err)
s.T().Log("Expect change to persist on the StateDB cacheCtx")
cacheCtx := stateDB.GetCacheContext()
s.NotNil(cacheCtx)
deps.Ctx = *cacheCtx
test.AssertWasmCounterState(
&s.Suite, deps, wasmContract, 12, // 12 = 7 + 5
)
// NOTE: that the [StateDB.Commit] fn has not been called yet. We're still
// mid-transaction.

s.T().Log("EVM revert operation should bring about the old state")
err = test.IncrementWasmCounterWithExecuteMultiViaVMCall(
&s.Suite, &deps, wasmContract, 50, commitEvmTx, evmObj,
)
stateDBPtr := evmObj.StateDB.(*statedb.StateDB)
s.Require().Equal(stateDB, stateDBPtr)
s.Require().NoError(err)
s.T().Log(heredoc.Doc(`At this point, 2 precompile calls have succeeded.
One that increments the counter to 7 + 5, and another for +50.
The StateDB has not been committed. We expect to be able to revert to both
snapshots and see the prior states.`))
cacheCtx = stateDB.GetCacheContext()
deps.Ctx = *cacheCtx
test.AssertWasmCounterState(
&s.Suite, deps, wasmContract, 7+5+50,
)

errFn := common.TryCatch(func() {
// There were only two EVM calls.
// Thus, there are only 2 snapshots: 0 and 1.
// We should not be able to revert to a third one.
stateDB.RevertToSnapshot(2)
})
s.Require().ErrorContains(errFn(), "revision id 2 cannot be reverted")

stateDB.RevertToSnapshot(1)
cacheCtx = stateDB.GetCacheContext()
s.NotNil(cacheCtx)
deps.Ctx = *cacheCtx
test.AssertWasmCounterState(
&s.Suite, deps, wasmContract, 7+5,
)

stateDB.RevertToSnapshot(0)
cacheCtx = stateDB.GetCacheContext()
s.NotNil(cacheCtx)
deps.Ctx = *cacheCtx
test.AssertWasmCounterState(
&s.Suite, deps, wasmContract, 7, // state before precompile called
)

err = stateDB.Commit()
deps.Ctx = stateDB.GetContext()
test.AssertWasmCounterState(
&s.Suite, deps, wasmContract, 7, // state before precompile called
)
})

s.Run("too many precompile calls in one tx will fail", func() {
// currently
// evmObj

})
}

Expand Down
26 changes: 20 additions & 6 deletions x/evm/statedb/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"math/big"
"sort"

store "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
gethcore "github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -91,6 +92,14 @@ func (s *StateDB) GetContext() sdk.Context {
return s.ctx
}

// GetCacheContext: Getter for testing purposes.
func (s *StateDB) GetCacheContext() *sdk.Context {
if s.writeToCommitCtxFromCacheCtx == nil {
return nil
}
return &s.cacheCtx
}

// AddLog adds a log, called by evm.
func (s *StateDB) AddLog(log *gethcore.Log) {
s.Journal.append(addLogChange{})
Expand Down Expand Up @@ -463,6 +472,9 @@ func (s *StateDB) Snapshot() int {

// RevertToSnapshot reverts all state changes made since the given revision.
func (s *StateDB) RevertToSnapshot(revid int) {
fmt.Printf("len(s.validRevisions): %d\n", len(s.validRevisions))
fmt.Printf("s.validRevisions: %v\n", s.validRevisions)

// Find the snapshot in the stack of valid snapshots.
idx := sort.Search(len(s.validRevisions), func(i int) bool {
return s.validRevisions[i].id >= revid
Expand Down Expand Up @@ -515,6 +527,7 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error {
continue
}
if obj.IsPrecompile {
// TODO: UD-DEBUG: Assume clean to pretend for tests
s.Journal.dirties[addr] = 0
continue
} else if obj.Suicided {
Expand Down Expand Up @@ -543,6 +556,7 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error {
obj.OriginStorage[key] = dirtyVal
}
}
// TODO: UD-DEBUG: Assume clean to pretend for tests
// Reset the dirty count to 0 because all state changes for this dirtied
// address in the journal have been committed.
s.Journal.dirties[addr] = 0
Expand All @@ -551,29 +565,29 @@ func (s *StateDB) commitCtx(ctx sdk.Context) error {
}

func (s *StateDB) CacheCtxForPrecompile(precompileAddr common.Address) (
sdk.Context, PrecompileSnapshotBeforeRun,
sdk.Context, PrecompileCalled,
) {
if s.writeToCommitCtxFromCacheCtx == nil {
s.cacheCtx, s.writeToCommitCtxFromCacheCtx = s.ctx.CacheContext()
}
return s.cacheCtx, PrecompileSnapshotBeforeRun{
MultiStore: s.cacheCtx.MultiStore().CacheMultiStore(),
return s.cacheCtx, PrecompileCalled{
MultiStore: s.cacheCtx.MultiStore().(store.CacheMultiStore).Copy(),
Events: s.cacheCtx.EventManager().Events(),
Precompile: precompileAddr,
}
}

// SavePrecompileSnapshotToJournal adds a snapshot of the commit multistore
// ([PrecompileSnapshotBeforeRun]) to the [StateDB] journal at the end of
// ([PrecompileCalled]) to the [StateDB] journal at the end of
// successful invocation of a precompiled contract. This is necessary to revert
// intermediate states where an EVM contract augments the multistore with a
// precompile and an inconsistency occurs between the EVM module and other
// modules.
//
// See [PrecompileSnapshotBeforeRun] for more info.
// See [PrecompileCalled] for more info.
func (s *StateDB) SavePrecompileSnapshotToJournal(
precompileAddr common.Address,
snapshot PrecompileSnapshotBeforeRun,
snapshot PrecompileCalled,
) error {
obj := s.getOrNewStateObject(precompileAddr)
obj.db.Journal.append(snapshot)
Expand Down

0 comments on commit 80dd2d7

Please sign in to comment.