Skip to content
Draft
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
46 changes: 31 additions & 15 deletions lib/utils/seth/seth.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package seth

import (
"errors"
"fmt"
"regexp"
"strings"

"github.com/pkg/errors"
"github.com/rs/zerolog"

pkg_seth "github.com/smartcontractkit/chainlink-testing-framework/seth"
Expand Down Expand Up @@ -112,27 +112,29 @@ func GetChainClient(c config.SethConfig, network blockchain.EVMNetwork) (*pkg_se
func GetChainClientWithConfigFunction(c config.SethConfig, network blockchain.EVMNetwork, configFn ConfigFunction) (*pkg_seth.Client, error) {
readSethCfg := c.GetSethConfig()
if readSethCfg == nil {
return nil, errors.New("Seth config not found")
return nil, fmt.Errorf("Seth config not found in the provided configuration. " +
"Ensure your TOML config file has a [Seth] section with required settings. " +
"See example: https://github.com/smartcontractkit/chainlink-testing-framework/blob/main/seth/seth.toml")
}

sethCfg, err := MergeSethAndEvmNetworkConfigs(network, *readSethCfg)
if err != nil {
return nil, errors.Wrapf(err, "Error merging seth and evm network configs")
return nil, fmt.Errorf("error merging seth and evm network configs: %w", err)
}

err = configFn(&sethCfg)
if err != nil {
return nil, errors.Wrapf(err, "Error applying seth config function")
return nil, fmt.Errorf("error applying seth config function: %w", err)
}

err = ValidateSethNetworkConfig(sethCfg.Network)
if err != nil {
return nil, errors.Wrapf(err, "Error validating seth network config")
return nil, fmt.Errorf("error validating seth network config: %w", err)
}

chainClient, err := pkg_seth.NewClientWithConfig(&sethCfg)
if err != nil {
return nil, errors.Wrapf(err, "Error creating seth client")
return nil, fmt.Errorf("error creating seth client: %w", err)
}

return chainClient, nil
Expand Down Expand Up @@ -255,33 +257,47 @@ func MustReplaceSimulatedNetworkUrlWithK8(l zerolog.Logger, network blockchain.E
// ValidateSethNetworkConfig validates the Seth network config
func ValidateSethNetworkConfig(cfg *pkg_seth.Network) error {
if cfg == nil {
return errors.New("network cannot be nil")
return fmt.Errorf("network configuration cannot be nil. " +
"Ensure your Seth config has properly configured network settings")
}
if len(cfg.URLs) == 0 {
return errors.New("URLs are required")
return fmt.Errorf("network URLs are required. " +
"Add RPC endpoint URLs in the 'urls_secret' field of your network config")
}
if len(cfg.PrivateKeys) == 0 {
return errors.New("PrivateKeys are required")
return fmt.Errorf("private keys are required. " +
"Add at least one private key in 'private_keys_secret' or via environment variables")
}
if cfg.TransferGasFee == 0 {
return errors.New("TransferGasFee needs to be above 0. It's the gas fee for a simple transfer transaction")
return fmt.Errorf("transfer_gas_fee must be greater than 0. " +
"This is the gas fee for a simple transfer transaction. " +
"Set 'transfer_gas_fee' in your network config")
}
if cfg.TxnTimeout.Duration() == 0 {
return errors.New("TxnTimeout needs to be above 0. It's the timeout for a transaction")
return fmt.Errorf("transaction timeout must be greater than 0. " +
"Set 'txn_timeout' in your network config (e.g., '30s', '1m')")
}
if cfg.EIP1559DynamicFees {
if cfg.GasFeeCap == 0 {
return errors.New("GasFeeCap needs to be above 0. It's the maximum fee per gas for a transaction (including tip)")
return fmt.Errorf("gas_fee_cap must be greater than 0 for EIP-1559 transactions. " +
"This is the maximum fee per gas (base fee + tip). " +
"Set 'gas_fee_cap' in your network config")
}
if cfg.GasTipCap == 0 {
return errors.New("GasTipCap needs to be above 0. It's the maximum tip per gas for a transaction")
return fmt.Errorf("gas_tip_cap must be greater than 0 for EIP-1559 transactions. " +
"This is the maximum priority fee per gas. " +
"Set 'gas_tip_cap' in your network config")
}
if cfg.GasFeeCap <= cfg.GasTipCap {
return errors.New("GasFeeCap needs to be above GasTipCap (as it is base fee + tip cap)")
return fmt.Errorf("gas_fee_cap (%d) must be greater than gas_tip_cap (%d). "+
"Fee cap should be base fee + tip cap. "+
"Adjust your network config accordingly",
cfg.GasFeeCap, cfg.GasTipCap)
}
} else {
if cfg.GasPrice == 0 {
return errors.New("GasPrice needs to be above 0. It's the price of gas for a transaction")
return fmt.Errorf("gas_price must be greater than 0 for legacy transactions. " +
"Set 'gas_price' in your network config")
}
}

Expand Down
1 change: 1 addition & 0 deletions seth/.changeset/v1.51.4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Reviewed error messages, made them easier to understand and more actionable. Removed "github.com/pkg/error" usage.
1 change: 1 addition & 0 deletions seth/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
golangci-lint 1.64.5
31 changes: 27 additions & 4 deletions seth/abi_finder.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package seth

import (
"fmt"
"strings"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/pkg/errors"
)

type ABIFinder struct {
Expand Down Expand Up @@ -46,11 +46,21 @@ func (a *ABIFinder) FindABIByMethod(address string, signature []byte) (ABIFinder
contractName := a.ContractMap.GetContractName(address)
abiInstanceCandidate, ok := a.ContractStore.ABIs[contractName+".abi"]
if !ok {
err := errors.New(ErrNoAbiFound)
err := fmt.Errorf("no ABI found for contract '%s' at address %s, even though it's registered in the contract map. "+
"This happens when:\n"+
" 1. Contract address is in the contract map but ABI file is missing from abi_dir\n"+
" 2. ABI files were moved or deleted after contract deployment\n"+
" 3. Contract map is corrupted or out of sync\n"+
"Troubleshooting:\n"+
" 1. Verify ABI file '%s.abi' exists in the configured abi_dir\n"+
" 2. Check if save_deployed_contracts_map = true in config\n"+
" 3. Re-deploy the contract or manually add ABI with ContractStore.AddABI()\n"+
" 4. For external contracts, obtain and add the ABI manually",
contractName, address, contractName)
L.Err(err).
Str("Contract", contractName).
Str("Address", address).
Msg("ABI not found, even though contract is known. This should not happen. Contract map might be corrupted")
Msg("ABI not found for known contract")
return ABIFinderResult{}, err
}

Expand Down Expand Up @@ -127,7 +137,20 @@ func (a *ABIFinder) FindABIByMethod(address string, signature []byte) (ABIFinder
}

if result.Method == nil {
return ABIFinderResult{}, errors.New(ErrNoABIMethod)
return ABIFinderResult{}, fmt.Errorf("no ABI found with method signature %s for contract at address %s.\n"+
"Checked %d ABIs but none matched.\n"+
"Possible causes:\n"+
" 1. Contract ABI not loaded (check abi_dir and contract_map_file)\n"+
" 2. Method signature doesn't match any function in loaded ABIs\n"+
" 3. Contract address not registered in contract map\n"+
" 4. Wrong contract address (check deployment logs)\n"+
"Troubleshooting:\n"+
" 1. Verify contract was deployed with DeployContract() or loaded with LoadContract()\n"+
" 2. Check the method signature is correct (case-sensitive, including parameter types)\n"+
" 3. Ensure ABI file exists in the directory specified by 'abi_dir'\n"+
" 4. Review contract_map_file for address-to-name mappings\n"+
" 5. Use ContractStore.AddABI() to manually add the ABI",
stringSignature, address, len(a.ContractStore.ABIs))
}

return result, nil
Expand Down
24 changes: 21 additions & 3 deletions seth/block_stats.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,14 @@ func (cs *BlockStats) Stats(startBlock *big.Int, endBlock *big.Int) error {
if endBlock == nil || startBlock.Sign() < 0 {
header, err := cs.Client.Client.HeaderByNumber(context.Background(), nil)
if err != nil {
return fmt.Errorf("failed to get the latest block header: %v", err)
return fmt.Errorf("failed to get the latest block header for block stats: %w\n"+
"This indicates RPC connectivity issues.\n"+
"Troubleshooting:\n"+
" 1. Verify RPC endpoint is accessible\n"+
" 2. Check network connectivity\n"+
" 3. Ensure the node is synced\n"+
" 4. Try increasing dial_timeout in config",
err)
}
latestBlockNumber = header.Number
}
Expand All @@ -62,7 +69,13 @@ func (cs *BlockStats) Stats(startBlock *big.Int, endBlock *big.Int) error {
endBlock = latestBlockNumber
}
if endBlock != nil && startBlock.Int64() > endBlock.Int64() {
return fmt.Errorf("start block is less than the end block")
return fmt.Errorf("invalid block range for statistics: start block %d > end block %d.\n"+
"This is a bug in Seth's block stats calculation logic.\n"+
"Please open a GitHub issue at https://github.com/smartcontractkit/chainlink-testing-framework/issues with:\n"+
" 1. Your configuration file\n"+
" 2. The operation you were performing\n"+
" 3. Network name and chain ID",
startBlock.Int64(), endBlock.Int64())
}
L.Info().
Int64("EndBlock", endBlock.Int64()).
Expand Down Expand Up @@ -107,7 +120,12 @@ func (cs *BlockStats) Stats(startBlock *big.Int, endBlock *big.Int) error {
// CalculateBlockDurations calculates and logs the duration, TPS, gas used, and gas limit between each consecutive block
func (cs *BlockStats) CalculateBlockDurations(blocks []*types.Block) error {
if len(blocks) == 0 {
return fmt.Errorf("no blocks no analyze")
return fmt.Errorf("no block data available for duration analysis. " +
"This happens when:\n" +
" 1. No blocks were provided for analysis\n" +
" 2. All block fetch attempts failed\n" +
" 3. Block range is invalid\n" +
"Check RPC connectivity and ensure blocks exist in the specified range")
}
var (
durations []time.Duration
Expand Down
Loading
Loading