Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/transaction/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Geth interface {
CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
ChainID(ctx context.Context) (*big.Int, error)
Close()
EstimateGasAtBlock(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (uint64, error)
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error)
HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)
NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error)
Expand Down
42 changes: 22 additions & 20 deletions pkg/transaction/backendmock/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import (
"github.com/ethersphere/bee/v2/pkg/transaction"
)

var ErrNotImplemented = errors.New("not implemented")

type backendMock struct {
callContract func(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
sendTransaction func(ctx context.Context, tx *types.Transaction) error
suggestedFeeAndTip func(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error)
suggestGasTipCap func(ctx context.Context) (*big.Int, error)
estimateGasAtBlock func(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (gas uint64, err error)
estimateGas func(ctx context.Context, msg ethereum.CallMsg) (gas uint64, err error)
transactionReceipt func(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
pendingNonceAt func(ctx context.Context, account common.Address) (uint64, error)
transactionByHash func(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error)
Expand All @@ -34,92 +36,92 @@ func (m *backendMock) CallContract(ctx context.Context, call ethereum.CallMsg, b
if m.callContract != nil {
return m.callContract(ctx, call, blockNumber)
}
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *backendMock) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
if m.pendingNonceAt != nil {
return m.pendingNonceAt(ctx, account)
}
return 0, errors.New("not implemented")
return 0, ErrNotImplemented
}

func (m *backendMock) SuggestedFeeAndTip(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error) {
if m.suggestedFeeAndTip != nil {
return m.suggestedFeeAndTip(ctx, gasPrice, boostPercent)
}
return nil, nil, errors.New("not implemented")
return nil, nil, ErrNotImplemented
}

func (m *backendMock) EstimateGasAtBlock(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (uint64, error) {
if m.estimateGasAtBlock != nil {
return m.estimateGasAtBlock(ctx, msg, blockNumber)
func (m *backendMock) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
if m.estimateGas != nil {
return m.estimateGas(ctx, msg)
}
return 0, errors.New("not implemented")
return 0, ErrNotImplemented
}

func (m *backendMock) SendTransaction(ctx context.Context, tx *types.Transaction) error {
if m.sendTransaction != nil {
return m.sendTransaction(ctx, tx)
}
return errors.New("not implemented")
return ErrNotImplemented
}

func (*backendMock) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *backendMock) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
if m.transactionReceipt != nil {
return m.transactionReceipt(ctx, txHash)
}
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *backendMock) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
if m.transactionByHash != nil {
return m.transactionByHash(ctx, hash)
}
return nil, false, errors.New("not implemented")
return nil, false, ErrNotImplemented
}

func (m *backendMock) BlockNumber(ctx context.Context) (uint64, error) {
if m.blockNumber != nil {
return m.blockNumber(ctx)
}
return 0, errors.New("not implemented")
return 0, ErrNotImplemented
}

func (m *backendMock) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
if m.headerByNumber != nil {
return m.headerByNumber(ctx, number)
}
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *backendMock) BalanceAt(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) {
if m.balanceAt != nil {
return m.balanceAt(ctx, address, block)
}
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *backendMock) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
if m.nonceAt != nil {
return m.nonceAt(ctx, account, blockNumber)
}
return 0, errors.New("not implemented")
return 0, ErrNotImplemented
}

func (m *backendMock) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
if m.suggestGasTipCap != nil {
return m.suggestGasTipCap(ctx)
}
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *backendMock) ChainID(ctx context.Context) (*big.Int, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *backendMock) Close() {}
Expand Down Expand Up @@ -171,9 +173,9 @@ func WithSuggestGasTipCapFunc(f func(ctx context.Context) (*big.Int, error)) Opt
})
}

