Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Problem: contract call which does lots of message calls is slow to execute #729

Merged
merged 7 commits into from
Jan 5, 2022

Conversation

yihuang
Copy link
Contributor

@yihuang yihuang commented Nov 8, 2021

This PR is huge, added some guide for review in the comment section below.

Description

Closes: #710

As profiled in the issue page, the bottleneck is the slowness of deep context stack, there's no easy way around it other than refactor the StateDB to use journal logs like what go-ethereum does, thus this big code refactoring.

What this PR does

  • Implement a standalone statedb module in x/evm/statedb which implements vm.StateDB interface with the support of several keeper methods, the keeper interface is captured in statedb.Keeper.
  • The StateDB implementation keep all the dirty states in memory, at the end of tx execution, commit all the dirty changes to keeper at once
  • record all the modify operations in a list of journal logs, snapshot revert is done by undo part of the logs.
  • x/evm/statedb/{journal.go, access_list.go, state_object.go} are directly adapted from go-ethereum itself.

Benchmark result

BenchmarkMessageCall (improved 310X)

Before

BenchmarkMessageCall-16    	       1	25846269634 ns/op
BenchmarkMessageCall-16    	       1	26773165908 ns/op
BenchmarkMessageCall-16    	       1	25786298762 ns/op

After

BenchmarkMessageCall-16    	      12	  91367037 ns/op
BenchmarkMessageCall-16    	      13	  82284757 ns/op
BenchmarkMessageCall-16    	      14	  83061467 ns/op

BenchmarkEmitLogs (improved 2.5X)

Before

BenchmarkEmitLogs-16    	      98	  12034965 ns/op
BenchmarkEmitLogs-16    	      98	  12146658 ns/op
BenchmarkEmitLogs-16    	      96	  12313053 ns/op

After

BenchmarkEmitLogs-16    	     225	   5288751 ns/op
BenchmarkEmitLogs-16    	     232	   5189175 ns/op
BenchmarkEmitLogs-16    	     222	   5191943 ns/op

For contributor use:

  • Targeted PR against correct branch (see CONTRIBUTING.md)
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the module structure standards.
  • Wrote unit and integration tests
  • Updated relevant documentation (docs/) or specification (x/<module>/spec/)
  • Added relevant godoc comments.
  • Added a relevant changelog entry to the Unreleased section in CHANGELOG.md
  • Re-reviewed Files changed in the Github PR explorer

For admin use:

  • Added appropriate labels to PR (ex. WIP, R4R, docs, etc)
  • Reviewers assigned
  • Squashed all commits, uses message "Merge pull request #XYZ: [title]" (coding standards)

@yihuang yihuang marked this pull request as draft November 8, 2021 11:52
@codecov
Copy link

codecov bot commented Nov 8, 2021

Codecov Report

Merging #729 (02e701a) into main (03768c2) will decrease coverage by 1.04%.
The diff coverage is 66.96%.

❗ Current head 02e701a differs from pull request most recent head 33d9d29. Consider uploading reports for the commit 33d9d29 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main     #729      +/-   ##
==========================================
- Coverage   57.79%   56.74%   -1.05%     
==========================================
  Files          74       78       +4     
  Lines        6205     6238      +33     
==========================================
- Hits         3586     3540      -46     
- Misses       2408     2499      +91     
+ Partials      211      199      -12     
Impacted Files Coverage Δ
x/evm/keeper/abci.go 20.00% <0.00%> (-1.43%) ⬇️
x/evm/types/key.go 0.00% <ø> (ø)
x/evm/statedb/access_list.go 5.79% <5.79%> (ø)
x/evm/genesis.go 53.57% <50.00%> (+1.90%) ⬆️
x/evm/statedb/config.go 50.00% <50.00%> (ø)
app/ante/eth.go 81.23% <53.84%> (-1.87%) ⬇️
x/evm/statedb/statedb.go 67.64% <67.64%> (ø)
x/evm/statedb/journal.go 71.11% <71.11%> (ø)
x/evm/keeper/statedb.go 77.39% <73.33%> (+4.19%) ⬆️
x/evm/keeper/grpc_query.go 66.66% <73.58%> (-1.22%) ⬇️
... and 11 more

@odeke-em
Copy link
Contributor

odeke-em commented Nov 8, 2021

@yihuang could I kindly ask if we've considered that things might be much faster using smt inside of cosmos-sdk instead of cosmos/iavl?

@yihuang
Copy link
Contributor Author

yihuang commented Nov 8, 2021

@yihuang could I kindly ask if we've considered that things might be much faster using smt inside of cosmos-sdk instead of cosmos/iavl?

Does that mean we need to reimplement state-sync too?

@tomtau
Copy link
Contributor

tomtau commented Nov 9, 2021

