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

Conversation

ian0371
Copy link
Collaborator

@ian0371 ian0371 commented Apr 2, 2025

Proposed changes

  • Fix recovering env.state with snapshot (work/worker.go). It fixes bad block issue when a tx in bundle reverts (Error: invalid gas used (remote: 461415 local: 463655)), which is caused by shallow-copying of statedb.stateObjects.
    • Instead of using a new statedb object from commitBundleTransaction() (i.e., env.state = lastSnapshot), old statedb must be reused because bc.State() callers will use it. Thus, func (s *StateDB) Set(src *StateDB) is introduced.
    • Now, all fields are copied by copyStateDB().
  • Update GenABlockWithTransactionsWithBundle to filter failing transactions
  • Remove txHashesExpectedFail argument from Update and GenABlockWithTransactions, so now it's the same as v1.0.3
  • Fix log15 error (kaiax/gasless/impl/getter.go)

Types of changes

  • Bugfix
  • New feature or enhancement
  • Others

Checklist

  • I have read the CONTRIBUTING GUIDELINES doc
  • I have read the CLA and signed by comment I have read the CLA Document and I hereby sign the CLA in first time contribute
  • Lint and unit tests pass locally with my changes ($ make test)
  • I have added tests that prove my fix is effective or that my feature works
  • I have added necessary documentation (if appropriate)
  • Any dependent changes have been merged and published in downstream modules

@ian0371 ian0371 self-assigned this Apr 2, 2025
@Mdaiki0730
Copy link
Contributor

Update GenABlockWithTransactionsWithBundle to filter failing transactions

This fix does not test revert because it removes transactions from the list before creating a block.
Would you like to undo the changes in test?

@ian0371
Copy link
Collaborator Author

ian0371 commented Apr 2, 2025

@Mdaiki0730 It was because the test fails due to invalid merkle root:

$ go test ./tests -run=TestTxBundle
--- FAIL: TestTxBundle (3.06s)
    --- FAIL: TestTxBundle/TestTxBundleRevert (0.35s)
        kaia_scenario_test.go:2073: err = invalid merkle root (remote: 50f0a818a4d39c2c41a73eb2e5c76eced007b1b768eb695304959fdd356ee2cf local: ad1a7cfac0d2affa89b58a8f653826a5ea8d1cf512b46963d54331239d996294), n = 0

    --- FAIL: TestTxBundle/TestTxBundleRevertByEvmError (1.99s)
        kaia_scenario_test.go:2166: err = invalid merkle root (remote: 0fb833ac89e8b9c10d4de405013554710b2eb17451e8e3326e82dba0e1ca3470 local: 1bb3d9e364647e280a9e352e345eac4fcb84f4e680bf1c733b4ff61bb6409201), n = 0

FAIL
FAIL    github.com/kaiachain/kaia/tests 3.885s
FAIL

I assumed MineABlock() should exclude the reverting txs, just like the previous Update logic. So I simply merged the logic and put it in GenABlockWithTransactionsWithBundle.

But I understand your point. What do you think should be the new logic of MineABlock()? Do you think we need to implement restore-env-on-revert logic inside MineABlock()? If so, would it make a big difference from the logic in this PR?

@Mdaiki0730
Copy link
Contributor

Mdaiki0730 commented Apr 2, 2025

@ian0371
Since MineABlock() executes task.ApplyTransaction, it is expected that revert will occur even without any changes to test.
https://github.com/kaiachain/kaia/blob/dev/tests/kaia_test_blockchain_test.go#L270

Do you know what causes invalid merkle root?

@ian0371
Copy link
Collaborator Author

ian0371 commented Apr 2, 2025

@Mdaiki0730 Oh I see. I reverted for now. 5ed118c

@hyeonLewis
Copy link
Contributor

Please consider updating statedb.Copy() method together. Before bundling, no case copied states become a real one. But now, with copies, one can replace the original state, and we need to copy everything. The trieOpts, dbErr, prefetching seems to be necessarily included, while thash, bhash, txIndex, validRevisions, nextRevisionId are not necessary since we copy state only between the transactions (after calling Finalise).

diff --git a/blockchain/state/statedb.go b/blockchain/state/statedb.go
index 66d65ef83..7eb70139d 100644
--- a/blockchain/state/statedb.go
+++ b/blockchain/state/statedb.go
@@ -26,6 +26,7 @@ import (
        "bytes"
        "errors"
        "fmt"
+       "maps"
        "math/big"
        "sort"
        "sync/atomic"
@@ -904,13 +905,24 @@ func (s *StateDB) Copy() *StateDB {
        state := &StateDB{
                db:                       s.db,
                trie:                     s.db.CopyTrie(s.trie),
+               trieOpts:                 s.trieOpts,
                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)),
+               dbErr:                    s.dbErr,
                refund:                   s.refund,
                logs:                     make(map[common.Hash][]*types.Log, len(s.logs)),
                logSize:                  s.logSize,
-               preimages:                make(map[common.Hash][]byte),
+               preimages:                maps.Clone(s.preimages),
+               prefetching:              s.prefetching,
+
+               // 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
+               accessList:               s.accessList.Copy(),
+               transientStorage:         s.transientStorage.Copy(),
                journal:                  newJournal(),
        }
        // Copy the dirty states, logs, and preimages
@@ -939,17 +951,6 @@ func (s *StateDB) Copy() *StateDB {
 
        deepCopyLogs(s, state)
 
-       for hash, preimage := range s.preimages {
-               state.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 {
                // In order for the miner to be able to use and make additions
                // to the snapshot tree, we need to copy that aswell.

@ian0371 ian0371 force-pushed the feat/gasless-fix-restore branch from 5ed118c to 3d3eaff Compare April 3, 2025 07:15
@ian0371 ian0371 merged commit f58cc54 into kaiachain:feat/gasless Apr 4, 2025
9 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Apr 4, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants