github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/blockchain/transaction_confirmers.go (about)

     1  package blockchain
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"encoding/json"
     7  	"fmt"
     8  	"math/big"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/ethereum/go-ethereum"
    14  	"github.com/ethereum/go-ethereum/accounts/abi"
    15  	"github.com/ethereum/go-ethereum/common"
    16  	"github.com/ethereum/go-ethereum/core"
    17  	"github.com/ethereum/go-ethereum/core/types"
    18  	"github.com/rs/zerolog"
    19  	"golang.org/x/sync/errgroup"
    20  )
    21  
    22  // How many successful connection polls to consider the connection restored after a disconnection
    23  const targetSuccessCount = 3
    24  
    25  // TransactionConfirmer is an implementation of HeaderEventSubscription that checks whether tx are confirmed
    26  type TransactionConfirmer struct {
    27  	minConfirmations      int
    28  	confirmations         int
    29  	client                EVMClient
    30  	tx                    *types.Transaction
    31  	doneChan              chan struct{}
    32  	revertChan            chan struct{}
    33  	context               context.Context
    34  	cancel                context.CancelFunc
    35  	networkConfig         *EVMNetwork
    36  	lastReceivedHeaderNum uint64
    37  	complete              bool
    38  	completeMu            sync.Mutex
    39  	l                     zerolog.Logger
    40  }
    41  
    42  // NewTransactionConfirmer returns a new instance of the transaction confirmer that waits for on-chain minimum
    43  // confirmations
    44  func NewTransactionConfirmer(client EVMClient, tx *types.Transaction, minConfirmations int, logger zerolog.Logger) *TransactionConfirmer {
    45  	ctx, ctxCancel := context.WithTimeout(context.Background(), client.GetNetworkConfig().Timeout.Duration)
    46  	tc := &TransactionConfirmer{
    47  		minConfirmations: minConfirmations,
    48  		confirmations:    0,
    49  		client:           client,
    50  		tx:               tx,
    51  		doneChan:         make(chan struct{}, 1),
    52  		revertChan:       make(chan struct{}, 1),
    53  		context:          ctx,
    54  		cancel:           ctxCancel,
    55  		networkConfig:    client.GetNetworkConfig(),
    56  		complete:         false,
    57  		l:                logger,
    58  	}
    59  	return tc
    60  }
    61  
    62  // ReceiveHeader the implementation of the HeaderEventSubscription that receives each header and checks
    63  // tx confirmation
    64  func (t *TransactionConfirmer) ReceiveHeader(header NodeHeader) error {
    65  	if header.Number.Uint64() <= t.lastReceivedHeaderNum {
    66  		return nil // Header with same number mined, disregard for confirming
    67  	}
    68  	t.lastReceivedHeaderNum = header.Number.Uint64()
    69  	confirmationLog := t.l.Debug().
    70  		Str("Network Name", t.networkConfig.Name).
    71  		Str("Header Hash", header.Hash.Hex()).
    72  		Str("Header Number", header.Number.String()).
    73  		Str("Tx Hash", t.tx.Hash().String()).
    74  		Uint64("Nonce", t.tx.Nonce()).
    75  		Int("Minimum Confirmations", t.minConfirmations)
    76  	isConfirmed, err := t.client.IsTxConfirmed(t.tx.Hash())
    77  	if err != nil {
    78  		if err.Error() == "not found" {
    79  			confirmationLog.Msg("Transaction not found on chain yet. Waiting to confirm.")
    80  			return nil
    81  		}
    82  		if strings.Contains(err.Error(), "transaction failed and was reverted") {
    83  			t.revertChan <- struct{}{}
    84  		}
    85  		return err
    86  	}
    87  	if isConfirmed {
    88  		t.confirmations++
    89  	}
    90  	if t.confirmations >= t.minConfirmations {
    91  		confirmationLog.Int("Current Confirmations", t.confirmations).Msg("Transaction confirmations met")
    92  		t.complete = true
    93  		t.doneChan <- struct{}{}
    94  	} else {
    95  		confirmationLog.Int("Current Confirmations", t.confirmations).Msg("Waiting on minimum confirmations")
    96  	}
    97  	return nil
    98  }
    99  
   100  // Wait is a blocking function that waits until the transaction is complete
   101  func (t *TransactionConfirmer) Wait() error {
   102  	defer func() {
   103  		t.completeMu.Lock()
   104  		t.complete = true
   105  		t.completeMu.Unlock()
   106  	}()
   107  
   108  	if t.Complete() {
   109  		t.cancel()
   110  		return nil
   111  	}
   112  
   113  	for {
   114  		select {
   115  		case <-t.doneChan:
   116  			t.cancel()
   117  			return nil
   118  		case <-t.revertChan:
   119  			return fmt.Errorf("transaction reverted: %s network %s", t.tx.Hash(), t.client.GetNetworkName())
   120  		case <-t.context.Done():
   121  			return fmt.Errorf("timeout waiting for transaction to confirm: %s network %s", t.tx.Hash(), t.client.GetNetworkName())
   122  		}
   123  	}
   124  }
   125  
   126  // Complete returns if the confirmer has completed or not
   127  func (t *TransactionConfirmer) Complete() bool {
   128  	t.completeMu.Lock()
   129  	defer t.completeMu.Unlock()
   130  	return t.complete
   131  }
   132  
   133  // InstantConfirmer is a near-instant confirmation method, primarily for optimistic L2s that have near-instant finalization
   134  type InstantConfirmer struct {
   135  	client        EVMClient
   136  	txHash        common.Hash
   137  	complete      bool // tracks if the subscription is completed or not
   138  	context       context.Context
   139  	cancel        context.CancelFunc
   140  	newHeaderChan chan struct{}
   141  	// For events
   142  	confirmed     bool // tracks the confirmation status of the subscription
   143  	confirmedChan chan bool
   144  	log           zerolog.Logger
   145  }
   146  
   147  func NewInstantConfirmer(
   148  	client EVMClient,
   149  	txHash common.Hash,
   150  	confirmedChan chan bool,
   151  	_ chan error,
   152  	logger zerolog.Logger,
   153  ) *InstantConfirmer {
   154  	ctx, ctxCancel := context.WithTimeout(context.Background(), client.GetNetworkConfig().Timeout.Duration)
   155  	return &InstantConfirmer{
   156  		client:  client,
   157  		txHash:  txHash,
   158  		context: ctx,
   159  		cancel:  ctxCancel,
   160  		// For events
   161  		confirmedChan: confirmedChan,
   162  		log:           logger,
   163  	}
   164  }
   165  
   166  // ReceiveHeader does a quick check on if the tx is confirmed already
   167  func (l *InstantConfirmer) ReceiveHeader(_ NodeHeader) error {
   168  	return nil
   169  }
   170  
   171  // Wait checks every header if the tx has been included on chain or not
   172  func (l *InstantConfirmer) Wait() error {
   173  	defer func() {
   174  		l.complete = true
   175  		l.cancel()
   176  	}()
   177  	confirmed, err := l.checkConfirmed()
   178  	if err != nil {
   179  		return err
   180  	}
   181  	if confirmed {
   182  		return nil
   183  	}
   184  
   185  	poll := time.NewTicker(time.Millisecond * 250)
   186  	for {
   187  		select {
   188  		case <-poll.C:
   189  			confirmed, err := l.checkConfirmed()
   190  			if err != nil {
   191  				return err
   192  			}
   193  			if confirmed {
   194  				return nil
   195  			}
   196  		case <-l.newHeaderChan:
   197  			confirmed, err := l.checkConfirmed()
   198  			if err != nil {
   199  				return err
   200  			}
   201  			if confirmed {
   202  				return nil
   203  			}
   204  		case <-l.context.Done():
   205  			return fmt.Errorf("timeout waiting for instant transaction to confirm after %s: %s",
   206  				l.client.GetNetworkConfig().Timeout.String(), l.txHash.Hex())
   207  		}
   208  	}
   209  }
   210  
   211  func (l *InstantConfirmer) checkConfirmed() (bool, error) {
   212  	confirmed, err := l.client.IsTxConfirmed(l.txHash)
   213  	if err != nil {
   214  		return false, err
   215  	}
   216  	l.confirmed = confirmed
   217  	if confirmed {
   218  		go func() {
   219  			if l.confirmedChan != nil {
   220  				l.confirmedChan <- true
   221  			}
   222  		}()
   223  	}
   224  	l.log.Trace().Bool("Confirmed", confirmed).Str("Hash", l.txHash.Hex()).Msg("Checked if transaction confirmed")
   225  	return confirmed, nil
   226  }
   227  
   228  // Complete returns if the transaction is complete or not
   229  func (l *InstantConfirmer) Complete() bool {
   230  	return l.complete
   231  }
   232  
   233  // EventConfirmer confirms that an event is confirmed by a certain amount of headers
   234  type EventConfirmer struct {
   235  	eventName             string
   236  	minConfirmations      int
   237  	confirmations         int
   238  	client                EVMClient
   239  	event                 *types.Log
   240  	waitChan              chan struct{}
   241  	errorChan             chan error
   242  	confirmedChan         chan bool
   243  	context               context.Context
   244  	cancel                context.CancelFunc
   245  	lastReceivedHeaderNum uint64
   246  	complete              bool
   247  }
   248  
   249  // NewEventConfirmer returns a new instance of the event confirmer that waits for on-chain minimum
   250  // confirmations
   251  func NewEventConfirmer(
   252  	eventName string,
   253  	client EVMClient,
   254  	event *types.Log,
   255  	minConfirmations int,
   256  	confirmedChan chan bool,
   257  	errorChan chan error,
   258  ) *EventConfirmer {
   259  	ctx, ctxCancel := context.WithTimeout(context.Background(), client.GetNetworkConfig().Timeout.Duration)
   260  	tc := &EventConfirmer{
   261  		eventName:        eventName,
   262  		minConfirmations: minConfirmations,
   263  		confirmations:    0,
   264  		client:           client,
   265  		event:            event,
   266  		waitChan:         make(chan struct{}, 1),
   267  		errorChan:        errorChan,
   268  		confirmedChan:    confirmedChan,
   269  		context:          ctx,
   270  		cancel:           ctxCancel,
   271  		complete:         false,
   272  	}
   273  	return tc
   274  }
   275  
   276  // ReceiveHeader will attempt to confirm an event for the chain's configured minimum confirmed headers. Errors encountered
   277  // are sent along the eventErrorChan, and the result of confirming the event is sent to eventConfirmedChan.
   278  func (e *EventConfirmer) ReceiveHeader(header NodeHeader) error {
   279  	if header.Number.Uint64() <= e.lastReceivedHeaderNum {
   280  		return nil
   281  	}
   282  	e.lastReceivedHeaderNum = header.Number.Uint64()
   283  	confirmed, removed, err := e.client.IsEventConfirmed(e.event)
   284  	if err != nil {
   285  		e.errorChan <- err
   286  		return err
   287  	}
   288  	if removed {
   289  		e.confirmedChan <- false
   290  		e.complete = true
   291  		return nil
   292  	}
   293  	if confirmed {
   294  		e.confirmations++
   295  	}
   296  	if e.confirmations >= e.minConfirmations {
   297  		e.confirmedChan <- true
   298  		e.complete = true
   299  	}
   300  	return nil
   301  }
   302  
   303  // Wait until the event fully presents as complete
   304  func (e *EventConfirmer) Wait() error {
   305  	defer func() { e.complete = true }()
   306  	for {
   307  		select {
   308  		case <-e.waitChan:
   309  			e.cancel()
   310  			return nil
   311  		case <-e.context.Done():
   312  			return fmt.Errorf("timeout waiting for event to confirm after %s: %s",
   313  				e.client.GetNetworkConfig().Timeout.String(), e.event.TxHash.Hex())
   314  		}
   315  	}
   316  }
   317  
   318  // Complete returns if the confirmer is done, whether confirmation was successful or not
   319  func (e *EventConfirmer) Complete() bool {
   320  	return e.complete
   321  }
   322  
   323  // GetHeaderSubscriptions returns a duplicate map of the queued transactions
   324  func (e *EthereumClient) GetHeaderSubscriptions() map[string]HeaderEventSubscription {
   325  	e.subscriptionMutex.Lock()
   326  	defer e.subscriptionMutex.Unlock()
   327  
   328  	newMap := map[string]HeaderEventSubscription{}
   329  	for k, v := range e.headerSubscriptions {
   330  		newMap[k] = v
   331  	}
   332  	return newMap
   333  }
   334  
   335  // subscribeToNewHeaders kicks off the primary header subscription for the test loop
   336  func (e *EthereumClient) subscribeToNewHeaders() error {
   337  	headerChannel := make(chan *SafeEVMHeader)
   338  	subscription, err := e.SubscribeNewHeaders(context.Background(), headerChannel)
   339  	if err != nil {
   340  		return err
   341  	}
   342  	e.l.Info().Str("Network", e.NetworkConfig.Name).Msg("Subscribed to new block headers")
   343  
   344  	e.subscriptionWg.Add(1)
   345  	go e.headerSubscriptionLoop(subscription, headerChannel)
   346  	return nil
   347  }
   348  
   349  // headerSubscriptionLoop receives new headers, and handles subscription errors when they pop up
   350  func (e *EthereumClient) headerSubscriptionLoop(subscription ethereum.Subscription, headerChannel chan *SafeEVMHeader) {
   351  	defer e.subscriptionWg.Done()
   352  	lastHeaderNumber := uint64(0)
   353  	for {
   354  		select {
   355  		case err := <-subscription.Err(): // Most subscription errors are temporary RPC downtime, so let's poll to resubscribe
   356  			e.l.Error().
   357  				Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]).
   358  				Str("Network", e.NetworkConfig.Name).
   359  				Err(err).
   360  				Msg("Error while subscribed to new headers, likely RPC downtime. Attempting to resubscribe")
   361  			e.connectionIssueCh <- time.Now()
   362  			subscription = e.resubscribeLoop(headerChannel, lastHeaderNumber)
   363  			e.connectionRestoredCh <- time.Now()
   364  		case header := <-headerChannel:
   365  			err := e.receiveHeader(header)
   366  			if err != nil {
   367  				e.l.Error().Str("Network", e.NetworkConfig.Name).Err(err).Msg("Error receiving header, possible RPC issues")
   368  			} else {
   369  				lastHeaderNumber = header.Number.Uint64()
   370  			}
   371  		case <-e.doneChan:
   372  			e.l.Debug().Str("Network", e.NetworkConfig.Name).Msg("Subscription cancelled")
   373  			e.Client.Close()
   374  			return
   375  		}
   376  	}
   377  }
   378  
   379  // resubscribeLoop polls the RPC connection until it comes back up, and then resubscribes to new headers
   380  func (e *EthereumClient) resubscribeLoop(headerChannel chan *SafeEVMHeader, lastHeaderNumber uint64) ethereum.Subscription {
   381  	rpcDegradedTime := time.Now()
   382  	pollInterval := time.Millisecond * 500
   383  	checkTicker, informTicker := time.NewTicker(pollInterval), time.NewTicker(time.Second*10)
   384  	reconnectAttempts, consecutiveSuccessCount := 0, 0
   385  	e.l.Debug().Str("Network", e.NetworkConfig.Name).Str("Poll Interval", pollInterval.String()).Msg("Attempting to resubscribe to new headers")
   386  
   387  	for {
   388  		select {
   389  		case <-checkTicker.C:
   390  			reconnectAttempts++
   391  			_, err := e.LatestBlockNumber(context.Background())
   392  			if err != nil {
   393  				e.l.Trace().Err(err).Msg("Error trying to resubscribe to new headers, likely RPC down")
   394  				consecutiveSuccessCount = 0
   395  				continue
   396  			}
   397  			consecutiveSuccessCount++
   398  			e.l.Debug().
   399  				Str("Network", e.NetworkConfig.Name).
   400  				Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]).
   401  				Int("Target Success Count", targetSuccessCount).
   402  				Int("Consecutive Success Count", consecutiveSuccessCount).
   403  				Msg("RPC connection seems to be healthy, still checking")
   404  
   405  			if consecutiveSuccessCount >= targetSuccessCount { // Make sure node is actually back up and not just a blip
   406  				subscription, err := e.SubscribeNewHeaders(context.Background(), headerChannel)
   407  				if err != nil {
   408  					e.l.Error().Err(err).Msg("Error resubscribing to new headers, RPC connection is still coming up")
   409  					consecutiveSuccessCount = 0
   410  					continue
   411  				}
   412  				// No error on resubscription, RPC connection restored, back to regularly scheduled programming
   413  				checkTicker.Stop()
   414  				informTicker.Stop()
   415  				e.l.Info().
   416  					Int("Reconnect Attempts", reconnectAttempts).
   417  					Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]).
   418  					Str("Time waiting", time.Since(rpcDegradedTime).String()).
   419  					Msg("RPC connection and subscription restored")
   420  				go e.backfillMissedBlocks(lastHeaderNumber, headerChannel)
   421  				return subscription
   422  			}
   423  		case <-informTicker.C:
   424  			e.l.Warn().
   425  				Str("Network", e.NetworkConfig.Name).
   426  				Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]).
   427  				Int("Reconnect Attempts", reconnectAttempts).
   428  				Str("Time waiting", time.Since(rpcDegradedTime).String()).
   429  				Str("RPC URL", e.NetworkConfig.URL).
   430  				Msg("RPC connection still down, waiting for it to come back up")
   431  		}
   432  	}
   433  }
   434  
   435  // backfillMissedBlocks checks if there are any missed blocks since a bad connection was detected, and if so, backfills them
   436  // to our header channel
   437  func (e *EthereumClient) backfillMissedBlocks(lastBlockSeen uint64, headerChannel chan *SafeEVMHeader) {
   438  	start := time.Now()
   439  	latestBlockNumber, err := e.LatestBlockNumber(context.Background())
   440  	if err != nil {
   441  		e.l.Error().Str("Network", e.NetworkConfig.Name).Err(err).Msg("Error getting latest block number. Unable to backfill missed blocks. Subscription likely degraded")
   442  		return
   443  	}
   444  	if latestBlockNumber <= lastBlockSeen {
   445  		e.l.Info().Msg("No missed blocks to backfill")
   446  		return
   447  	}
   448  	e.l.Info().
   449  		Str("Network", e.NetworkConfig.Name).
   450  		Uint64("Last Block Seen", lastBlockSeen).
   451  		Uint64("Blocks Behind", latestBlockNumber-lastBlockSeen).
   452  		Uint64("Latest Block", latestBlockNumber).
   453  		Msg("Backfilling missed blocks since RPC connection issues")
   454  	for i := lastBlockSeen + 1; i <= latestBlockNumber; i++ {
   455  		header, err := e.HeaderByNumber(context.Background(), big.NewInt(int64(i)))
   456  		if err != nil {
   457  			e.l.Err(err).Uint64("Number", i).Msg("Error getting header, unable to backfill and process it")
   458  			return
   459  		}
   460  		e.l.Trace().Str("Network", e.NetworkConfig.Name).Str("Hash", header.Hash.Hex()).Uint64("Number", i).Msg("Backfilling header")
   461  		headerChannel <- header
   462  	}
   463  	e.l.Info().
   464  		Str("Network", e.NetworkConfig.Name).
   465  		Uint64("Backfilled blocks", latestBlockNumber-lastBlockSeen).
   466  		Str("Time", time.Since(start).String()).
   467  		Msg("Finished backfilling missed blocks")
   468  }
   469  
   470  // receiveHeader takes in a new header from the chain, and sends the header to all active header subscriptions
   471  func (e *EthereumClient) receiveHeader(header *SafeEVMHeader) error {
   472  	if header == nil {
   473  		e.l.Debug().Msg("Received Nil Header")
   474  		return nil
   475  	}
   476  	headerValue := *header
   477  
   478  	e.l.Debug().
   479  		Str("NetworkName", e.NetworkConfig.Name).
   480  		Int("Node", e.ID).
   481  		Str("Hash", headerValue.Hash.String()).
   482  		Str("Number", headerValue.Number.String()).
   483  		Msg("Received Header")
   484  
   485  	safeHeader := NodeHeader{NodeID: e.ID, SafeEVMHeader: headerValue}
   486  	subs := e.GetHeaderSubscriptions()
   487  	e.l.Trace().
   488  		Str("NetworkName", e.NetworkConfig.Name).
   489  		Int("Node", e.ID).
   490  		Interface("Map", subs).Msg("Active Header Subscriptions")
   491  
   492  	g := errgroup.Group{}
   493  	for _, sub := range subs {
   494  		sub := sub
   495  		g.Go(func() error {
   496  			return sub.ReceiveHeader(safeHeader)
   497  		})
   498  	}
   499  
   500  	if err := g.Wait(); err != nil {
   501  		return fmt.Errorf("error on sending block header to receivers: '%w'", err)
   502  	}
   503  
   504  	if len(subs) > 0 {
   505  		var subsRemoved uint
   506  		for key, sub := range subs { // Cleanup subscriptions that might not have Wait called on them
   507  			if sub.Complete() {
   508  				subsRemoved++
   509  				e.DeleteHeaderEventSubscription(key)
   510  			}
   511  		}
   512  		if subsRemoved > 0 {
   513  			e.l.Trace().
   514  				Str("NetworkName", e.NetworkConfig.Name).
   515  				Int("Node", e.ID).
   516  				Uint("Recently Removed", subsRemoved).
   517  				Int("Active", len(e.GetHeaderSubscriptions())).
   518  				Msg("Updated Header Subscriptions")
   519  		}
   520  	}
   521  	return nil
   522  }
   523  
   524  // errorReason decodes tx revert reason
   525  func (e *EthereumClient) errorReason(
   526  	b ethereum.ContractCaller,
   527  	tx *types.Transaction,
   528  	receipt *types.Receipt,
   529  ) (string, error) {
   530  	chID, err := e.Client.NetworkID(context.Background())
   531  	if err != nil {
   532  		return "", err
   533  	}
   534  	var msg *core.Message
   535  	if e.NetworkConfig.SupportsEIP1559 {
   536  		msg, err = core.TransactionToMessage(tx, types.LatestSignerForChainID(chID), nil)
   537  	} else {
   538  		msg, err = core.TransactionToMessage(tx, types.NewEIP155Signer(chID), nil)
   539  	}
   540  	if err != nil {
   541  		return "", err
   542  	}
   543  
   544  	callMsg := ethereum.CallMsg{
   545  		From:     msg.From,
   546  		To:       tx.To(),
   547  		Gas:      tx.Gas(),
   548  		GasPrice: tx.GasPrice(),
   549  		Value:    tx.Value(),
   550  		Data:     tx.Data(),
   551  	}
   552  	_, txError := b.CallContract(context.Background(), callMsg, receipt.BlockNumber)
   553  	if txError == nil {
   554  		return "", fmt.Errorf("no error in CallContract: %w", err)
   555  	}
   556  	return RPCErrorFromError(txError)
   557  }
   558  
   559  func RPCErrorFromError(txError error) (string, error) {
   560  	errBytes, err := json.Marshal(txError)
   561  	if err != nil {
   562  		return "", err
   563  	}
   564  	var callErr struct {
   565  		Code    int
   566  		Data    string `json:"data"`
   567  		Message string `json:"message"`
   568  	}
   569  	err = json.Unmarshal(errBytes, &callErr)
   570  	if err != nil {
   571  		return "", err
   572  	}
   573  	// If the error data is blank
   574  	if len(callErr.Data) == 0 {
   575  		return callErr.Data, nil
   576  	}
   577  	// Some nodes prepend "Reverted " and we also remove the 0x
   578  	trimmed := strings.TrimPrefix(callErr.Data, "Reverted ")[2:]
   579  	data, err := hex.DecodeString(trimmed)
   580  	if err != nil {
   581  		return "", err
   582  	}
   583  	revert, err := abi.UnpackRevert(data)
   584  	// If we can't decode the revert reason, return the raw data
   585  	if err != nil {
   586  		return callErr.Data, nil
   587  	}
   588  	return revert, nil
   589  }