• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lmittmann / w3 / 12712430872

10 Jan 2025 03:13PM UTC coverage: 64.258% (-0.04%) from 64.3%
12712430872

Pull #207

github

lmittmann
fix
Pull Request #207: w3vm: fix tx coinbase tip

1 of 1 new or added line in 1 file covered. (100.0%)

16 existing lines in 1 file now uncovered.

1992 of 3100 relevant lines covered (64.26%)

810.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.79
/w3vm/fetcher.go
1
package w3vm
2

3
import (
4
        "bytes"
5
        "encoding/json"
6
        "errors"
7
        "fmt"
8
        "math/big"
9
        "os"
10
        "path/filepath"
11
        "sync"
12
        "sync/atomic"
13
        "testing"
14

15
        "github.com/ethereum/go-ethereum/common"
16
        "github.com/ethereum/go-ethereum/common/hexutil"
17
        "github.com/ethereum/go-ethereum/core/types"
18
        "github.com/holiman/uint256"
19
        "github.com/lmittmann/w3"
20
        "github.com/lmittmann/w3/internal/crypto"
21
        w3hexutil "github.com/lmittmann/w3/internal/hexutil"
22
        "github.com/lmittmann/w3/module/eth"
23
        "github.com/lmittmann/w3/w3types"
24
)
25

26
// Fetcher is the interface to access account state of a blockchain.
27
type Fetcher interface {
28
        // Account fetches the account of the given address.
29
        Account(common.Address) (*types.StateAccount, error)
30

31
        // Code fetches the code of the given code hash.
32
        Code(common.Hash) ([]byte, error)
33

34
        // StorageAt fetches the state of the given address and storage slot.
35
        StorageAt(common.Address, common.Hash) (common.Hash, error)
36

37
        // HeaderHash fetches the hash of the header with the given number.
38
        HeaderHash(uint64) (common.Hash, error)
39
}
40

41
type rpcFetcher struct {
42
        client      *w3.Client
43
        blockNumber *big.Int
44

45
        mux          sync.RWMutex
46
        accounts     map[common.Address]func() (*types.StateAccount, error)
47
        contracts    map[common.Hash]func() ([]byte, error)
48
        mux2         sync.RWMutex
49
        storage      map[storageKey]func() (common.Hash, error)
50
        mux3         sync.RWMutex
51
        headerHashes map[uint64]func() (common.Hash, error)
52

53
        dirty uint32 // indicates whether new state has been fetched (0=false, 1=true)
54
}
55

56
// NewRPCFetcher returns a new [Fetcher] that fetches account state from the given
57
// RPC client for the given block number.
58
//
59
// Note, that the returned state for a given block number is the state after the
60
// execution of that block.
61
func NewRPCFetcher(client *w3.Client, blockNumber *big.Int) Fetcher {
6✔
62
        return newRPCFetcher(client, blockNumber)
6✔
63
}
6✔
64

65
func newRPCFetcher(client *w3.Client, blockNumber *big.Int) *rpcFetcher {
51✔
66
        return &rpcFetcher{
51✔
67
                client:       client,
51✔
68
                blockNumber:  blockNumber,
51✔
69
                accounts:     make(map[common.Address]func() (*types.StateAccount, error)),
51✔
70
                contracts:    make(map[common.Hash]func() ([]byte, error)),
51✔
71
                storage:      make(map[storageKey]func() (common.Hash, error)),
51✔
72
                headerHashes: make(map[uint64]func() (common.Hash, error)),
51✔
73
        }
51✔
74
}
51✔
75

