0%

fuzzyVM

Reproduce the fuzzyVM from Go-ethereum team for comparison.

1
2
3
4
5
6
7
8
9
10
# Clone the repo to a place of your liking using
git clone https://github.com/MariusVanDerWijden/FuzzyVM.git
# Enter the repo
cd FuzzyVM
# Build the binary
go build
# Create an initial corpus
./FuzzyVM corpus --count 100
# Run the fuzzer
./FuzzyVM run

Check func (g *GstMaker) Fill(traceOutput io.Writer) in FuzzyVM/fuzzer/fuzzer.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Fuzz is the entry point for go-fuzz
func Fuzz(data []byte) int {
// Too little data destroys our performance and makes it hard for the generator
if len(data) < 32 {
return -1
}
f := filler.NewFiller(data)
testMaker, _ := generator.GenerateProgram(f)
name := randTestName(data)
// minimize test
minimized, err := minimizeProgram(testMaker, name)
if err == nil {
testMaker = minimized
}
hashed := hash(testMaker.ToGeneralStateTest("hashName"))
finalName := fmt.Sprintf("FuzzyVM-%v", common.Bytes2Hex(hashed))
// Execute the test and write out the resulting trace
var traceFile *os.File
if shouldTrace {
traceFile = setupTrace(finalName)
defer traceFile.Close()
}
// where statetest are executed.
if err := testMaker.Fill(traceFile); err != nil {
panic(err)
}
// Save the test
test := testMaker.ToGeneralStateTest(finalName)
if storeTest(test, hashed, finalName) {
return 0
}
if f.UsedUp() {
return 0
}
return 1
}

Fill is in goevmlab/fuzzing/statetest_maker.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (g *GstMaker) Fill(traceOutput io.Writer) error {

test, err := g.ToStateTest()
if err != nil {
return err
}
subtest := test.Subtests()[0]
cfg := vm.Config{}
if traceOutput != nil {
cfg.Tracer = logger.NewJSONLogger(&logger.Config{}, traceOutput)
}
// Here, RunNoVerify.
state, root, _, err := test.RunNoVerify(subtest, cfg, false, rawdb.HashScheme)
if err != nil {
return err
}
defer state.Close()
logs := rlpHash(state.StateDB.Logs())
g.SetResult(root, logs)
return nil
}

RunNoVerify is located in go-ethereum@1.14.3/tests/state_test_util.go .

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string) (st StateTestState, root common.Hash, err error) {
config, eips, err := GetChainConfig(subtest.Fork)
if err != nil {
return st, common.Hash{}, UnsupportedForkError{subtest.Fork}
}
vmconfig.ExtraEips = eips

block := t.genesis(config).ToBlock()
st = MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter, scheme)

var baseFee *big.Int
if config.IsLondon(new(big.Int)) {
baseFee = t.json.Env.BaseFee
if baseFee == nil {
// Retesteth uses `0x10` for genesis baseFee. Therefore, it defaults to
// parent - 2 : 0xa as the basefee for 'this' context.
baseFee = big.NewInt(0x0a)
}
}
post := t.json.Post[subtest.Fork][subtest.Index]
msg, err := t.json.Tx.toMessage(post, baseFee)
if err != nil {
return st, common.Hash{}, err
}

{ // Blob transactions may be present after the Cancun fork.
// In production,
// - the header is verified against the max in eip4844.go:VerifyEIP4844Header
// - the block body is verified against the header in block_validator.go:ValidateBody
// Here, we just do this shortcut smaller fix, since state tests do not
// utilize those codepaths
if len(msg.BlobHashes)*params.BlobTxBlobGasPerBlob > params.MaxBlobGasPerBlock {
return st, common.Hash{}, errors.New("blob gas exceeds maximum")
}
}

// Try to recover tx with current signer
if len(post.TxBytes) != 0 {
var ttx types.Transaction
err := ttx.UnmarshalBinary(post.TxBytes)
if err != nil {
return st, common.Hash{}, err
}
if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil {
return st, common.Hash{}, err
}
}

// Prepare the EVM.
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase)
context.GetHash = vmTestBlockHash
context.BaseFee = baseFee
context.Random = nil
if t.json.Env.Difficulty != nil {
context.Difficulty = new(big.Int).Set(t.json.Env.Difficulty)
}
if config.IsLondon(new(big.Int)) && t.json.Env.Random != nil {
rnd := common.BigToHash(t.json.Env.Random)
context.Random = &rnd
context.Difficulty = big.NewInt(0)
}
if config.IsCancun(new(big.Int), block.Time()) && t.json.Env.ExcessBlobGas != nil {
context.BlobBaseFee = eip4844.CalcBlobFee(*t.json.Env.ExcessBlobGas)
}
evm := vm.NewEVM(context, txContext, st.StateDB, config, vmconfig)

