github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/test/e2e/runner/rpc.go (about)

     1  package main
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/ari-anchor/sei-tendermint/libs/log"
    10  	rpchttp "github.com/ari-anchor/sei-tendermint/rpc/client/http"
    11  	rpctypes "github.com/ari-anchor/sei-tendermint/rpc/coretypes"
    12  	e2e "github.com/ari-anchor/sei-tendermint/test/e2e/pkg"
    13  	"github.com/ari-anchor/sei-tendermint/types"
    14  )
    15  
    16  // waitForHeight waits for the network to reach a certain height (or above),
    17  // returning the block at the height seen. Errors if the network is not making
    18  // progress at all.
    19  // If height == 0, the initial height of the test network is used as the target.
    20  func waitForHeight(ctx context.Context, testnet *e2e.Testnet, height int64) (*types.Block, *types.BlockID, error) {
    21  	var (
    22  		err             error
    23  		clients         = map[string]*rpchttp.HTTP{}
    24  		lastHeight      int64
    25  		lastIncrease    = time.Now()
    26  		nodesAtHeight   = map[string]struct{}{}
    27  		numRunningNodes int
    28  	)
    29  	if height == 0 {
    30  		height = testnet.InitialHeight
    31  	}
    32  
    33  	for _, node := range testnet.Nodes {
    34  		if node.Stateless() {
    35  			continue
    36  		}
    37  
    38  		if node.HasStarted {
    39  			numRunningNodes++
    40  		}
    41  	}
    42  
    43  	timer := time.NewTimer(0)
    44  	defer timer.Stop()
    45  	for {
    46  		select {
    47  		case <-ctx.Done():
    48  			return nil, nil, ctx.Err()
    49  		case <-timer.C:
    50  			for _, node := range testnet.Nodes {
    51  				// skip nodes that have reached the target height
    52  				if _, ok := nodesAtHeight[node.Name]; ok {
    53  					continue
    54  				}
    55  
    56  				// skip nodes that don't have state or haven't started yet
    57  				if node.Stateless() {
    58  					continue
    59  				}
    60  				if !node.HasStarted {
    61  					continue
    62  				}
    63  
    64  				// cache the clients
    65  				client, ok := clients[node.Name]
    66  				if !ok {
    67  					client, err = node.Client()
    68  					if err != nil {
    69  						continue
    70  					}
    71  					clients[node.Name] = client
    72  				}
    73  
    74  				result, err := client.Status(ctx)
    75  				if err != nil {
    76  					continue
    77  				}
    78  				if result.SyncInfo.LatestBlockHeight > lastHeight {
    79  					lastHeight = result.SyncInfo.LatestBlockHeight
    80  					lastIncrease = time.Now()
    81  				}
    82  
    83  				if result.SyncInfo.LatestBlockHeight >= height {
    84  					// the node has achieved the target height!
    85  
    86  					// add this node to the set of target
    87  					// height nodes
    88  					nodesAtHeight[node.Name] = struct{}{}
    89  
    90  					// if not all of the nodes that we
    91  					// have clients for have reached the
    92  					// target height, keep trying.
    93  					if numRunningNodes > len(nodesAtHeight) {
    94  						continue
    95  					}
    96  
    97  					// All nodes are at or above the target height. Now fetch the block for that target height
    98  					// and return it. We loop again through all clients because some may have pruning set but
    99  					// at least two of them should be archive nodes.
   100  					for _, c := range clients {
   101  						result, err := c.Block(ctx, &height)
   102  						if err != nil || result == nil || result.Block == nil {
   103  							continue
   104  						}
   105  						return result.Block, &result.BlockID, err
   106  					}
   107  				}
   108  			}
   109  
   110  			if len(clients) == 0 {
   111  				return nil, nil, errors.New("unable to connect to any network nodes")
   112  			}
   113  			if time.Since(lastIncrease) >= time.Minute {
   114  				if lastHeight == 0 {
   115  					return nil, nil, errors.New("chain stalled at unknown height (most likely upon starting)")
   116  				}
   117  
   118  				return nil, nil, fmt.Errorf("chain stalled at height %v [%d of %d nodes %+v]",
   119  					lastHeight,
   120  					len(nodesAtHeight),
   121  					numRunningNodes,
   122  					nodesAtHeight)
   123  
   124  			}
   125  			timer.Reset(1 * time.Second)
   126  		}
   127  	}
   128  }
   129  
   130  // waitForNode waits for a node to become available and catch up to the given block height.
   131  func waitForNode(ctx context.Context, logger log.Logger, node *e2e.Node, height int64) (*rpctypes.ResultStatus, error) {
   132  	// If the node is the light client or seed note, we do not check for the last height.
   133  	// The light client and seed note can be behind the full node and validator
   134  	if node.Mode == e2e.ModeSeed {
   135  		return nil, nil
   136  	}
   137  	client, err := node.Client()
   138  	if err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	timer := time.NewTimer(0)
   143  	defer timer.Stop()
   144  
   145  	var (
   146  		lastFailed bool
   147  		counter    int
   148  	)
   149  	for {
   150  		counter++
   151  		if lastFailed {
   152  			lastFailed = false
   153  
   154  			// if there was a problem with the request in
   155  			// the previous recreate the client to ensure
   156  			// reconnection
   157  			client, err = node.Client()
   158  			if err != nil {
   159  				return nil, err
   160  			}
   161  		}
   162  
   163  		select {
   164  		case <-ctx.Done():
   165  			return nil, ctx.Err()
   166  		case <-timer.C:
   167  			status, err := client.Status(ctx)
   168  			switch {
   169  			case errors.Is(err, context.DeadlineExceeded):
   170  				return nil, fmt.Errorf("timed out waiting for %v to reach height %v", node.Name, height)
   171  			case errors.Is(err, context.Canceled):
   172  				return nil, err
   173  				// If the node is the light client, it is not essential to wait for it to catch up, but we must return status info
   174  			case err == nil && node.Mode == e2e.ModeLight:
   175  				return status, nil
   176  			case err == nil && node.Mode != e2e.ModeLight && status.SyncInfo.LatestBlockHeight >= height:
   177  				return status, nil
   178  			case counter%500 == 0:
   179  				switch {
   180  				case err != nil:
   181  					lastFailed = true
   182  					logger.Error("node not yet ready",
   183  						"iter", counter,
   184  						"node", node.Name,
   185  						"target", height,
   186  						"err", err,
   187  					)
   188  				case status != nil:
   189  					logger.Info("node not yet ready",
   190  						"iter", counter,
   191  						"node", node.Name,
   192  						"height", status.SyncInfo.LatestBlockHeight,
   193  						"target", height,
   194  					)
   195  				}
   196  			}
   197  			timer.Reset(250 * time.Millisecond)
   198  		}
   199  	}
   200  }
   201  
   202  // getLatestBlock returns the last block that all active nodes in the network have
   203  // agreed upon i.e. the earlist of each nodes latest block
   204  func getLatestBlock(ctx context.Context, testnet *e2e.Testnet) (*types.Block, error) {
   205  	var earliestBlock *types.Block
   206  	for _, node := range testnet.Nodes {
   207  		// skip nodes that don't have state or haven't started yet
   208  		if node.Stateless() {
   209  			continue
   210  		}
   211  		if !node.HasStarted {
   212  			continue
   213  		}
   214  
   215  		client, err := node.Client()
   216  		if err != nil {
   217  			return nil, err
   218  		}
   219  
   220  		wctx, cancel := context.WithTimeout(ctx, 10*time.Second)
   221  		defer cancel()
   222  		result, err := client.Block(wctx, nil)
   223  		if err != nil {
   224  			return nil, err
   225  		}
   226  
   227  		if result.Block != nil && (earliestBlock == nil || earliestBlock.Height > result.Block.Height) {
   228  			earliestBlock = result.Block
   229  		}
   230  	}
   231  	return earliestBlock, nil
   232  }