76
func (f *rpcFetcher) Account(addr common.Address) (a *types.StateAccount, e error) {
13,345✔
77
        f.mux.RLock()
13,345✔
78
        acc, ok := f.accounts[addr]
13,345✔
79
        f.mux.RUnlock()
13,345✔
80
        if ok {
26,674✔
81
                return acc()
13,329✔
82
        }
13,329✔
83
        atomic.StoreUint32(&f.dirty, 1)
16✔
84

16✔
85
        var (
16✔
86
                accNew      = &types.StateAccount{Balance: new(uint256.Int)}
16✔
87
                contractNew []byte
16✔
88

16✔
89
                accCh      = make(chan func() (*types.StateAccount, error), 1)
16✔
90
                contractCh = make(chan func() ([]byte, error), 1)
16✔
91
        )
16✔
92
        go func() {
32✔
93
                err := f.call(
16✔
94
                        eth.Nonce(addr, f.blockNumber).Returns(&accNew.Nonce),
16✔
95
                        ethBalance(addr, f.blockNumber).Returns(accNew.Balance),
16✔
96
                        eth.Code(addr, f.blockNumber).Returns(&contractNew),
16✔
97
                )
16✔
98
                if err != nil {
16✔
99
                        accCh <- func() (*types.StateAccount, error) { return nil, err }
×
100
                        contractCh <- func() ([]byte, error) { return nil, err }
×
101
                        return
×
102
                }
103

104
                if len(contractNew) == 0 {
24✔
105
                        accNew.CodeHash = types.EmptyCodeHash[:]
8✔
106
                } else {
16✔
107
                        accNew.CodeHash = crypto.Keccak256(contractNew)
8✔
108
                }
8✔
109
                accCh <- func() (*types.StateAccount, error) { return accNew, nil }
32✔
110
                contractCh <- func() ([]byte, error) { return contractNew, nil }
24✔
111
        }()
112

113
        f.mux.Lock()
16✔
114
        defer f.mux.Unlock()
16✔
115
        accOnce := sync.OnceValues(<-accCh)
16✔
116
        f.accounts[addr] = accOnce
16✔
117
        accRet, err := accOnce()
16✔
118
        if err != nil {
16✔
119
                return nil, err
×
120
        }
×
121
        f.contracts[common.BytesToHash(accRet.CodeHash)] = sync.OnceValues(<-contractCh)
16✔
122
        return accRet, nil
16✔
123
}
124

125
func (f *rpcFetcher) Code(codeHash common.Hash) ([]byte, error) {
8,731✔
126
        f.mux.RLock()
8,731✔
127
        contract, ok := f.contracts[codeHash]
8,731✔
128
        f.mux.RUnlock()
8,731✔
129
        if !ok {
8,731✔
130
                panic("not implemented")
×
131
        }
132
        return contract()
8,731✔
133
}
134

135
func (f *rpcFetcher) StorageAt(addr common.Address, slot common.Hash) (common.Hash, error) {
40,428✔
136
        key := storageKey{addr, slot}
40,428✔
137

40,428✔
138
        f.mux2.RLock()
40,428✔
139
        storage, ok := f.storage[key]
40,428✔
140
        f.mux2.RUnlock()
40,428✔
141
        if ok {
80,821✔
142
                return storage()
40,393✔
143
        }
40,393✔
144
        atomic.StoreUint32(&f.dirty, 1)
35✔
145

35✔
146
        var (
35✔
147
                storageVal   common.Hash
35✔
148
                storageValCh = make(chan func() (common.Hash, error), 1)
35✔
149
        )
35✔
150
        go func() {
70✔
151
                err := f.call(ethStorageAt(addr, slot, f.blockNumber).Returns(&storageVal))
35✔
152
                storageValCh <- func() (common.Hash, error) { return storageVal, err }
70✔
153
        }()
154

155
        storageValOnce := sync.OnceValues(<-storageValCh)
35✔
156
        f.mux2.Lock()
35✔
157
        f.storage[key] = storageValOnce
35✔
158
        f.mux2.Unlock()
35✔
159
        return storageValOnce()
35✔
160
}
161