func WithEstimateGasAtBlockFunc(f func(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (gas uint64, err error)) Option {
func WithEstimateGasFunc(f func(ctx context.Context, msg ethereum.CallMsg) (gas uint64, err error)) Option {
return optionFunc(func(s *backendMock) {
s.estimateGasAtBlock = f
s.estimateGas = f
})
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/transaction/backendnoop/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (b *Backend) SuggestGasTipCap(context.Context) (*big.Int, error) {
return nil, postagecontract.ErrChainDisabled
}

func (b *Backend) EstimateGasAtBlock(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (uint64, error) {
func (b *Backend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
return 0, postagecontract.ErrChainDisabled
}

Expand Down
26 changes: 14 additions & 12 deletions pkg/transaction/backendsimulation/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/ethersphere/bee/v2/pkg/transaction"
)

var ErrNotImplemented = errors.New("not implemented")

type AccountAtKey struct {
BlockNumber uint64
Account common.Address
Expand Down Expand Up @@ -84,27 +86,27 @@ func (m *simulatedBackend) advanceBlock() {
}

func (*simulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *simulatedBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) {
return 0, errors.New("not implemented")
return 0, ErrNotImplemented
}

func (m *simulatedBackend) SuggestedFeeAndTip(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error) {
return nil, nil, errors.New("not implemented")
return nil, nil, ErrNotImplemented
}

func (m *simulatedBackend) EstimateGasAtBlock(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) (uint64, error) {
return 0, errors.New("not implemented")
func (m *simulatedBackend) EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error) {
return 0, ErrNotImplemented
}

func (m *simulatedBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error {
return errors.New("not implemented")
return ErrNotImplemented
}

func (*simulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *simulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
Expand All @@ -117,7 +119,7 @@ func (m *simulatedBackend) TransactionReceipt(ctx context.Context, txHash common
}

func (m *simulatedBackend) TransactionByHash(ctx context.Context, hash common.Hash) (tx *types.Transaction, isPending bool, err error) {
return nil, false, errors.New("not implemented")
return nil, false, ErrNotImplemented
}

func (m *simulatedBackend) BlockNumber(ctx context.Context) (uint64, error) {
Expand All @@ -126,11 +128,11 @@ func (m *simulatedBackend) BlockNumber(ctx context.Context) (uint64, error) {
}

func (m *simulatedBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *simulatedBackend) BalanceAt(ctx context.Context, address common.Address, block *big.Int) (*big.Int, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *simulatedBackend) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) {
Expand All @@ -143,11 +145,11 @@ func (m *simulatedBackend) NonceAt(ctx context.Context, account common.Address,
}

func (m *simulatedBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *simulatedBackend) ChainID(ctx context.Context) (*big.Int, error) {
return nil, errors.New("not implemented")
return nil, ErrNotImplemented
}

func (m *simulatedBackend) Close() {}
71 changes: 59 additions & 12 deletions pkg/transaction/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ var (
)

const (
DefaultGasLimit = 1_000_000
DefaultGasLimit = 1_000_000 // Used for contract operations when setGasLimit flag is enabled
DefaultTipBoostPercent = 25
MaxGasLimit = 10_000_000 // Maximum allowed gas limit to prevent excessive values
MinGasLimit = 21_000 // Minimum gas for any transaction
GasBufferPercent = 33 // Add 33% buffer to estimated gas
FallbackGasLimit = 500_000 // Fallback when estimation fails and no minimum is set
)

// TxRequest describes a request for a transaction that can be executed.
Expand Down Expand Up @@ -273,22 +277,55 @@ func (t *transactionService) StoredTransaction(txHash common.Hash) (*StoredTrans
func (t *transactionService) prepareTransaction(ctx context.Context, request *TxRequest, nonce uint64, boostPercent int) (tx *types.Transaction, err error) {
var gasLimit uint64
if request.GasLimit == 0 {
gasLimit, err = t.backend.EstimateGasAtBlock(ctx, ethereum.CallMsg{
From: t.sender,
To: request.To,
Data: request.Data,
}, nil) // nil for latest block
// Estimate gas using pending state for consistency with PendingNonceAt
gasLimit, err = t.backend.EstimateGas(ctx, ethereum.CallMsg{
From: t.sender,
To: request.To,
Data: request.Data,
Value: request.Value,
})

if err != nil {
t.logger.Debug("estimate gas failed", "error", err)
gasLimit = request.MinEstimatedGasLimit
t.logger.Warning("gas estimation failed, using fallback",
"error", err,
"description", request.Description,
)

if request.MinEstimatedGasLimit > 0 {
gasLimit = request.MinEstimatedGasLimit
} else if len(request.Data) > 0 {
// Contract call - use reasonable fallback
gasLimit = FallbackGasLimit
} else {
// Simple transfer - use minimum
gasLimit = MinGasLimit
}
} else {
// Estimation succeeded - add buffer for state changes
gasLimit += gasLimit * GasBufferPercent / 100

// Apply minimum if specified
if gasLimit < request.MinEstimatedGasLimit {
gasLimit = request.MinEstimatedGasLimit
}

// Cap at maximum
if gasLimit > MaxGasLimit {
gasLimit = MaxGasLimit
}
}

gasLimit += gasLimit / 2 // add 50% buffer to the estimated gas limit
if gasLimit < request.MinEstimatedGasLimit {
gasLimit = request.MinEstimatedGasLimit
// Ensure absolute minimum
if gasLimit < MinGasLimit {
gasLimit = MinGasLimit
}
} else {
gasLimit = request.GasLimit
// Use provided gas limit with bounds validation
gasLimit = min(max(request.GasLimit, MinGasLimit), MaxGasLimit)
}

if gasLimit == 0 {
return nil, errors.New("gas limit cannot be zero")
}

/*
Expand All @@ -308,6 +345,16 @@ func (t *transactionService) prepareTransaction(ctx context.Context, request *Tx
return nil, err
}

t.logger.Debug("prepared transaction",
"to", request.To,
"value", request.Value,
"gas_limit", gasLimit,
"gas_fee_cap", gasFeeCap,
"gas_tip_cap", gasTipCap,
"nonce", nonce,
"description", request.Description,
)

return types.NewTx(&types.DynamicFeeTx{
Nonce: nonce,
ChainID: t.chainID,
Expand Down
Loading
Loading