-
Notifications
You must be signed in to change notification settings - Fork 598
Expand file tree
/
Copy pathapi_backend_test.go
More file actions
468 lines (407 loc) · 15.3 KB
/
Copy pathapi_backend_test.go
File metadata and controls
468 lines (407 loc) · 15.3 KB
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"context"
"crypto/ecdsa"
"errors"
"math"
"math/big"
"testing"
"time"
"github.com/holiman/uint256"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/core/txpool/locals"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/ethconfig"
"github.com/ethereum/go-ethereum/eth/relay"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stretchr/testify/require"
)
var (
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
address = crypto.PubkeyToAddress(key.PublicKey)
funds = big.NewInt(10_000_000_000_000_000)
gspec = &core.Genesis{
Config: params.MergedTestChainConfig,
Alloc: types.GenesisAlloc{
address: {Balance: funds},
},
Difficulty: common.Big0,
BaseFee: big.NewInt(params.InitialBaseFee),
}
signer = types.LatestSignerForChainID(gspec.Config.ChainID)
)
func initBackend(withLocal bool) *EthAPIBackend {
var (
// Create a database pre-initialize with a genesis block
db = rawdb.NewMemoryDatabase()
engine = beacon.New(ethash.NewFaker())
)
chain, _ := core.NewBlockChain(db, gspec, engine, nil)
txconfig := legacypool.DefaultConfig
txconfig.Journal = "" // Don't litter the disk with test journals
blobPool := blobpool.New(blobpool.Config{Datadir: ""}, chain, nil)
legacyPool := legacypool.New(txconfig, chain)
txpool, _ := txpool.New(txconfig.PriceLimit, chain, []txpool.SubPool{legacyPool, blobPool})
ethConfig := ðconfig.Config{
WitnessAPIEnabled: true, // Enable for testing
}
eth := &Ethereum{
blockchain: chain,
txPool: txpool,
config: ethConfig,
}
if withLocal {
eth.localTxTracker = locals.New("", time.Minute, gspec.Config, txpool)
}
return &EthAPIBackend{
eth: eth,
}
}
func makeTx(nonce uint64, gasPrice *big.Int, amount *big.Int, key *ecdsa.PrivateKey) *types.Transaction {
if gasPrice == nil {
gasPrice = big.NewInt(25 * params.GWei)
}
if amount == nil {
amount = big.NewInt(1000)
}
tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{0x00}, amount, params.TxGas, gasPrice, nil), signer, key)
return tx
}
type unsignedAuth struct {
nonce uint64
key *ecdsa.PrivateKey
}
func pricedSetCodeTx(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, unsigned []unsignedAuth) *types.Transaction {
var authList []types.SetCodeAuthorization
for _, u := range unsigned {
auth, _ := types.SignSetCode(u.key, types.SetCodeAuthorization{
ChainID: *uint256.MustFromBig(gspec.Config.ChainID),
Address: common.Address{0x42},
Nonce: u.nonce,
})
authList = append(authList, auth)
}
return pricedSetCodeTxWithAuth(nonce, gaslimit, gasFee, tip, key, authList)
}
func pricedSetCodeTxWithAuth(nonce uint64, gaslimit uint64, gasFee, tip *uint256.Int, key *ecdsa.PrivateKey, authList []types.SetCodeAuthorization) *types.Transaction {
return types.MustSignNewTx(key, signer, &types.SetCodeTx{
ChainID: uint256.MustFromBig(gspec.Config.ChainID),
Nonce: nonce,
GasTipCap: tip,
GasFeeCap: gasFee,
Gas: gaslimit,
To: common.Address{},
Value: uint256.NewInt(100),
Data: nil,
AccessList: nil,
AuthList: authList,
})
}
func TestSendTx(t *testing.T) {
testSendTx(t, false)
testSendTx(t, true)
}
func TestSendTxEIP2681(t *testing.T) {
b := initBackend(false)
// Test EIP-2681: nonce overflow should be rejected
tx := makeTx(uint64(math.MaxUint64), nil, nil, key) // max uint64 nonce
err := b.SendTx(t.Context(), tx)
if err == nil {
t.Fatal("Expected EIP-2681 nonce overflow error, but transaction was accepted")
}
if !errors.Is(err, core.ErrNonceMax) {
t.Errorf("Expected core.ErrNonceMax, got: %v", err)
}
// Test normal case: should succeed
normalTx := makeTx(0, nil, nil, key)
err = b.SendTx(t.Context(), normalTx)
if err != nil {
t.Errorf("Normal transaction should succeed, got error: %v", err)
}
}
func testSendTx(t *testing.T, withLocal bool) {
b := initBackend(withLocal)
txA := pricedSetCodeTx(0, 250000, uint256.NewInt(25*params.GWei), uint256.NewInt(25*params.GWei), key, []unsignedAuth{
{
nonce: 0,
key: key,
},
})
b.SendTx(t.Context(), txA)
txB := makeTx(1, nil, nil, key)
err := b.SendTx(t.Context(), txB)
if withLocal {
if err != nil {
t.Fatalf("Unexpected error sending tx: %v", err)
}
} else {
if !errors.Is(err, txpool.ErrInflightTxLimitReached) {
t.Fatalf("Unexpected error, want: %v, got: %v", txpool.ErrInflightTxLimitReached, err)
}
}
}
func TestWitnessCacheUsageInAPIBackend(t *testing.T) {
b := initBackend(false)
defer b.eth.blockchain.Stop()
// Get genesis block to use for testing
genesisBlock := b.eth.blockchain.CurrentBlock()
blockHash := genesisBlock.Hash()
blockNumber := genesisBlock.Number.Uint64()
// Create a simple witness for testing (using empty witness struct)
// We just need to test that the API methods call blockchain.GetWitness/WriteWitness
witness := &stateless.Witness{}
ctx := context.Background()
// First, manually store a witness using blockchain.WriteWitness so we have something to retrieve
// This tests that the APIs work with the cache
testWitnessBytes := []byte("test witness RLP bytes")
b.eth.blockchain.WriteWitness(blockHash, testWitnessBytes)
// Test StoreWitness - this uses blockchain.WriteWitness() [Line 640 in api_backend.go]
err := b.StoreWitness(ctx, blockHash, witness)
if err != nil {
t.Fatalf("StoreWitness failed: %v", err)
}
// Verify witness was stored and cached
if !b.eth.blockchain.HasWitness(blockHash) {
t.Fatal("Witness was not stored")
}
// Now store it again using the API method to cover the StoreWitness line
// This overwrites with properly encoded witness
err = b.StoreWitness(ctx, blockHash, witness)
if err != nil {
t.Fatalf("Second StoreWitness failed: %v", err)
}
// Test WitnessByHash - this uses blockchain.GetWitness() [Line 684 in api_backend.go]
// This will attempt to decode, so we expect it might fail with invalid test data
// But the important part is that blockchain.GetWitness() was called (which is covered)
_, err = b.WitnessByHash(ctx, blockHash)
if err != nil {
// Expected - our test witness may not be fully valid, but the GetWitness call was made
t.Logf("WitnessByHash returned error (expected for test data): %v", err)
}
// We successfully called the method, covering line 684
// Test WitnessByNumber - this uses blockchain.GetWitness() [Line 658 in api_backend.go]
_, err = b.WitnessByNumber(ctx, rpc.BlockNumber(blockNumber))
if err != nil {
t.Logf("WitnessByNumber returned error (expected for test data): %v", err)
}
// We successfully called the method, covering line 658
// Test GetWitnesses (batch) - this uses blockchain.GetWitness() [Line 618 in api_backend.go]
// First test with 0 blocks to cover the early return path
emptyWitnesses, err := b.GetWitnesses(ctx, blockNumber, 0)
if err != nil {
t.Fatalf("GetWitnesses with 0 blocks failed: %v", err)
}
if len(emptyWitnesses) != 0 {
t.Fatalf("Expected 0 witnesses, got %d", len(emptyWitnesses))
}
// Now test with 1 block - this will call blockchain.GetWitness() and attempt to decode
_, err = b.GetWitnesses(ctx, blockNumber, 1)
if err != nil {
// Expected - witness decode will fail, but GetWitness was called (line 618)
t.Logf("GetWitnesses returned error (expected for test data): %v", err)
}
// We successfully called the method, covering line 618
// Test with non-existent block hash
nonExistentHash := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000")
witnessNil, err := b.WitnessByHash(ctx, nonExistentHash)
if err != nil {
t.Fatalf("WitnessByHash with non-existent hash should not error: %v", err)
}
if witnessNil != nil {
t.Fatal("WitnessByHash should return nil for non-existent block")
}
// Test HasWitness with cached witness (cache hit)
if !b.eth.blockchain.HasWitness(blockHash) {
t.Fatal("HasWitness should return true for cached witness")
}
// Clear cache and test cache miss path
b.eth.blockchain.SetHead(blockNumber) // This triggers cache purge
// After purge, WitnessByHash should still work (reads from DB and re-caches)
_, err = b.WitnessByHash(ctx, blockHash)
if err != nil {
t.Logf("WitnessByHash after cache purge returned error (expected for test data): %v", err)
}
// The important part is that blockchain.GetWitness() was called again after cache purge
// demonstrating the cache miss -> DB read -> re-cache flow
}
// TestRelayMethodWiring verifies that EthAPIBackend relay methods correctly
// delegate to the underlying RelayService.
func TestRelayMethodWiring(t *testing.T) {
t.Parallel()
t.Run("all flags enabled", func(t *testing.T) {
t.Parallel()
rs := relay.Init(true, true, true, true, nil)
defer rs.Close()
b := &EthAPIBackend{relay: rs}
require.True(t, b.PreconfEnabled())
require.True(t, b.PrivateTxEnabled())
require.True(t, b.AcceptPreconfTxs())
require.True(t, b.AcceptPrivateTxs())
})
t.Run("all flags disabled", func(t *testing.T) {
t.Parallel()
rs := relay.Init(false, false, false, false, nil)
defer rs.Close()
b := &EthAPIBackend{relay: rs}
require.False(t, b.PreconfEnabled())
require.False(t, b.PrivateTxEnabled())
require.False(t, b.AcceptPreconfTxs())
require.False(t, b.AcceptPrivateTxs())
})
t.Run("submit and check methods reach relay", func(t *testing.T) {
t.Parallel()
// Init with enablePreconf=true, enablePrivateTx=true but no URLs
// → txRelay created with nil multiclient → methods return errors, proving wiring works
rs := relay.Init(true, true, false, false, nil)
defer rs.Close()
b := &EthAPIBackend{relay: rs}
tx := types.NewTransaction(0, common.Address{}, nil, 0, nil, nil)
err := b.SubmitTxForPreconf(tx)
require.Error(t, err, "SubmitTxForPreconf should return error with nil multiclient")
_, err = b.CheckPreconfStatus(common.Hash{})
require.Error(t, err, "CheckPreconfStatus should return error with nil multiclient")
err = b.SubmitPrivateTx(tx)
require.Error(t, err, "SubmitPrivateTx should return error with nil multiclient")
})
t.Run("record and purge private tx do not panic", func(t *testing.T) {
t.Parallel()
rs := relay.Init(false, false, false, true, nil) // creates privateTxStore
defer rs.Close()
b := &EthAPIBackend{relay: rs}
hash := common.HexToHash("0xabc")
require.NotPanics(t, func() { b.RecordPrivateTx(hash) })
require.NotPanics(t, func() { b.PurgePrivateTx(hash) })
})
}
func TestBaseFee(t *testing.T) {
t.Run("LondonActive", func(t *testing.T) {
b := initBackend(false)
defer b.eth.blockchain.Stop()
if b.BaseFee(t.Context()) == nil {
t.Fatal("expected non-nil BaseFee when London is active")
}
})
t.Run("PreLondon", func(t *testing.T) {
// NonActivatedConfig has no LondonBlock — IsLondon always returns false.
db := rawdb.NewMemoryDatabase()
genesis := &core.Genesis{
Config: params.NonActivatedConfig,
Difficulty: big.NewInt(1),
}
chain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil)
require.NoError(t, err)
defer chain.Stop()
b := &EthAPIBackend{eth: &Ethereum{blockchain: chain}}
if b.BaseFee(t.Context()) != nil {
t.Fatal("expected nil BaseFee when London is inactive")
}
})
}
func TestBlobBaseFee(t *testing.T) {
t.Run("CancunActive", func(t *testing.T) {
// MergedTestChainConfig has Cancun at block 0 with a BlobScheduleConfig.
// Genesis sets ExcessBlobGas = new(uint64) so the guard passes.
b := initBackend(false)
defer b.eth.blockchain.Stop()
if b.BlobBaseFee(t.Context()) == nil {
t.Fatal("expected non-nil BlobBaseFee when Cancun is active")
}
})
t.Run("NoExcessBlobGas", func(t *testing.T) {
// AllEthashProtocolChanges has London at block 0 but no CancunBlock.
// Genesis therefore leaves ExcessBlobGas nil, so BlobBaseFee must return nil.
db := rawdb.NewMemoryDatabase()
genesis := &core.Genesis{
Config: params.AllEthashProtocolChanges,
BaseFee: big.NewInt(params.InitialBaseFee),
Difficulty: big.NewInt(1),
}
chain, err := core.NewBlockChain(db, genesis, ethash.NewFaker(), nil)
require.NoError(t, err)
defer chain.Stop()
b := &EthAPIBackend{eth: &Ethereum{blockchain: chain}}
if b.BlobBaseFee(t.Context()) != nil {
t.Fatal("expected nil BlobBaseFee when ExcessBlobGas is nil")
}
})
t.Run("NoBlobScheduleConfig", func(t *testing.T) {
// Cancun active but BlobScheduleConfig nil: ExcessBlobGas is set by genesis
// (not nil), but the guard short-circuits before calling CalcBlobFee.
db := rawdb.NewMemoryDatabase()
config := *params.MergedTestChainConfig
config.BlobScheduleConfig = nil
genesis := &core.Genesis{
Config: &config,
Difficulty: common.Big0,
BaseFee: big.NewInt(params.InitialBaseFee),
}
chain, err := core.NewBlockChain(db, genesis, beacon.New(ethash.NewFaker()), nil)
require.NoError(t, err)
defer chain.Stop()
b := &EthAPIBackend{eth: &Ethereum{blockchain: chain}}
if b.BlobBaseFee(t.Context()) != nil {
t.Fatal("expected nil BlobBaseFee when BlobScheduleConfig is nil")
}
})
}
// TestRelayGracefulShutdownOnStop verifies that the relay shutdown path in
// Ethereum.Stop() completes promptly and doesn't hang or panic.
func TestRelayGracefulShutdownOnStop(t *testing.T) {
t.Parallel()
t.Run("nil relay does not panic", func(t *testing.T) {
t.Parallel()
b := &EthAPIBackend{}
require.NotPanics(t, func() {
if b.relay != nil {
b.relay.Close()
}
})
})
t.Run("close completes promptly with active service", func(t *testing.T) {
t.Parallel()
// enablePreconf=true creates a Service with background goroutines
// (processPreconfTasks, cleanup). Close() must signal them and wait.
rs := relay.Init(true, true, true, true, nil)
b := &EthAPIBackend{relay: rs}
require.True(t, b.PreconfEnabled(), "relay should be operational before shutdown")
done := make(chan struct{})
go func() {
// This is the exact call from Ethereum.Stop()
if b.relay != nil {
b.relay.Close()
}
close(done)
}()
select {
case <-done:
// Close completed — goroutines shut down cleanly
case <-time.After(5 * time.Second):
t.Fatal("relay.Close() did not complete within 5s, likely a goroutine leak or deadlock")
}
})
}