@yihuang could I kindly ask if we've considered that things might be much faster using smt inside of cosmos-sdk instead of cosmos/iavl?

@odeke-em SMT is orthogonal to it AFAIK: one of the core issues here is e.g. nested cache contexts (cosmos/cosmos-sdk#10310) #710 (comment)

@yihuang yihuang force-pushed the statedb-refactor branch 4 times, most recently from 99f643c to 69d4309 Compare November 15, 2021 10:46
@yihuang yihuang marked this pull request as ready for review November 16, 2021 01:59
@tomtau
Copy link
Contributor

tomtau commented Nov 16, 2021

the importer test runs faster now:

processed block: 97000 (time so far: 3m3.791587401s)
--- PASS: TestImporterTestSuite (186.34s)
    --- PASS: TestImporterTestSuite/TestImportBlocks (186.34s)

vs on main:

processed block: 97000 (time so far: 6m53.091837321s)
--- PASS: TestImporterTestSuite (416.45s)
    --- PASS: TestImporterTestSuite/TestImportBlocks (416.45s)

@yihuang
Copy link
Contributor Author

yihuang commented Nov 17, 2021

Review Guide

Abstract

The problem:

  • Deep context stack is slow to do commit, and it's hard to work around this issue.
  • Have to call WithContext on EvmKeeper is error-prone.

The basic idea is to adopt the design in go-ethereum:

  • Cache the intermediate states during contract execution in memory, flush the dirty states to storage at the end of tx execution.
  • Move the vm.StateDB implementation out of EvmKeeper, then the EvmKeeper become an idiomatic keeper, mostly providing stateless operations over the storage, the ctx parameter is passed around explicitly.
  • snapshot and revert is done by recording and undoing the journal logs.

x/evm/statedb package

All the statedb logic is implemented in this package, the access to underlying storage is provided by statedb.Keeper interface, which is implemented by EvmKeeper.

Most of the StateDB implementations are directly adapted from go-ethereum codebase itself.

Transient Storage Simplification

The transient storage is greatly simplified since many temporary states are moved to memory, the left ones are:

  • KeyPrefixTransientBloom store current block bloom
  • KeyPrefixTransientTxIndex store current tx index
  • KeyPrefixTransientLogSize store current log index

Persistent Storage

It doesn't change the format of persistent storage, so no migration is needed when upgrading. But because of different storage writing orders, the app hash would be different.

Unit Test Changes

They are all mechanically changes to adapt to the new API, the test logic didn't change.

app/ante/eth.go Outdated Show resolved Hide resolved
Copy link
Contributor

@tomtau tomtau left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the review guide! I think moving out statedb makes sense. At least removing the need for the WithContext calls makes it a bit less error-prone.
I left a few comments, but it's mostly random parts I came across and they were probably there before:

Boy Scout Rule

For the upgrade migrations, it seems some of the storage keys aren't used anymore -- should that be cleaned up in migrations?

In any case, at least from the importer test, this looks like a big win. It'd need a bit more testing (especially on the web3 RPC API) and pairs of eyes to look at this PR. But overall, it looks promising (GPL v3 re-licensing is a side-issue and there are already some code portions from go-ethereum before in the current codebase, e.g.: https://github.com/tharsis/ethermint/blob/main/x/evm/types/tx_args.go#L19 so Ethermint should be re-licensed anyway)

app/ante/eth.go Show resolved Hide resolved
app/ante/eth.go Outdated Show resolved Hide resolved
app/ante/eth.go Outdated Show resolved Hide resolved
x/evm/handler_test.go Show resolved Hide resolved
x/evm/keeper/grpc_query.go Show resolved Hide resolved
x/evm/statedb/access_list.go Show resolved Hide resolved
x/evm/statedb/journal.go Show resolved Hide resolved
x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/keeper/keeper.go Outdated Show resolved Hide resolved
@yihuang
Copy link
Contributor Author

yihuang commented Nov 19, 2021

For the upgrade migrations, it seems some of the storage keys aren't used anymore -- should that be cleaned up in migrations?

I think the removed keys are all transient storage, so no migration is needed.

Copy link
Contributor

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass

x/evm/genesis.go Outdated Show resolved Hide resolved
app/ante/utils_test.go Show resolved Hide resolved
x/evm/handler_test.go Show resolved Hide resolved
x/evm/keeper/grpc_query.go Outdated Show resolved Hide resolved
x/evm/keeper/grpc_query.go Outdated Show resolved Hide resolved
x/evm/keeper/grpc_query.go Show resolved Hide resolved
app/ante/eth.go Show resolved Hide resolved
app/ante/eth.go Outdated Show resolved Hide resolved
app/ante/eth.go Outdated Show resolved Hide resolved
app/ante/eth.go Outdated Show resolved Hide resolved
@yihuang yihuang force-pushed the statedb-refactor branch 2 times, most recently from ef58960 to b9168a5 Compare January 4, 2022 13:09
Copy link
Contributor

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second pass

app/ante/eth.go Outdated Show resolved Hide resolved
app/ante/eth.go Outdated Show resolved Hide resolved
}

