Skip to content

kaiax/gasless: Fix restore env upon revert #305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ executors:
auth:
username: $DOCKER_LOGIN
password: $DOCKER_PASSWORD
test-tests-executor: # this executor is for test-tests job
working_directory: ~/go/src/github.com/kaiachain/kaia
resource_class: medium+
docker:
- image: kaiachain/build_base:go1.23-solc0.8.13-ubuntu-22.04
auth:
username: $DOCKER_LOGIN
password: $DOCKER_PASSWORD
test-others-executor: # this executor is for test-others job
working_directory: /go/src/github.com/kaiachain/kaia
resource_class: xlarge
Expand Down Expand Up @@ -396,7 +404,7 @@ jobs:
no_output_timeout: 30m
command: make test-node
test-tests:
executor: test-executor
executor: test-tests-executor
steps:
- checkout
- run:
Expand Down
110 changes: 65 additions & 45 deletions blockchain/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -900,82 +900,102 @@ func (s *StateDB) ForEachStorage(addr common.Address, cb func(key, value common.
// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
func (s *StateDB) Copy() *StateDB {
state := &StateDB{}
copyStateDB(state, s)
return state
}

func (s *StateDB) Set(src *StateDB) {
copyStateDB(s, src)
}

func copyStateDB(dst, src *StateDB) {
// Copy all the basic fields, initialize the memory ones
state := &StateDB{
db: s.db,
trie: s.db.CopyTrie(s.trie),
stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
stateObjectsDirtyStorage: make(map[common.Address]struct{}, len(s.stateObjectsDirtyStorage)),
refund: s.refund,
logs: make(map[common.Hash][]*types.Log, len(s.logs)),
logSize: s.logSize,
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
}
dst.db = src.db
dst.trie = src.db.CopyTrie(src.trie)
dst.trieOpts = src.trieOpts

dst.stateObjects = make(map[common.Address]*stateObject, len(src.stateObjects))
dst.stateObjectsDirty = make(map[common.Address]struct{}, len(src.stateObjectsDirty))
dst.stateObjectsDirtyStorage = make(map[common.Address]struct{}, len(src.stateObjectsDirtyStorage))

dst.dbErr = src.dbErr
dst.refund = src.refund

dst.thash = src.thash
dst.bhash = src.bhash
dst.txIndex = src.txIndex
dst.logs = make(map[common.Hash][]*types.Log, len(src.logs))
dst.logSize = src.logSize

dst.preimages = make(map[common.Hash][]byte, len(src.preimages))

// Do we need to copy the access list? In practice: No. At the start of a
// transaction, the access list is empty. In practice, we only ever copy state
// _between_ transactions/blocks, never in the middle of a transaction.
// However, it doesn't cost us much to copy an empty list, so we do it anyway
// to not blow up if we ever decide copy it in the middle of a transaction
dst.accessList = src.accessList.Copy()
dst.transientStorage = src.transientStorage.Copy()
dst.journal = newJournal()

// Copy the dirty states, logs, and preimages
for addr := range s.journal.dirties {
for addr := range src.journal.dirties {
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
// and in the Finalise-method, there is a case where an object is in the journal but not
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
// nil
if object, exist := s.stateObjects[addr]; exist {
state.stateObjects[addr] = object.deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{}
if object, exist := src.stateObjects[addr]; exist {
dst.stateObjects[addr] = object.deepCopy(dst)
dst.stateObjectsDirty[addr] = struct{}{}
}
}
// Above, we don't copy the actual journal. This means that if the copy is copied, the
// loop above will be a no-op, since the copy's journal is empty.
// Thus, here we iterate over stateObjects, to enable copies of copies
for addr := range s.stateObjectsDirty {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{}
for addr := range src.stateObjectsDirty {
if _, exist := dst.stateObjects[addr]; !exist {
dst.stateObjects[addr] = src.stateObjects[addr].deepCopy(dst)
dst.stateObjectsDirty[addr] = struct{}{}
}
}
for addr := range s.stateObjectsDirtyStorage {
state.stateObjectsDirtyStorage[addr] = struct{}{}
for addr := range src.stateObjectsDirtyStorage {
dst.stateObjectsDirtyStorage[addr] = struct{}{}
}

deepCopyLogs(s, state)
// Deep copy logs
deepCopyLogs(src, dst)

for hash, preimage := range s.preimages {
state.preimages[hash] = preimage
// Copy preimages
for hash, preimage := range src.preimages {
dst.preimages[hash] = preimage
}

// Do we need to copy the access list? In practice: No. At the start of a
// transaction, the access list is empty. In practice, we only ever copy state
// _between_ transactions/blocks, never in the middle of a transaction.
// However, it doesn't cost us much to copy an empty list, so we do it anyway
// to not blow up if we ever decide copy it in the middle of a transaction
state.accessList = s.accessList.Copy()
state.transientStorage = s.transientStorage.Copy()
if s.snaps != nil {
if src.snaps != nil {
// In order for the miner to be able to use and make additions
// to the snapshot tree, we need to copy that aswell.
// Otherwise, any block mined by ourselves will cause gaps in the tree,
// and force the miner to operate trie-backed only
state.snaps = s.snaps
state.snap = s.snap
dst.snaps = src.snaps
dst.snap = src.snap
// deep copy needed
state.snapDestructs = make(map[common.Hash]struct{})
for k, v := range s.snapDestructs {
state.snapDestructs[k] = v
dst.snapDestructs = make(map[common.Hash]struct{})
for k, v := range src.snapDestructs {
dst.snapDestructs[k] = v
}
state.snapAccounts = make(map[common.Hash][]byte)
for k, v := range s.snapAccounts {
state.snapAccounts[k] = v
dst.snapAccounts = make(map[common.Hash][]byte)
for k, v := range src.snapAccounts {
dst.snapAccounts[k] = v
}
state.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
for k, v := range s.snapStorage {
dst.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
for k, v := range src.snapStorage {
temp := make(map[common.Hash][]byte)
for kk, vv := range v {
temp[kk] = vv
}
state.snapStorage[k] = temp
dst.snapStorage[k] = temp
}
}
return state
}

// deepCopyLogs deep-copies StateDB.logs from the left to the right.
Expand Down
2 changes: 1 addition & 1 deletion kaiax/gasless/impl/getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (g *GaslessModule) updateAddresses(header *types.Header) error {
if err != nil {
g.swapRouter = common.Address{}
g.allowedTokens = map[common.Address]bool{}
logger.Warn("there is something wrong with multicall contract", err.Error())
logger.Warn("there is something wrong with multicall contract", "err", err.Error())
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion work/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ func (env *Task) commitBundleTransaction(bundle *builder.Bundle, bc BlockChain,
}

restoreEnv := func() {
*env.state = *lastSnapshot
env.state.Set(lastSnapshot)
env.header.GasUsed = gasUsedSnapshot
env.tcount = tcountSnapshot
}
Expand All @@ -929,6 +929,7 @@ func (env *Task) commitBundleTransaction(bundle *builder.Bundle, bc BlockChain,
if err != vm.ErrInsufficientBalance && err != vm.ErrTotalTimeLimitReached {
markAllTxUnexecutable()
}
logger.Error("ApplyTransaction error, restoring env", "error", err)
restoreEnv()
if err == nil {
err = kerrors.ErrRevertedBundleByVmErr
Expand Down