if tracer := vmconfig.Tracer; tracer != nil && tracer.OnTxStart != nil {
tracer.OnTxStart(evm.GetVMContext(), nil, msg.From)
if evm.Config.Tracer.OnTxEnd != nil {
defer func() {
evm.Config.Tracer.OnTxEnd(nil, err)
}()
}
}
// Execute the message.
snapshot := st.StateDB.Snapshot()
gaspool := new(core.GasPool)
gaspool.AddGas(block.GasLimit())
_, err = core.ApplyMessage(evm, msg, gaspool)
if err != nil {
st.StateDB.RevertToSnapshot(snapshot)
}
// Add 0-value mining reward. This only makes a difference in the cases
// where
// - the coinbase self-destructed, or
// - there are only 'bad' transactions, which aren't executed. In those cases,
// the coinbase gets no txfee, so isn't created, and thus needs to be touched
st.StateDB.AddBalance(block.Coinbase(), new(uint256.Int), tracing.BalanceChangeUnspecified)

// Commit state mutations into database.
root, _ = st.StateDB.Commit(block.NumberU64(), config.IsEIP158(block.Number()))
return st, root, err
}

It seems that fuzzyVM only run geth, instead of differential testing with other EVM implementations. So I will re-compile fuzzyVM with (url) to get coverage.

This job is time-consuming, moving to the server…

We have 40 workers on the server while 6 workers in wsl.

1
2
3
4
5
6
go build -cover -o FuzzyVM-ins -coverpkg=github.com/ethereum/go-ethereum/core/vm,github.com/MariusVanDerWijden/FuzzyVM,github.com/ethereum/go-ethereum/common,github.com/ethereum/go-ethereum/core/rawdb,github.com/ethereum/go-ethereum/tests,github.com/ethereum/go-ethereum/common

GOCOVERDIR=coverdata ./FuzzyVM-ins corpus --count 100
mkdir coverdata
GOCOVERDIR=coverdata ./FuzzyVM-ins run
go tool covdata percent -i=coverdata

image-20241228100931599

image-20241228100946111

image-20241228100956745

Filed an issue at github, not responding in 48H.

Another try in wsl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
alleysira@LAPTOP-M4HO8L6S:~/FuzzyVM$ GOCOVERDIR=coverdata ./FuzzyVM-ins run
Duplicate test found
&map[FuzzyVM-1052571076-2145213028:0xc00058a000]fuzz: elapsed: 0s, gathering baseline coverage: 0/455 completed
fuzz: elapsed: 3s, gathering baseline coverage: 250/455 completed
fuzz: elapsed: 6s, gathering baseline coverage: 409/455 completed
fuzz: elapsed: 8s, gathering baseline coverage: 455/455 completed, now fuzzing with 6 workers
fuzz: elapsed: 9s, execs: 670 (87/sec), new interesting: 0 (total: 455)
fuzz: elapsed: 16m9s, execs: 103867 (69/sec), new interesting: 164 (total: 619)
fuzz: elapsed: 16m12s, execs: 103868 (0/sec), new interesting: 165 (total: 620)
fuzz: elapsed: 16m14s, execs: 103894 (12/sec), new interesting: 165 (total: 620)
--- FAIL: FuzzVM (974.43s)
fuzzing process hung or terminated unexpectedly while minimizing: EOF
Failing input written to testdata/fuzz/FuzzVM/b78e04fdd359ae40
To re-run:
go test -run=FuzzVM/b78e04fdd359ae40
FAIL
exit status 1
FAIL github.com/MariusVanDerWijden/FuzzyVM/fuzzer 975.320s
exit status 1
alleysira@LAPTOP-M4HO8L6S:~/FuzzyVM$ go tool covdata percent -i=coverdata
github.com/MariusVanDerWijden/FuzzyVM coverage: 35.5% of statements
github.com/ethereum/go-ethereum/common coverage: 7.9% of statements
github.com/ethereum/go-ethereum/core/rawdb coverage: 0.0% of statements
github.com/ethereum/go-ethereum/core/vm coverage: 7.2% of statements
github.com/ethereum/go-ethereum/tests coverage: 0.2% of statements

Try exception handling mechanism later.