github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/rpc/core/mempool.go (about)

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	abci "github.com/badrootd/nibiru-cometbft/abci/types"
    10  	mempl "github.com/badrootd/nibiru-cometbft/mempool"
    11  	ctypes "github.com/badrootd/nibiru-cometbft/rpc/core/types"
    12  	rpctypes "github.com/badrootd/nibiru-cometbft/rpc/jsonrpc/types"
    13  	"github.com/badrootd/nibiru-cometbft/types"
    14  )
    15  
    16  //-----------------------------------------------------------------------------
    17  // NOTE: tx should be signed, but this is only checked at the app level (not by CometBFT!)
    18  
    19  // BroadcastTxAsync returns right away, with no response. Does not wait for
    20  // CheckTx nor DeliverTx results.
    21  // More: https://docs.cometbft.com/v0.37/rpc/#/Tx/broadcast_tx_async
    22  func BroadcastTxAsync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    23  	err := env.Mempool.CheckTx(tx, nil, mempl.TxInfo{})
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  	return &ctypes.ResultBroadcastTx{Hash: tx.Hash()}, nil
    28  }
    29  
    30  // BroadcastTxSync returns with the response from CheckTx. Does not wait for
    31  // DeliverTx result.
    32  // More: https://docs.cometbft.com/v0.37/rpc/#/Tx/broadcast_tx_sync
    33  func BroadcastTxSync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    34  	resCh := make(chan *abci.Response, 1)
    35  	err := env.Mempool.CheckTx(tx, func(res *abci.Response) {
    36  		select {
    37  		case <-ctx.Context().Done():
    38  		case resCh <- res:
    39  		}
    40  	}, mempl.TxInfo{})
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	select {
    46  	case <-ctx.Context().Done():
    47  		return nil, fmt.Errorf("broadcast confirmation not received: %w", ctx.Context().Err())
    48  	case res := <-resCh:
    49  		r := res.GetCheckTx()
    50  		return &ctypes.ResultBroadcastTx{
    51  			Code:      r.Code,
    52  			Data:      r.Data,
    53  			Log:       r.Log,
    54  			Codespace: r.Codespace,
    55  			Hash:      tx.Hash(),
    56  		}, nil
    57  	}
    58  }
    59  
    60  // BroadcastTxCommit returns with the responses from CheckTx and DeliverTx.
    61  // More: https://docs.cometbft.com/v0.37/rpc/#/Tx/broadcast_tx_commit
    62  func BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
    63  	subscriber := ctx.RemoteAddr()
    64  
    65  	if env.EventBus.NumClients() >= env.Config.MaxSubscriptionClients {
    66  		return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients)
    67  	} else if env.EventBus.NumClientSubscriptions(subscriber) >= env.Config.MaxSubscriptionsPerClient {
    68  		return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient)
    69  	}
    70  
    71  	// Subscribe to tx being committed in block.
    72  	subCtx, cancel := context.WithTimeout(ctx.Context(), SubscribeTimeout)
    73  	defer cancel()
    74  	q := types.EventQueryTxFor(tx)
    75  	deliverTxSub, err := env.EventBus.Subscribe(subCtx, subscriber, q)
    76  	if err != nil {
    77  		err = fmt.Errorf("failed to subscribe to tx: %w", err)
    78  		env.Logger.Error("Error on broadcast_tx_commit", "err", err)
    79  		return nil, err
    80  	}
    81  	defer func() {
    82  		if err := env.EventBus.Unsubscribe(context.Background(), subscriber, q); err != nil {
    83  			env.Logger.Error("Error unsubscribing from eventBus", "err", err)
    84  		}
    85  	}()
    86  
    87  	// Broadcast tx and wait for CheckTx result
    88  	checkTxResCh := make(chan *abci.Response, 1)
    89  	err = env.Mempool.CheckTx(tx, func(res *abci.Response) {
    90  		select {
    91  		case <-ctx.Context().Done():
    92  		case checkTxResCh <- res:
    93  		}
    94  	}, mempl.TxInfo{})
    95  	if err != nil {
    96  		env.Logger.Error("Error on broadcastTxCommit", "err", err)
    97  		return nil, fmt.Errorf("error on broadcastTxCommit: %v", err)
    98  	}
    99  	select {
   100  	case <-ctx.Context().Done():
   101  		return nil, fmt.Errorf("broadcast confirmation not received: %w", ctx.Context().Err())
   102  	case checkTxResMsg := <-checkTxResCh:
   103  		checkTxRes := checkTxResMsg.GetCheckTx()
   104  		if checkTxRes.Code != abci.CodeTypeOK {
   105  			return &ctypes.ResultBroadcastTxCommit{
   106  				CheckTx:   *checkTxRes,
   107  				DeliverTx: abci.ResponseDeliverTx{},
   108  				Hash:      tx.Hash(),
   109  			}, nil
   110  		}
   111  
   112  		// Wait for the tx to be included in a block or timeout.
   113  		select {
   114  		case msg := <-deliverTxSub.Out(): // The tx was included in a block.
   115  			deliverTxRes := msg.Data().(types.EventDataTx)
   116  			return &ctypes.ResultBroadcastTxCommit{
   117  				CheckTx:   *checkTxRes,
   118  				DeliverTx: deliverTxRes.Result,
   119  				Hash:      tx.Hash(),
   120  				Height:    deliverTxRes.Height,
   121  			}, nil
   122  		case <-deliverTxSub.Cancelled():
   123  			var reason string
   124  			if deliverTxSub.Err() == nil {
   125  				reason = "CometBFT exited"
   126  			} else {
   127  				reason = deliverTxSub.Err().Error()
   128  			}
   129  			err = fmt.Errorf("deliverTxSub was canceled (reason: %s)", reason)
   130  			env.Logger.Error("Error on broadcastTxCommit", "err", err)
   131  			return &ctypes.ResultBroadcastTxCommit{
   132  				CheckTx:   *checkTxRes,
   133  				DeliverTx: abci.ResponseDeliverTx{},
   134  				Hash:      tx.Hash(),
   135  			}, err
   136  		case <-time.After(env.Config.TimeoutBroadcastTxCommit):
   137  			err = errors.New("timed out waiting for tx to be included in a block")
   138  			env.Logger.Error("Error on broadcastTxCommit", "err", err)
   139  			return &ctypes.ResultBroadcastTxCommit{
   140  				CheckTx:   *checkTxRes,
   141  				DeliverTx: abci.ResponseDeliverTx{},
   142  				Hash:      tx.Hash(),
   143  			}, err
   144  		}
   145  	}
   146  }
   147  
   148  // UnconfirmedTxs gets unconfirmed transactions (maximum ?limit entries)
   149  // including their number.
   150  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/unconfirmed_txs
   151  func UnconfirmedTxs(ctx *rpctypes.Context, limitPtr *int) (*ctypes.ResultUnconfirmedTxs, error) {
   152  	// reuse per_page validator
   153  	limit := validatePerPage(limitPtr)
   154  
   155  	txs := env.Mempool.ReapMaxTxs(limit)
   156  	return &ctypes.ResultUnconfirmedTxs{
   157  		Count:      len(txs),
   158  		Total:      env.Mempool.Size(),
   159  		TotalBytes: env.Mempool.SizeBytes(),
   160  		Txs:        txs,
   161  	}, nil
   162  }
   163  
   164  // NumUnconfirmedTxs gets number of unconfirmed transactions.
   165  // More: https://docs.cometbft.com/v0.37/rpc/#/Info/num_unconfirmed_txs
   166  func NumUnconfirmedTxs(ctx *rpctypes.Context) (*ctypes.ResultUnconfirmedTxs, error) {
   167  	return &ctypes.ResultUnconfirmedTxs{
   168  		Count:      env.Mempool.Size(),
   169  		Total:      env.Mempool.Size(),
   170  		TotalBytes: env.Mempool.SizeBytes(),
   171  	}, nil
   172  }
   173  
   174  // CheckTx checks the transaction without executing it. The transaction won't
   175  // be added to the mempool either.
   176  // More: https://docs.cometbft.com/v0.37/rpc/#/Tx/check_tx
   177  func CheckTx(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) {
   178  	res, err := env.ProxyAppMempool.CheckTxSync(abci.RequestCheckTx{Tx: tx})
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  	return &ctypes.ResultCheckTx{ResponseCheckTx: *res}, nil
   183  }