github.com/ethersphere/bee/v2@v2.2.0/pkg/transaction/transaction.go (about)

     1  // Copyright 2020 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 transaction
     6  
     7  import (
     8  	"bytes"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"math/big"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/ethereum/go-ethereum"
    18  	"github.com/ethereum/go-ethereum/accounts/abi"
    19  	"github.com/ethereum/go-ethereum/common"
    20  	"github.com/ethereum/go-ethereum/core/types"
    21  	"github.com/ethereum/go-ethereum/rpc"
    22  	"github.com/ethersphere/bee/v2/pkg/crypto"
    23  	"github.com/ethersphere/bee/v2/pkg/log"
    24  	"github.com/ethersphere/bee/v2/pkg/sctx"
    25  	"github.com/ethersphere/bee/v2/pkg/storage"
    26  	"golang.org/x/net/context"
    27  )
    28  
    29  // loggerName is the tree path name of the logger for this package.
    30  const loggerName = "transaction"
    31  
    32  const (
    33  	noncePrefix              = "transaction_nonce_"
    34  	storedTransactionPrefix  = "transaction_stored_"
    35  	pendingTransactionPrefix = "transaction_pending_"
    36  )
    37  
    38  var (
    39  	// ErrTransactionReverted denotes that the sent transaction has been
    40  	// reverted.
    41  	ErrTransactionReverted = errors.New("transaction reverted")
    42  	ErrUnknownTransaction  = errors.New("unknown transaction")
    43  	ErrAlreadyImported     = errors.New("already imported")
    44  )
    45  
    46  const (
    47  	DefaultTipBoostPercent = 20
    48  	DefaultGasLimit        = 1_000_000
    49  )
    50  
    51  // TxRequest describes a request for a transaction that can be executed.
    52  type TxRequest struct {
    53  	To                   *common.Address // recipient of the transaction
    54  	Data                 []byte          // transaction data
    55  	GasPrice             *big.Int        // gas price or nil if suggested gas price should be used
    56  	GasLimit             uint64          // gas limit or 0 if it should be estimated
    57  	MinEstimatedGasLimit uint64          // minimum gas limit to use if the gas limit was estimated; it will not apply when this value is 0 or when GasLimit is not 0
    58  	GasFeeCap            *big.Int        // adds a cap to maximum fee user is willing to pay
    59  	Value                *big.Int        // amount of wei to send
    60  	Description          string          // optional description
    61  }
    62  
    63  type StoredTransaction struct {
    64  	To          *common.Address // recipient of the transaction
    65  	Data        []byte          // transaction data
    66  	GasPrice    *big.Int        // used gas price
    67  	GasLimit    uint64          // used gas limit
    68  	GasTipBoost int             // adds a tip for the miner for prioritizing transaction
    69  	GasTipCap   *big.Int        // adds a cap to the tip
    70  	GasFeeCap   *big.Int        // adds a cap to maximum fee user is willing to pay
    71  	Value       *big.Int        // amount of wei to send
    72  	Nonce       uint64          // used nonce
    73  	Created     int64           // creation timestamp
    74  	Description string          // description
    75  }
    76  
    77  // Service is the service to send transactions. It takes care of gas price, gas
    78  // limit and nonce management.
    79  type Service interface {
    80  	io.Closer
    81  	// Send creates a transaction based on the request (with gasprice increased by provided percentage) and sends it.
    82  	Send(ctx context.Context, request *TxRequest, tipCapBoostPercent int) (txHash common.Hash, err error)
    83  	// Call simulate a transaction based on the request.
    84  	Call(ctx context.Context, request *TxRequest) (result []byte, err error)
    85  	// WaitForReceipt waits until either the transaction with the given hash has been mined or the context is cancelled.
    86  	// This is only valid for transaction sent by this service.
    87  	WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error)
    88  	// WatchSentTransaction start watching the given transaction.
    89  	// This wraps the monitors watch function by loading the correct nonce from the store.
    90  	// This is only valid for transaction sent by this service.
    91  	WatchSentTransaction(txHash common.Hash) (<-chan types.Receipt, <-chan error, error)
    92  	// StoredTransaction retrieves the stored information for the transaction
    93  	StoredTransaction(txHash common.Hash) (*StoredTransaction, error)
    94  	// PendingTransactions retrieves the list of all pending transaction hashes
    95  	PendingTransactions() ([]common.Hash, error)
    96  	// ResendTransaction resends a previously sent transaction
    97  	// This operation can be useful if for some reason the transaction vanished from the eth networks pending pool
    98  	ResendTransaction(ctx context.Context, txHash common.Hash) error
    99  	// CancelTransaction cancels a previously sent transaction by double-spending its nonce with zero-transfer one
   100  	CancelTransaction(ctx context.Context, originalTxHash common.Hash) (common.Hash, error)
   101  	// TransactionFee retrieves the transaction fee
   102  	TransactionFee(ctx context.Context, txHash common.Hash) (*big.Int, error)
   103  	// UnwrapABIError tries to unwrap the ABI error if the given error is not nil.
   104  	// The original error is wrapped together with the ABI error if it exists.
   105  	UnwrapABIError(ctx context.Context, req *TxRequest, err error, abiErrors map[string]abi.Error) error
   106  }
   107  
   108  type transactionService struct {
   109  	wg     sync.WaitGroup
   110  	lock   sync.Mutex
   111  	ctx    context.Context
   112  	cancel context.CancelFunc
   113  
   114  	logger  log.Logger
   115  	backend Backend
   116  	signer  crypto.Signer
   117  	sender  common.Address
   118  	store   storage.StateStorer
   119  	chainID *big.Int
   120  	monitor Monitor
   121  }
   122  
   123  // NewService creates a new transaction service.
   124  func NewService(logger log.Logger, overlayEthAddress common.Address, backend Backend, signer crypto.Signer, store storage.StateStorer, chainID *big.Int, monitor Monitor) (Service, error) {
   125  	senderAddress, err := signer.EthereumAddress()
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	ctx, cancel := context.WithCancel(context.Background())
   131  
   132  	t := &transactionService{
   133  		ctx:     ctx,
   134  		cancel:  cancel,
   135  		logger:  logger.WithName(loggerName).WithValues("sender_address", overlayEthAddress).Register(),
   136  		backend: backend,
   137  		signer:  signer,
   138  		sender:  senderAddress,
   139  		store:   store,
   140  		chainID: chainID,
   141  		monitor: monitor,
   142  	}
   143  
   144  	err = t.waitForAllPendingTx()
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return t, nil
   150  }
   151  
   152  func (t *transactionService) waitForAllPendingTx() error {
   153  	pendingTxs, err := t.PendingTransactions()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	pendingTxs = t.filterPendingTransactions(t.ctx, pendingTxs)
   159  
   160  	for _, txHash := range pendingTxs {
   161  		t.waitForPendingTx(txHash)
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  // Send creates and signs a transaction based on the request and sends it.
   168  func (t *transactionService) Send(ctx context.Context, request *TxRequest, boostPercent int) (txHash common.Hash, err error) {
   169  	loggerV1 := t.logger.V(1).Register()
   170  
   171  	t.lock.Lock()
   172  	defer t.lock.Unlock()
   173  
   174  	nonce, err := t.nextNonce(ctx)
   175  	if err != nil {
   176  		return common.Hash{}, err
   177  	}
   178  
   179  	tx, err := t.prepareTransaction(ctx, request, nonce, boostPercent)
   180  	if err != nil {
   181  		return common.Hash{}, err
   182  	}
   183  
   184  	signedTx, err := t.signer.SignTx(tx, t.chainID)
   185  	if err != nil {
   186  		return common.Hash{}, err
   187  	}
   188  
   189  	loggerV1.Debug("sending transaction", "tx", signedTx.Hash(), "nonce", nonce)
   190  
   191  	err = t.backend.SendTransaction(ctx, signedTx)
   192  	if err != nil {
   193  		return common.Hash{}, err
   194  	}
   195  
   196  	err = t.putNonce(nonce + 1)
   197  	if err != nil {
   198  		return common.Hash{}, err
   199  	}
   200  
   201  	txHash = signedTx.Hash()
   202  
   203  	err = t.store.Put(storedTransactionKey(txHash), StoredTransaction{
   204  		To:          signedTx.To(),
   205  		Data:        signedTx.Data(),
   206  		GasPrice:    signedTx.GasPrice(),
   207  		GasLimit:    signedTx.Gas(),
   208  		GasTipBoost: boostPercent,
   209  		GasTipCap:   signedTx.GasTipCap(),
   210  		GasFeeCap:   signedTx.GasFeeCap(),
   211  		Value:       signedTx.Value(),
   212  		Nonce:       signedTx.Nonce(),
   213  		Created:     time.Now().Unix(),
   214  		Description: request.Description,
   215  	})
   216  	if err != nil {
   217  		return common.Hash{}, err
   218  	}
   219  
   220  	err = t.store.Put(pendingTransactionKey(txHash), struct{}{})
   221  	if err != nil {
   222  		return common.Hash{}, err
   223  	}
   224  
   225  	t.waitForPendingTx(txHash)
   226  
   227  	return signedTx.Hash(), nil
   228  }
   229  
   230  func (t *transactionService) waitForPendingTx(txHash common.Hash) {
   231  	t.wg.Add(1)
   232  	go func() {
   233  		defer t.wg.Done()
   234  		switch _, err := t.WaitForReceipt(t.ctx, txHash); {
   235  		case err == nil:
   236  			t.logger.Info("pending transaction confirmed", "tx", txHash)
   237  			err = t.store.Delete(pendingTransactionKey(txHash))
   238  			if err != nil {
   239  				t.logger.Error(err, "unregistering finished pending transaction failed", "tx", txHash)
   240  			}
   241  		default:
   242  			if errors.Is(err, ErrTransactionCancelled) {
   243  				t.logger.Warning("pending transaction cancelled", "tx", txHash)
   244  			} else {
   245  				t.logger.Error(err, "waiting for pending transaction failed", "tx", txHash)
   246  			}
   247  		}
   248  	}()
   249  }
   250  
   251  func (t *transactionService) Call(ctx context.Context, request *TxRequest) ([]byte, error) {
   252  	msg := ethereum.CallMsg{
   253  		From:     t.sender,
   254  		To:       request.To,
   255  		Data:     request.Data,
   256  		GasPrice: request.GasPrice,
   257  		Gas:      request.GasLimit,
   258  		Value:    request.Value,
   259  	}
   260  	data, err := t.backend.CallContract(ctx, msg, nil)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	return data, nil
   266  }
   267  
   268  func (t *transactionService) StoredTransaction(txHash common.Hash) (*StoredTransaction, error) {
   269  	var tx StoredTransaction
   270  	err := t.store.Get(storedTransactionKey(txHash), &tx)
   271  	if err != nil {
   272  		if errors.Is(err, storage.ErrNotFound) {
   273  			return nil, ErrUnknownTransaction
   274  		}
   275  		return nil, err
   276  	}
   277  	return &tx, nil
   278  }
   279  
   280  // prepareTransaction creates a signable transaction based on a request.
   281  func (t *transactionService) prepareTransaction(ctx context.Context, request *TxRequest, nonce uint64, boostPercent int) (tx *types.Transaction, err error) {
   282  	var gasLimit uint64
   283  	if request.GasLimit == 0 {
   284  		gasLimit, err = t.backend.EstimateGas(ctx, ethereum.CallMsg{
   285  			From: t.sender,
   286  			To:   request.To,
   287  			Data: request.Data,
   288  		})
   289  		if err != nil {
   290  			t.logger.Debug("estimate gas failed", "error", err)
   291  			gasLimit = request.MinEstimatedGasLimit
   292  		}
   293  
   294  		gasLimit += gasLimit / 4 // add 25% on top
   295  		if gasLimit < request.MinEstimatedGasLimit {
   296  			gasLimit = request.MinEstimatedGasLimit
   297  		}
   298  	} else {
   299  		gasLimit = request.GasLimit
   300  	}
   301  
   302  	/*
   303  		Transactions are EIP 1559 dynamic transactions where there are three fee related fields:
   304  			1. base fee is the price that will be burned as part of the transaction.
   305  			2. max fee is the max price we are willing to spend as gas price.
   306  			3. max priority fee is max price want to give to the miner to prioritize the transaction.
   307  		as an example:
   308  		if base fee is 15, max fee is 20, and max priority is 3, gas price will be 15 + 3 = 18
   309  		if base is 15, max fee is 20, and max priority fee is 10,
   310  		gas price will be 15 + 10 = 25, but since 25 > 20, gas price is 20.
   311  		notice that gas price does not exceed 20 as defined by max fee.
   312  	*/
   313  
   314  	gasFeeCap, gasTipCap, err := t.suggestedFeeAndTip(ctx, request.GasPrice, boostPercent)
   315  	if err != nil {
   316  		return nil, err
   317  	}
   318  
   319  	return types.NewTx(&types.DynamicFeeTx{
   320  		Nonce:     nonce,
   321  		ChainID:   t.chainID,
   322  		To:        request.To,
   323  		Value:     request.Value,
   324  		Gas:       gasLimit,
   325  		GasFeeCap: gasFeeCap,
   326  		GasTipCap: gasTipCap,
   327  		Data:      request.Data,
   328  	}), nil
   329  }
   330  
   331  func (t *transactionService) suggestedFeeAndTip(ctx context.Context, gasPrice *big.Int, boostPercent int) (*big.Int, *big.Int, error) {
   332  	var err error
   333  
   334  	if gasPrice == nil {
   335  		gasPrice, err = t.backend.SuggestGasPrice(ctx)
   336  		if err != nil {
   337  			return nil, nil, err
   338  		}
   339  		gasPrice = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(boostPercent)+100), gasPrice), big.NewInt(100))
   340  	}
   341  
   342  	gasTipCap, err := t.backend.SuggestGasTipCap(ctx)
   343  	if err != nil {
   344  		return nil, nil, err
   345  	}
   346  
   347  	gasTipCap = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(boostPercent)+100), gasTipCap), big.NewInt(100))
   348  	gasFeeCap := new(big.Int).Add(gasTipCap, gasPrice)
   349  
   350  	t.logger.Debug("prepare transaction", "gas_price", gasPrice, "gas_max_fee", gasFeeCap, "gas_max_tip", gasTipCap)
   351  
   352  	return gasFeeCap, gasTipCap, nil
   353  
   354  }
   355  
   356  func (t *transactionService) nonceKey() string {
   357  	return fmt.Sprintf("%s%x", noncePrefix, t.sender)
   358  }
   359  
   360  func storedTransactionKey(txHash common.Hash) string {
   361  	return fmt.Sprintf("%s%x", storedTransactionPrefix, txHash)
   362  }
   363  
   364  func pendingTransactionKey(txHash common.Hash) string {
   365  	return fmt.Sprintf("%s%x", pendingTransactionPrefix, txHash)
   366  }
   367  
   368  func (t *transactionService) nextNonce(ctx context.Context) (uint64, error) {
   369  	onchainNonce, err := t.backend.PendingNonceAt(ctx, t.sender)
   370  	if err != nil {
   371  		return 0, err
   372  	}
   373  
   374  	var nonce uint64
   375  	err = t.store.Get(t.nonceKey(), &nonce)
   376  	if err != nil {
   377  		// If no nonce was found locally used whatever we get from the backend.
   378  		if errors.Is(err, storage.ErrNotFound) {
   379  			return onchainNonce, nil
   380  		}
   381  		return 0, err
   382  	}
   383  
   384  	// If the nonce onchain is larger than what we have there were external
   385  	// transactions and we need to update our nonce.
   386  	if onchainNonce > nonce {
   387  		return onchainNonce, nil
   388  	}
   389  	return nonce, nil
   390  }
   391  
   392  func (t *transactionService) putNonce(nonce uint64) error {
   393  	return t.store.Put(t.nonceKey(), nonce)
   394  }
   395  
   396  // WaitForReceipt waits until either the transaction with the given hash has
   397  // been mined or the context is cancelled.
   398  func (t *transactionService) WaitForReceipt(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) {
   399  	receiptC, errC, err := t.WatchSentTransaction(txHash)
   400  	if err != nil {
   401  		return nil, err
   402  	}
   403  	select {
   404  	case receipt := <-receiptC:
   405  		return &receipt, nil
   406  	case err := <-errC:
   407  		return nil, err
   408  	// don't wait longer than the context that was passed in
   409  	case <-ctx.Done():
   410  		return nil, ctx.Err()
   411  	}
   412  }
   413  
   414  func (t *transactionService) WatchSentTransaction(txHash common.Hash) (<-chan types.Receipt, <-chan error, error) {
   415  	t.lock.Lock()
   416  	defer t.lock.Unlock()
   417  
   418  	// loading the tx here guarantees it was in fact sent from this transaction service
   419  	// also it allows us to avoid having to load the transaction during the watch loop
   420  	storedTransaction, err := t.StoredTransaction(txHash)
   421  	if err != nil {
   422  		return nil, nil, err
   423  	}
   424  
   425  	return t.monitor.WatchTransaction(txHash, storedTransaction.Nonce)
   426  }
   427  
   428  func (t *transactionService) PendingTransactions() ([]common.Hash, error) {
   429  	var txHashes []common.Hash = make([]common.Hash, 0)
   430  	err := t.store.Iterate(pendingTransactionPrefix, func(key, value []byte) (stop bool, err error) {
   431  		txHash := common.HexToHash(strings.TrimPrefix(string(key), pendingTransactionPrefix))
   432  		txHashes = append(txHashes, txHash)
   433  		return false, nil
   434  	})
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  	return txHashes, nil
   439  }
   440  
   441  // filterPendingTransactions will filter supplied transaction hashes removing those that are not pending anymore.
   442  // Removed transactions will be also removed from store.
   443  func (t *transactionService) filterPendingTransactions(ctx context.Context, txHashes []common.Hash) []common.Hash {
   444  	result := make([]common.Hash, 0, len(txHashes))
   445  
   446  	for _, txHash := range txHashes {
   447  		_, isPending, err := t.backend.TransactionByHash(ctx, txHash)
   448  
   449  		// When error occurres consider transaction as pending (so this transaction won't be filtered out),
   450  		// unless it was not found
   451  		if err != nil {
   452  			if errors.Is(err, ethereum.NotFound) {
   453  				t.logger.Error(err, "pending transactions not found", "tx", txHash)
   454  
   455  				isPending = false
   456  			} else {
   457  				isPending = true
   458  			}
   459  		}
   460  
   461  		if isPending {
   462  			result = append(result, txHash)
   463  		} else {
   464  			err := t.store.Delete(pendingTransactionKey(txHash))
   465  			if err != nil {
   466  				t.logger.Error(err, "error while unregistering transaction as pending", "tx", txHash)
   467  			}
   468  		}
   469  	}
   470  
   471  	return result
   472  }
   473  
   474  func (t *transactionService) ResendTransaction(ctx context.Context, txHash common.Hash) error {
   475  	storedTransaction, err := t.StoredTransaction(txHash)
   476  	if err != nil {
   477  		return err
   478  	}
   479  
   480  	gasFeeCap, gasTipCap, err := t.suggestedFeeAndTip(ctx, sctx.GetGasPrice(ctx), storedTransaction.GasTipBoost)
   481  	if err != nil {
   482  		return err
   483  	}
   484  
   485  	tx := types.NewTx(&types.DynamicFeeTx{
   486  		Nonce:     storedTransaction.Nonce,
   487  		ChainID:   t.chainID,
   488  		To:        storedTransaction.To,
   489  		Value:     storedTransaction.Value,
   490  		Gas:       storedTransaction.GasLimit,
   491  		GasTipCap: gasTipCap,
   492  		GasFeeCap: gasFeeCap,
   493  		Data:      storedTransaction.Data,
   494  	})
   495  
   496  	signedTx, err := t.signer.SignTx(tx, t.chainID)
   497  	if err != nil {
   498  		return err
   499  	}
   500  
   501  	if signedTx.Hash() != txHash {
   502  		return errors.New("transaction hash changed")
   503  	}
   504  
   505  	err = t.backend.SendTransaction(t.ctx, signedTx)
   506  	if err != nil {
   507  		if strings.Contains(err.Error(), "already imported") {
   508  			return ErrAlreadyImported
   509  		}
   510  	}
   511  	return nil
   512  }
   513  
   514  func (t *transactionService) CancelTransaction(ctx context.Context, originalTxHash common.Hash) (common.Hash, error) {
   515  	storedTransaction, err := t.StoredTransaction(originalTxHash)
   516  	if err != nil {
   517  		return common.Hash{}, err
   518  	}
   519  
   520  	gasFeeCap, gasTipCap, err := t.suggestedFeeAndTip(ctx, sctx.GetGasPrice(ctx), 0)
   521  	if err != nil {
   522  		return common.Hash{}, err
   523  	}
   524  
   525  	if gasFeeCap.Cmp(storedTransaction.GasFeeCap) <= 0 {
   526  		gasFeeCap = storedTransaction.GasFeeCap
   527  	}
   528  
   529  	if gasTipCap.Cmp(storedTransaction.GasTipCap) <= 0 {
   530  		gasTipCap = storedTransaction.GasTipCap
   531  	}
   532  
   533  	gasTipCap = new(big.Int).Div(new(big.Int).Mul(big.NewInt(int64(10)+100), gasTipCap), big.NewInt(100))
   534  
   535  	gasFeeCap.Add(gasFeeCap, gasTipCap)
   536  
   537  	signedTx, err := t.signer.SignTx(types.NewTx(&types.DynamicFeeTx{
   538  		Nonce:     storedTransaction.Nonce,
   539  		ChainID:   t.chainID,
   540  		To:        &t.sender,
   541  		Value:     big.NewInt(0),
   542  		Gas:       21000,
   543  		GasTipCap: gasTipCap,
   544  		GasFeeCap: gasFeeCap,
   545  		Data:      []byte{},
   546  	}), t.chainID)
   547  	if err != nil {
   548  		return common.Hash{}, err
   549  	}
   550  
   551  	err = t.backend.SendTransaction(t.ctx, signedTx)
   552  	if err != nil {
   553  		return common.Hash{}, err
   554  	}
   555  
   556  	txHash := signedTx.Hash()
   557  	err = t.store.Put(storedTransactionKey(txHash), StoredTransaction{
   558  		To:          signedTx.To(),
   559  		Data:        signedTx.Data(),
   560  		GasPrice:    signedTx.GasPrice(),
   561  		GasLimit:    signedTx.Gas(),
   562  		GasFeeCap:   signedTx.GasFeeCap(),
   563  		GasTipBoost: storedTransaction.GasTipBoost,
   564  		GasTipCap:   signedTx.GasTipCap(),
   565  		Value:       signedTx.Value(),
   566  		Nonce:       signedTx.Nonce(),
   567  		Created:     time.Now().Unix(),
   568  		Description: fmt.Sprintf("%s (cancellation)", storedTransaction.Description),
   569  	})
   570  	if err != nil {
   571  		return common.Hash{}, err
   572  	}
   573  
   574  	err = t.store.Put(pendingTransactionKey(txHash), struct{}{})
   575  	if err != nil {
   576  		return common.Hash{}, err
   577  	}
   578  
   579  	t.waitForPendingTx(txHash)
   580  
   581  	return txHash, err
   582  }
   583  
   584  func (t *transactionService) Close() error {
   585  	t.cancel()
   586  	t.wg.Wait()
   587  	return nil
   588  }
   589  
   590  func (t *transactionService) TransactionFee(ctx context.Context, txHash common.Hash) (*big.Int, error) {
   591  	trx, _, err := t.backend.TransactionByHash(ctx, txHash)
   592  	if err != nil {
   593  		return nil, err
   594  	}
   595  	return trx.Cost(), nil
   596  }
   597  
   598  func (t *transactionService) UnwrapABIError(ctx context.Context, req *TxRequest, err error, abiErrors map[string]abi.Error) error {
   599  	if err == nil {
   600  		return nil
   601  	}
   602  
   603  	_, cErr := t.Call(ctx, req)
   604  	if cErr == nil {
   605  		return err
   606  	}
   607  	err = fmt.Errorf("%w: %s", err, cErr) //nolint:errorlint
   608  
   609  	var derr rpc.DataError
   610  	if !errors.As(cErr, &derr) {
   611  		return err
   612  	}
   613  
   614  	res, ok := derr.ErrorData().(string)
   615  	if !ok {
   616  		return err
   617  	}
   618  	buf := common.FromHex(res)
   619  
   620  	if reason, uErr := abi.UnpackRevert(buf); uErr == nil {
   621  		return fmt.Errorf("%w: %s", err, reason)
   622  	}
   623  
   624  	for _, abiError := range abiErrors {
   625  		if !bytes.Equal(buf[:4], abiError.ID[:4]) {
   626  			continue
   627  		}
   628  
   629  		data, uErr := abiError.Unpack(buf)
   630  		if uErr != nil {
   631  			continue
   632  		}
   633  
   634  		values, ok := data.([]interface{})
   635  		if !ok {
   636  			values = make([]interface{}, len(abiError.Inputs))
   637  			for i := range values {
   638  				values[i] = "?"
   639  			}
   640  		}
   641  
   642  		params := make([]string, len(abiError.Inputs))
   643  		for i, input := range abiError.Inputs {
   644  			if input.Name == "" {
   645  				input.Name = fmt.Sprintf("arg%d", i)
   646  			}
   647  			params[i] = fmt.Sprintf("%s=%v", input.Name, values[i])
   648  
   649  		}
   650  
   651  		return fmt.Errorf("%w: %s(%s)", err, abiError.Name, strings.Join(params, ","))
   652  	}
   653  
   654  	return err
   655  }