162
func (f *rpcFetcher) HeaderHash(blockNumber uint64) (common.Hash, error) {
1,425✔
163
        f.mux3.RLock()
1,425✔
164
        hash, ok := f.headerHashes[blockNumber]
1,425✔
165
        f.mux3.RUnlock()
1,425✔
166
        if ok {
2,850✔
167
                return hash()
1,425✔
168
        }
1,425✔
169
        atomic.StoreUint32(&f.dirty, 1)
×
170

×
171
        var (
×
172
                header       header
×
173
                headerHashCh = make(chan func() (common.Hash, error), 1)
×
174
        )
×
175
        go func() {
×
176
                err := f.call(ethHeaderHash(blockNumber).Returns(&header))
×
177
                headerHashCh <- func() (common.Hash, error) { return header.Hash, err }
×
178
        }()
179

180
        headerHashOnce := sync.OnceValues(<-headerHashCh)
×
181
        f.mux3.Lock()
×
182
        f.headerHashes[blockNumber] = headerHashOnce
×
183
        f.mux3.Unlock()
×
184
        return headerHashOnce()
×
185
}
186

187
func (f *rpcFetcher) call(calls ...w3types.RPCCaller) error {
51✔
188
        return f.client.Call(calls...)
51✔
189
}
51✔
190

191
////////////////////////////////////////////////////////////////////////////////////////////////////
192
// TestingRPCFetcher ///////////////////////////////////////////////////////////////////////////////
193
////////////////////////////////////////////////////////////////////////////////////////////////////
194

195
// NewTestingRPCFetcher returns a new [Fetcher] like [NewRPCFetcher], but caches
196
// the fetched state on disk in the testdata directory of the tests package.
197
func NewTestingRPCFetcher(tb testing.TB, chainID uint64, client *w3.Client, blockNumber *big.Int) Fetcher {
45✔
198
        if ok := isTbInMod(getTbFilepath(tb)); !ok {
45✔
199
                panic("must be called from a test in a module")
×
200
        }
201

202
        fetcher := newRPCFetcher(client, blockNumber)
45✔
203
        if err := fetcher.loadTestdataState(tb, chainID); err != nil {
45✔
204
                tb.Fatalf("w3vm: failed to load state from testdata: %v", err)
×
205
        }
×
206

207
        tb.Cleanup(func() {
90✔
208
                if err := fetcher.storeTestdataState(tb, chainID); err != nil {
45✔
209
                        tb.Fatalf("w3vm: failed to write state to testdata: %v", err)
×
210
                }
×
211
        })
212
        return fetcher
45✔
213
}
214

215
var (
216
        globalStateStoreMux sync.RWMutex
217
        globalStateStore    = make(map[string]*testdataState)
218
)
219

