Skip to content

Commit e98a057

Browse files
test(wrapped): add unit tests for SuggestedFeeAndTip
1 parent b3dc070 commit e98a057

8 files changed

Lines changed: 173 additions & 33 deletions

File tree

pkg/node/chain.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,4 @@ func (m noOpChainBackend) ChainID(context.Context) (*big.Int, error) {
420420
return big.NewInt(m.chainID), nil
421421
}
422422

423-
func (m noOpChainBackend) Close() error {
424-
return nil
425-
}
423+
func (m noOpChainBackend) Close() {}

pkg/node/node.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ type Bee struct {
106106
pullSyncCloser io.Closer
107107
pssCloser io.Closer
108108
gsocCloser io.Closer
109-
ethClientCloser io.Closer
110109
transactionMonitorCloser io.Closer
111110
transactionCloser io.Closer
112111
listenerCloser io.Closer
@@ -121,6 +120,7 @@ type Bee struct {
121120
shutdownMutex sync.Mutex
122121
syncingStopped *syncutil.Signaler
123122
accesscontrolCloser io.Closer
123+
ethClientCloser func()
124124
}
125125

126126
type Options struct {
@@ -397,7 +397,7 @@ func NewBee(
397397
if err != nil {
398398
return nil, fmt.Errorf("init chain: %w", err)
399399
}
400-
b.ethClientCloser = chainBackend
400+
b.ethClientCloser = chainBackend.Close
401401

402402
logger.Info("using chain with network network", "chain_id", chainID, "network_id", networkID)
403403

@@ -1387,7 +1387,10 @@ func (b *Bee) Shutdown() error {
13871387

13881388
wg.Wait()
13891389

1390-
tryClose(b.ethClientCloser, "eth client")
1390+
if b.ethClientCloser != nil {
1391+
b.ethClientCloser()
1392+
}
1393+
13911394
tryClose(b.accesscontrolCloser, "accesscontrol")
13921395
tryClose(b.tracerCloser, "tracer")
13931396
tryClose(b.topologyCloser, "topology driver")

pkg/transaction/backend.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,13 @@ import (
1414
"github.com/ethereum/go-ethereum/common"
1515
"github.com/ethereum/go-ethereum/core/types"
1616
"github.com/ethersphere/bee/v2/pkg/log"
17+
"github.com/ethersphere/bee/v2/pkg/transaction/backend"
1718
)
1819

1920
// Backend is the minimum of blockchain backend functions we need.
2021
type Backend interface {
21-
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
22-
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
23-
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
24-
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
25-
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
22+
backend.Backend
2623
SuggestedFeeAndTip(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error)
27-
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
28-
SendTransaction(ctx context.Context, tx *types.Transaction) error
29-
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
30-
TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
31-
BlockNumber(ctx context.Context) (uint64, error)
32-
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
33-
BalanceAt(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error)
34-
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
35-
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
36-
ChainID(ctx context.Context) (*big.Int, error)
37-
Close() error
3824
}
3925

4026
// IsSynced will check if we are synced with the given blockchain backend. This

pkg/transaction/backend/backend.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package backend
2+
3+
import (
4+
"context"
5+
"math/big"
6+
7+
"github.com/ethereum/go-ethereum"
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/core/types"
10+
)
11+
12+
// Backend is the interface that an ethclient.Client satisfies.
13+
type Backend interface {
14+
BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error)
15+
BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error)
16+
BlockNumber(ctx context.Context) (uint64, error)
17+
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
18+
ChainID(ctx context.Context) (*big.Int, error)
19+
Close()
20+
CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error)
21+
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
22+
FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
23+
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
24+
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
25+
PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
26+
SendTransaction(ctx context.Context, tx *types.Transaction) error
27+
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
28+
TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
29+
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
30+
}

pkg/transaction/backendmock/backend.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,7 @@ func (m *backendMock) ChainID(ctx context.Context) (*big.Int, error) {
146146
return nil, errors.New("not implemented")
147147
}
148148

149-
func (m *backendMock) Close() error {
150-
return nil
151-
}
149+
func (m *backendMock) Close() {}
152150

153151
func New(opts ...Option) transaction.Backend {
154152
mock := new(backendMock)

pkg/transaction/backendsimulation/backend.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,4 @@ func (m *simulatedBackend) ChainID(ctx context.Context) (*big.Int, error) {
169169
return nil, errors.New("not implemented")
170170
}
171171

172-
func (m *simulatedBackend) Close() error {
173-
return nil
174-
}
172+
func (m *simulatedBackend) Close() {}

pkg/transaction/wrapped/wrapped.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313
"github.com/ethereum/go-ethereum"
1414
"github.com/ethereum/go-ethereum/common"
1515
"github.com/ethereum/go-ethereum/core/types"
16-
"github.com/ethereum/go-ethereum/ethclient"
1716
"github.com/ethersphere/bee/v2/pkg/transaction"
17+
"github.com/ethersphere/bee/v2/pkg/transaction/backend"
1818
)
1919

2020
const (
@@ -28,12 +28,12 @@ var (
2828
)
2929

3030
type wrappedBackend struct {
31-
backend *ethclient.Client
31+
backend backend.Backend
3232
metrics metrics
3333
minimumGasTipCap int64
3434
}
3535

36-
func NewBackend(backend *ethclient.Client, minimumGasTipCap uint64) transaction.Backend {
36+
func NewBackend(backend backend.Backend, minimumGasTipCap uint64) transaction.Backend {
3737
return &wrappedBackend{
3838
backend: backend,
3939
minimumGasTipCap: int64(minimumGasTipCap),
@@ -215,9 +215,8 @@ func (b *wrappedBackend) BlockByNumber(ctx context.Context, number *big.Int) (*t
215215
return block, nil
216216
}
217217

218-
func (b *wrappedBackend) Close() error {
218+
func (b *wrappedBackend) Close() {
219219
b.backend.Close()
220-
return nil
221220
}
222221

223222
// SuggestedFeeAndTip calculates the recommended gasFeeCap and gasTipCap for a transaction.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright 2025 The Swarm Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package wrapped_test
6+
7+
import (
8+
"context"
9+
"errors"
10+
"math/big"
11+
"testing"
12+
13+
"github.com/ethereum/go-ethereum/core/types"
14+
"github.com/ethersphere/bee/v2/pkg/transaction/backendmock"
15+
"github.com/ethersphere/bee/v2/pkg/transaction/wrapped"
16+
"github.com/google/go-cmp/cmp"
17+
)
18+
19+
func TestSuggestedFeeAndTip(t *testing.T) {
20+
t.Parallel()
21+
22+
var (
23+
ctx = context.Background()
24+
minimumGasTipCap = uint64(10)
25+
baseFee = big.NewInt(100)
26+
)
27+
28+
testCases := []struct {
29+
name string
30+
gasPrice *big.Int
31+
boostPercent int
32+
mockSuggestGasTip *big.Int
33+
mockSuggestGasErr error
34+
mockHeader *types.Header
35+
mockHeaderErr error
36+
wantGasFeeCap *big.Int
37+
wantGasTipCap *big.Int
38+
wantErr error
39+
}{
40+
{
41+
name: "with gas price",
42+
gasPrice: big.NewInt(1000),
43+
wantGasFeeCap: big.NewInt(1000),
44+
wantGasTipCap: big.NewInt(1000),
45+
},
46+
{
47+
name: "suggest tip error",
48+
mockSuggestGasErr: errors.New("suggest tip error"),
49+
wantErr: errors.New("failed to suggest gas tip cap: suggest tip error"),
50+
},
51+
{
52+
name: "header error",
53+
mockSuggestGasTip: big.NewInt(20),
54+
mockHeaderErr: errors.New("header error"),
55+
wantErr: errors.New("failed to get latest block header: header error"),
56+
},
57+
{
58+
name: "no base fee",
59+
mockSuggestGasTip: big.NewInt(20),
60+
mockHeader: &types.Header{},
61+
wantErr: wrapped.ErrEIP1559NotSupported,
62+
},
63+
{
64+
name: "suggested tip > minimum",
65+
mockSuggestGasTip: big.NewInt(20),
66+
mockHeader: &types.Header{BaseFee: baseFee},
67+
wantGasFeeCap: big.NewInt(220), // 2*100 + 20
68+
wantGasTipCap: big.NewInt(20),
69+
},
70+
{
71+
name: "suggested tip < minimum",
72+
mockSuggestGasTip: big.NewInt(5),
73+
mockHeader: &types.Header{BaseFee: baseFee},
74+
wantGasFeeCap: big.NewInt(210), // 2*100 + 10
75+
wantGasTipCap: big.NewInt(10),
76+
},
77+
{
78+
name: "with boost",
79+
boostPercent: 10,
80+
mockSuggestGasTip: big.NewInt(20),
81+
mockHeader: &types.Header{BaseFee: baseFee},
82+
wantGasFeeCap: big.NewInt(222), // 2*100 + 22
83+
wantGasTipCap: big.NewInt(22), // 20 * 1.1
84+
},
85+
}
86+
87+
for _, tc := range testCases {
88+
t.Run(tc.name, func(t *testing.T) {
89+
t.Parallel()
90+
91+
backend := wrapped.NewBackend(
92+
backendmock.New(
93+
backendmock.WithSuggestGasTipCapFunc(func(ctx context.Context) (*big.Int, error) {
94+
return tc.mockSuggestGasTip, tc.mockSuggestGasErr
95+
}),
96+
backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) {
97+
return tc.mockHeader, tc.mockHeaderErr
98+
}),
99+
),
100+
minimumGasTipCap,
101+
)
102+
103+
gasFeeCap, gasTipCap, err := backend.SuggestedFeeAndTip(ctx, tc.gasPrice, tc.boostPercent)
104+
105+
if tc.wantErr != nil {
106+
if err == nil {
107+
t.Fatal("expected error but got none")
108+
}
109+
if err.Error() != tc.wantErr.Error() {
110+
t.Fatalf("unexpected error. want %v, got %v", tc.wantErr, err)
111+
}
112+
return
113+
}
114+
115+
if err != nil {
116+
t.Fatalf("unexpected error: %v", err)
117+
}
118+
119+
if diff := cmp.Diff(tc.wantGasFeeCap.String(), gasFeeCap.String()); diff != "" {
120+
t.Errorf("gasFeeCap mismatch (-want +got):\n%s", diff)
121+
}
122+
123+
if diff := cmp.Diff(tc.wantGasTipCap.String(), gasTipCap.String()); diff != "" {
124+
t.Errorf("gasTipCap mismatch (-want +got):\n%s", diff)
125+
}
126+
})
127+
}
128+
}

0 commit comments

Comments
 (0)