if err := evmkeeper.CheckSenderBalance(ctx, avd.bankKeeper, from, txData, evmDenom); err != nil {
if err := evmkeeper.CheckSenderBalance(sdk.NewIntFromBigInt(acct.Balance), txData); err != nil {
Copy link
Contributor

@fedekunze fedekunze Jan 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check if acct.Balance is not nil

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sdk.NewIntFromBigInt treat nil as an empty int, so looks ok?

CodeHash: k.GetCodeHash(addr).Hex(),
Nonce: k.GetNonce(addr),
Balance: acct.Balance.String(),
CodeHash: hexutil.Encode(acct.CodeHash),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use common library instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// operations.
func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) {
idx, addrOk := al.addresses[address]
// There are two ways this can fail
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two ways this can fail

... which ones? can you add a comment

Copy link
Contributor Author

@yihuang yihuang Jan 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, I'm not sure, it's blindly copied from go-ethereum, by reading the code, it seems impossible to fail here, probably the comment is out of date here. let me remove it.

x/evm/statedb/journal.go Show resolved Hide resolved
x/evm/statedb/journal.go Show resolved Hide resolved
x/evm/statedb/journal.go Show resolved Hide resolved
x/evm/statedb/journal.go Show resolved Hide resolved
x/evm/statedb/journal.go Show resolved Hide resolved
Copy link
Contributor

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

third pass, would be great to add tests for journal

x/evm/statedb/journal.go Show resolved Hide resolved
x/evm/statedb/state_object.go Show resolved Hide resolved
x/evm/statedb/state_object.go Show resolved Hide resolved
Comment on lines 62 to 66
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no dbErr?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed, since our account keeper API doesn't return database errors, so the field is useless here.

x/evm/statedb/state_object.go Show resolved Hide resolved
x/evm/statedb/state_object.go Show resolved Hide resolved
return nil
}

func TestAccounts(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create a suite for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

{
"success,empty account",
func(t *testing.T, db *statedb.StateDB) {
require.Equal(t, true, db.Empty(addr2))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use suite.Require()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


var _ statedb.Keeper = &MockKeeper{}

type MockKeeper struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move to mock_test.go

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

require.Equal(t, big.NewInt(1), db.GetBalance(addr2))
},
},
// TODO access list, ForEachStorage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

address this TODO or create issue for follow up

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linked the #876 here.

Copy link
Contributor

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Final rounds of comments. Mostly adding more code and godoc comments.
Can you also add a state machine breaking change changelog?
Also can you open a PR to update the spec with the new design?

x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/statedb/statedb.go Show resolved Hide resolved
x/evm/keeper/keeper.go Outdated Show resolved Hide resolved
x/evm/keeper/keeper.go Outdated Show resolved Hide resolved
x/evm/keeper/keeper.go Outdated Show resolved Hide resolved
x/evm/keeper/keeper.go Outdated Show resolved Hide resolved
x/evm/keeper/keeper.go Outdated Show resolved Hide resolved
@yihuang yihuang force-pushed the statedb-refactor branch 3 times, most recently from 976e39a to 3c26f7f Compare January 5, 2022 03:42
yihuang and others added 4 commits January 5, 2022 12:03
unit tests

unit tests

keeper implementation

extract TxConfig

remove unused code
Co-authored-by: Federico Kunze Küllmer <[email protected]>
Copy link
Contributor

@fedekunze fedekunze left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK! 💯 @yihuang can you add the relevant changelog entries?

@yihuang
Copy link
Contributor Author

yihuang commented Jan 5, 2022

Final rounds of comments. Mostly adding more code and godoc comments.

for the unit tests and coding style of statedb package, I suggest postponing that to another PR (#876), because:

  • this PR is big enough
  • there's are several modules under x/evm/statedb that are copied from go-ethereum, the diffs are small, it's easy to track what have we changed on top of go-ethereum version.

Can you also add a state machine breaking change changelog?

done

Also can you open a PR to update the spec with the new design?

#879

@fedekunze fedekunze merged commit ade8431 into evmos:main Jan 5, 2022
@yihuang yihuang deleted the statedb-refactor branch January 5, 2022 07:33
mmsqe added a commit to mmsqe/ethermint that referenced this pull request Dec 21, 2022
mmsqe added a commit to mmsqe/ethermint that referenced this pull request Dec 26, 2022
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.

Problem: contract call which does lots of message calls is slow to execute
7 participants