220
func (f *rpcFetcher) loadTestdataState(tb testing.TB, chainID uint64) error {
45✔
221
        dir := getTbFilepath(tb)
45✔
222
        fn := filepath.Join(dir,
45✔
223
                "testdata",
45✔
224
                "w3vm",
45✔
225
                fmt.Sprintf("%d_%v.json", chainID, f.blockNumber),
45✔
226
        )
45✔
227

45✔
228
        var s *testdataState
45✔
229

45✔
230
        // check if the state has already been loaded
45✔
231
        globalStateStoreMux.RLock()
45✔
232
        s, ok := globalStateStore[fn]
45✔
233
        globalStateStoreMux.RUnlock()
45✔
234

45✔
235
        if !ok {
90✔
236
                // load state from file
45✔
237
                file, err := os.Open(fn)
45✔
238
                if errors.Is(err, os.ErrNotExist) {
46✔
239
                        return nil
1✔
240
                } else if err != nil {
45✔
241
                        return err
×
242
                }
×
243
                defer file.Close()
44✔
244

44✔
245
                if err := json.NewDecoder(file).Decode(&s); err != nil {
44✔
246
                        return err
×
247
                }
×
248
        }
249

250
        f.mux.Lock()
44✔
251
        f.mux2.Lock()
44✔
252
        f.mux3.Lock()
44✔
253
        defer f.mux.Unlock()
44✔
254
        defer f.mux2.Unlock()
44✔
255
        defer f.mux3.Unlock()
44✔
256

44✔
257
        for addr, acc := range s.Accounts {
13,377✔
258
                var codeHash common.Hash
13,333✔
259
                if len(acc.Code) > 0 {
19,703✔
260
                        codeHash = crypto.Keccak256Hash(acc.Code)
6,370✔
261
                } else {
13,333✔
262
                        codeHash = types.EmptyCodeHash
6,963✔
263
                }
6,963✔
264

265
                f.accounts[addr] = func() (*types.StateAccount, error) {
26,662✔
266
                        return &types.StateAccount{
13,329✔
267
                                Nonce:    uint64(acc.Nonce),
13,329✔
268
                                Balance:  (*uint256.Int)(acc.Balance),
13,329✔
269
                                CodeHash: codeHash[:],
13,329✔
270
                        }, nil
13,329✔
271
                }
13,329✔
272
                if _, ok := f.contracts[codeHash]; codeHash != types.EmptyCodeHash && !ok {
18,897✔
273
                        f.contracts[codeHash] = func() ([]byte, error) {
14,286✔
274
                                return acc.Code, nil
8,722✔
275
                        }
8,722✔
276
                }
277
                for slot, val := range acc.Storage {
53,737✔
278
                        f.storage[storageKey{addr, (common.Hash)(slot)}] = func() (common.Hash, error) {
80,797✔
279
                                return (common.Hash)(val), nil
40,393✔
280
                        }
40,393✔
281
                }
282
                for blockNumber, hash := range s.HeaderHashes {
60,739✔
283
                        f.headerHashes[uint64(blockNumber)] = func() (common.Hash, error) {
48,831✔
284
                                return hash, nil
1,425✔
285
                        }
1,425✔
286
                }
287
        }
288
        return nil
44✔
289
}
290

291
func (f *rpcFetcher) storeTestdataState(tb testing.TB, chainID uint64) error {
45✔
292
        if atomic.LoadUint32(&f.dirty) == 0 {
90✔
293
                return nil // the state has not been modified
45✔
294
        }
45✔
295

296
        dir := getTbFilepath(tb)
×
297
        fn := filepath.Join(dir,
×
298
                "testdata",
×
299
                "w3vm",
×
300
                fmt.Sprintf("%d_%v.json", chainID, f.blockNumber),
×
301
        )
×
302

×
303
        // build state
×
304
        f.mux.RLock()
×
305
        f.mux2.RLock()
×
306
        f.mux3.RLock()
×
307
        defer f.mux.RUnlock()
×
308
        defer f.mux2.RUnlock()
×
309
        defer f.mux3.RUnlock()
×
310

×
311
        s := &testdataState{
×
312
                Accounts:     make(map[common.Address]*account, len(f.accounts)),
×
313
                HeaderHashes: make(map[hexutil.Uint64]common.Hash, len(f.headerHashes)),
×
314
        }
×
315

×
316
        for addr, acc := range f.accounts {
×
317
                acc, err := acc()
×
318
                if err != nil {
×
319
                        continue
×
320
                }
321

322
                s.Accounts[addr] = &account{
×
323
                        Nonce:   hexutil.Uint64(acc.Nonce),
×
324
                        Balance: (*hexutil.U256)(acc.Balance),
×
325
                }
×
326
                if !bytes.Equal(acc.CodeHash, types.EmptyCodeHash[:]) {
×
327
                        s.Accounts[addr].Code, _ = f.contracts[common.BytesToHash(acc.CodeHash)]()
×
328
                }
×
329
        }
330

331
        for storageKey, storageVal := range f.storage {
×
332
                storageVal, err := storageVal()
×
333
                if err != nil {
×
334
                        continue
×
335
                }
336

337
                if s.Accounts[storageKey.addr].Storage == nil {
×
338
                        s.Accounts[storageKey.addr].Storage = make(map[w3hexutil.Hash]w3hexutil.Hash)
×
339
                }
×
340
                s.Accounts[storageKey.addr].Storage[w3hexutil.Hash(storageKey.slot)] = w3hexutil.Hash(storageVal)
×
UNCOV
341
        }
×
UNCOV
342

×
343
        for blockNumber, hash := range f.headerHashes {
344
                hash, err := hash()
345
                if err != nil {
×
346
                        continue
×
UNCOV
347
                }
×
348
                s.HeaderHashes[hexutil.Uint64(blockNumber)] = hash
×
349
        }
UNCOV
350

×
351
        globalStateStoreMux.Lock()
352
        defer globalStateStoreMux.Unlock()
353
        // merge state
×
354
        dstState, ok := globalStateStore[fn]
×
355
        if ok {
×
356
                if modified := mergeStates(dstState, s); !modified {
×
357
                        return nil
×
358
                }
×
359
        } else {
×
360
                dstState = s
×
361
                globalStateStore[fn] = s
×
362
        }
×
UNCOV
363

×
UNCOV
364
        // create directory, if it does not exist
×
365
        dirPath := filepath.Dir(fn)
366
        if _, err := os.Stat(dirPath); errors.Is(err, os.ErrNotExist) {
367
                if err := os.MkdirAll(dirPath, 0o775); err != nil {
×
368
                        return err
×
369
                }
×
UNCOV
370
        }
×
UNCOV
371

×
372
        file, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o664)
