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