373
        if err != nil {
374
                return err
×
375
        }
×
376
        defer file.Close()
×
377

×
378
        dec := json.NewEncoder(file)
×
379
        dec.SetIndent("", "\t")
×
380
        if err := dec.Encode(dstState); err != nil {
×
381
                return err
×
382
        }
×
383
        return nil
×
UNCOV
384
}
×
UNCOV
385

×
386
type testdataState struct {
387
        Accounts     map[common.Address]*account    `json:"accounts"`
388
        HeaderHashes map[hexutil.Uint64]common.Hash `json:"headerHashes,omitempty"`
389
}
390

391
type account struct {
392
        Nonce   hexutil.Uint64                    `json:"nonce"`
393
        Balance *hexutil.U256                     `json:"balance"`
394
        Code    hexutil.Bytes                     `json:"code"`
395
        Storage map[w3hexutil.Hash]w3hexutil.Hash `json:"storage,omitempty"`
396
}
397

398
// mergeStates merges the source state into the destination state and returns
399
// whether the destination state has been modified.
400
func mergeStates(dst, src *testdataState) (modified bool) {
401
        // merge accounts
402
        for addr, acc := range src.Accounts {
×
403
                if dstAcc, ok := dst.Accounts[addr]; !ok {
×
404
                        dst.Accounts[addr] = acc
×
405
                        modified = true
×
406
                } else {
×
407
                        if dstAcc.Storage == nil {
×
408
                                dstAcc.Storage = make(map[w3hexutil.Hash]w3hexutil.Hash)
×
409
                        }
×
UNCOV
410

×
411
                        for slot, storageVal := range acc.Storage {
×
412
                                if _, ok := dstAcc.Storage[slot]; !ok {
413
                                        dstAcc.Storage[slot] = storageVal
×
414
                                        modified = true
×
415
                                }
×
UNCOV
416
                        }
×
417
                        dst.Accounts[addr] = dstAcc
×
418
                }
UNCOV
419
        }
×
420

421
        // merge header hashes
422
        for blockNumber, hash := range src.HeaderHashes {
423
                if _, ok := dst.HeaderHashes[blockNumber]; !ok {
424
                        dst.HeaderHashes[blockNumber] = hash
×
425
                        modified = true
×
426
                }
×
UNCOV
427
        }
×
UNCOV
428

×
429
        return modified
430
}
UNCOV
431

×
432
type storageKey struct {
433
        addr common.Address
434
        slot common.Hash
435
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc