github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/rpc/core/mempool.go (about)

     1  package core
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  	"time"
     7  
     8  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
     9  	ctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/core/types"
    10  	rpctypes "github.com/gnolang/gno/tm2/pkg/bft/rpc/lib/types"
    11  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    12  	"github.com/gnolang/gno/tm2/pkg/errors"
    13  	"github.com/gnolang/gno/tm2/pkg/events"
    14  	"github.com/gnolang/gno/tm2/pkg/random"
    15  	"github.com/gnolang/gno/tm2/pkg/service"
    16  )
    17  
    18  // -----------------------------------------------------------------------------
    19  // NOTE: tx should be signed, but this is only checked at the app level (not by Tendermint!)
    20  
    21  // Returns right away, with no response. Does not wait for CheckTx nor
    22  // DeliverTx results.
    23  //
    24  // If you want to be sure that the transaction is included in a block, you can
    25  // subscribe for the result using JSONRPC via a websocket. See
    26  // https://tendermint.com/docs/app-dev/subscribing-to-events-via-websocket.html
    27  // If you haven't received anything after a couple of blocks, resend it. If the
    28  // same happens again, send it to some other node. A few reasons why it could
    29  // happen:
    30  //
    31  // 1. malicious node can drop or pretend it had committed your tx
    32  // 2. malicious proposer (not necessary the one you're communicating with) can
    33  // drop transactions, which might become valid in the future
    34  // (https://github.com/gnolang/gno/tm2/pkg/bft/issues/3322)
    35  // 3. node can be offline
    36  //
    37  // Please refer to
    38  // https://tendermint.com/docs/tendermint-core/using-tendermint.html#formatting
    39  // for formatting/encoding rules.
    40  //
    41  // ```shell
    42  // curl 'localhost:26657/broadcast_tx_async?tx="123"'
    43  // ```
    44  //
    45  // ```go
    46  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
    47  // err := client.Start()
    48  //
    49  //	if err != nil {
    50  //	  // handle error
    51  //	}
    52  //
    53  // defer client.Stop()
    54  // result, err := client.BroadcastTxAsync("123")
    55  // ```
    56  //
    57  // > The above command returns JSON structured like this:
    58  //
    59  // ```json
    60  //
    61  //	{
    62  //		"error": "",
    63  //		"result": {
    64  //			"hash": "E39AAB7A537ABAA237831742DCE1117F187C3C52",
    65  //			"log": "",
    66  //			"data": "",
    67  //			"code": "0"
    68  //		},
    69  //		"id": "",
    70  //		"jsonrpc": "2.0"
    71  //	}
    72  //
    73  // ```
    74  //
    75  // ### Query Parameters
    76  //
    77  // | Parameter | Type | Default | Required | Description     |
    78  // |-----------+------+---------+----------+-----------------|
    79  // | tx        | Tx   | nil     | true     | The transaction |
    80  func BroadcastTxAsync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
    81  	err := mempool.CheckTx(tx, nil)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	return &ctypes.ResultBroadcastTx{Hash: tx.Hash()}, nil
    86  }
    87  
    88  // Returns with the response from CheckTx. Does not wait for DeliverTx result.
    89  //
    90  // If you want to be sure that the transaction is included in a block, you can
    91  // subscribe for the result using JSONRPC via a websocket. See
    92  // https://tendermint.com/docs/app-dev/subscribing-to-events-via-websocket.html
    93  // If you haven't received anything after a couple of blocks, resend it. If the
    94  // same happens again, send it to some other node. A few reasons why it could
    95  // happen:
    96  //
    97  // 1. malicious node can drop or pretend it had committed your tx
    98  // 2. malicious proposer (not necessary the one you're communicating with) can
    99  // drop transactions, which might become valid in the future
   100  // (https://github.com/gnolang/gno/tm2/pkg/bft/issues/3322)
   101  //
   102  // Please refer to
   103  // https://tendermint.com/docs/tendermint-core/using-tendermint.html#formatting
   104  // for formatting/encoding rules.
   105  //
   106  // ```shell
   107  // curl 'localhost:26657/broadcast_tx_sync?tx="456"'
   108  // ```
   109  //
   110  // ```go
   111  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   112  // err := client.Start()
   113  //
   114  //	if err != nil {
   115  //	  // handle error
   116  //	}
   117  //
   118  // defer client.Stop()
   119  // result, err := client.BroadcastTxSync("456")
   120  // ```
   121  //
   122  // > The above command returns JSON structured like this:
   123  //
   124  // ```json
   125  //
   126  //	{
   127  //		"jsonrpc": "2.0",
   128  //		"id": "",
   129  //		"result": {
   130  //			"code": "0",
   131  //			"data": "",
   132  //			"log": "",
   133  //			"hash": "0D33F2F03A5234F38706E43004489E061AC40A2E"
   134  //		},
   135  //		"error": ""
   136  //	}
   137  //
   138  // ```
   139  //
   140  // ### Query Parameters
   141  //
   142  // | Parameter | Type | Default | Required | Description     |
   143  // |-----------+------+---------+----------+-----------------|
   144  // | tx        | Tx   | nil     | true     | The transaction |
   145  func BroadcastTxSync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
   146  	resCh := make(chan abci.Response, 1)
   147  	err := mempool.CheckTx(tx, func(res abci.Response) {
   148  		resCh <- res
   149  	})
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	res := <-resCh
   154  	r := res.(abci.ResponseCheckTx)
   155  	return &ctypes.ResultBroadcastTx{
   156  		Error: r.Error,
   157  		Data:  r.Data,
   158  		Log:   r.Log,
   159  		Hash:  tx.Hash(),
   160  	}, nil
   161  }
   162  
   163  // Returns with the responses from CheckTx and DeliverTx.
   164  //
   165  // IMPORTANT: use only for testing and development. In production, use
   166  // BroadcastTxSync or BroadcastTxAsync. You can subscribe for the transaction
   167  // result using JSONRPC via a websocket. See
   168  // https://tendermint.com/docs/app-dev/subscribing-to-events-via-websocket.html
   169  //
   170  // CONTRACT: only returns error if mempool.CheckTx() errs or if we timeout
   171  // waiting for tx to commit.
   172  //
   173  // If CheckTx or DeliverTx fail, no error will be returned, but the returned result
   174  // will contain a non-OK ABCI code.
   175  //
   176  // Please refer to
   177  // https://tendermint.com/docs/tendermint-core/using-tendermint.html#formatting
   178  // for formatting/encoding rules.
   179  //
   180  // ```shell
   181  // curl 'localhost:26657/broadcast_tx_commit?tx="789"'
   182  // ```
   183  //
   184  // ```go
   185  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   186  // err := client.Start()
   187  //
   188  //	if err != nil {
   189  //	  // handle error
   190  //	}
   191  //
   192  // defer client.Stop()
   193  // result, err := client.BroadcastTxCommit("789")
   194  // ```
   195  //
   196  // > The above command returns JSON structured like this:
   197  //
   198  // ```json
   199  //
   200  //	{
   201  //		"error": "",
   202  //		"result": {
   203  //			"height": "26682",
   204  //			"hash": "75CA0F856A4DA078FC4911580360E70CEFB2EBEE",
   205  //			"deliver_tx": {
   206  //				"log": "",
   207  //				"data": "",
   208  //				"code": "0"
   209  //			},
   210  //			"check_tx": {
   211  //				"log": "",
   212  //				"data": "",
   213  //				"code": "0"
   214  //			}
   215  //		},
   216  //		"id": "",
   217  //		"jsonrpc": "2.0"
   218  //	}
   219  //
   220  // ```
   221  //
   222  // ### Query Parameters
   223  //
   224  // | Parameter | Type | Default | Required | Description     |
   225  // |-----------+------+---------+----------+-----------------|
   226  // | tx        | Tx   | nil     | true     | The transaction |
   227  func BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
   228  	// Broadcast tx and wait for CheckTx result
   229  	checkTxResCh := make(chan abci.Response, 1)
   230  	err := mempool.CheckTx(tx, func(res abci.Response) {
   231  		checkTxResCh <- res
   232  	})
   233  	if err != nil {
   234  		logger.Error("Error on broadcastTxCommit", "err", err)
   235  		return nil, fmt.Errorf("error on broadcastTxCommit: %w", err)
   236  	}
   237  	checkTxResMsg := <-checkTxResCh
   238  	checkTxRes := checkTxResMsg.(abci.ResponseCheckTx)
   239  	if checkTxRes.Error != nil {
   240  		return &ctypes.ResultBroadcastTxCommit{
   241  			CheckTx:   checkTxRes,
   242  			DeliverTx: abci.ResponseDeliverTx{},
   243  			Hash:      tx.Hash(),
   244  		}, nil
   245  	}
   246  
   247  	// Wait for the tx to be included in a block or timeout.
   248  	txRes, err := gTxDispatcher.getTxResult(tx, nil)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  	return &ctypes.ResultBroadcastTxCommit{
   253  		CheckTx:   checkTxRes,
   254  		DeliverTx: txRes.Response,
   255  		Hash:      tx.Hash(),
   256  		Height:    txRes.Height,
   257  	}, nil
   258  }
   259  
   260  // Get unconfirmed transactions (maximum ?limit entries) including their number.
   261  //
   262  // ```shell
   263  // curl 'localhost:26657/unconfirmed_txs'
   264  // ```
   265  //
   266  // ```go
   267  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   268  // err := client.Start()
   269  //
   270  //	if err != nil {
   271  //	  // handle error
   272  //	}
   273  //
   274  // defer client.Stop()
   275  // result, err := client.UnconfirmedTxs()
   276  // ```
   277  //
   278  // > The above command returns JSON structured like this:
   279  //
   280  // ```json
   281  //
   282  //	{
   283  //	  "result" : {
   284  //	      "txs" : [],
   285  //	      "total_bytes" : "0",
   286  //	      "n_txs" : "0",
   287  //	      "total" : "0"
   288  //	    },
   289  //	    "jsonrpc" : "2.0",
   290  //	    "id" : ""
   291  //	  }
   292  //
   293  // ```
   294  //
   295  // ### Query Parameters
   296  //
   297  // | Parameter | Type | Default | Required | Description                          |
   298  // |-----------+------+---------+----------+--------------------------------------|
   299  // | limit     | int  | 30      | false    | Maximum number of entries (max: 100) |
   300  // ```
   301  func UnconfirmedTxs(ctx *rpctypes.Context, limit int) (*ctypes.ResultUnconfirmedTxs, error) {
   302  	// reuse per_page validator
   303  	limit = validatePerPage(limit)
   304  
   305  	txs := mempool.ReapMaxTxs(limit)
   306  	return &ctypes.ResultUnconfirmedTxs{
   307  		Count:      len(txs),
   308  		Total:      mempool.Size(),
   309  		TotalBytes: mempool.TxsBytes(),
   310  		Txs:        txs,
   311  	}, nil
   312  }
   313  
   314  // Get number of unconfirmed transactions.
   315  //
   316  // ```shell
   317  // curl 'localhost:26657/num_unconfirmed_txs'
   318  // ```
   319  //
   320  // ```go
   321  // client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
   322  // err := client.Start()
   323  // if err != nil {
   324  // // handle error
   325  // }
   326  // defer client.Stop()
   327  // result, err := client.UnconfirmedTxs()
   328  // ```
   329  //
   330  // > The above command returns JSON structured like this:
   331  //
   332  // ```json
   333  //
   334  //	{
   335  //	  "jsonrpc" : "2.0",
   336  //	  "id" : "",
   337  //	  "result" : {
   338  //	    "n_txs" : "0",
   339  //	    "total_bytes" : "0",
   340  //	    "total" : "0"
   341  //	    "txs" : null,
   342  //	  }
   343  //	}
   344  //
   345  // ```
   346  func NumUnconfirmedTxs(ctx *rpctypes.Context) (*ctypes.ResultUnconfirmedTxs, error) {
   347  	return &ctypes.ResultUnconfirmedTxs{
   348  		Count:      mempool.Size(),
   349  		Total:      mempool.Size(),
   350  		TotalBytes: mempool.TxsBytes(),
   351  	}, nil
   352  }
   353  
   354  // ----------------------------------------
   355  // txListener
   356  
   357  // NOTE: txDispatcher doesn't handle any throttling or resource management.
   358  // The RPC websockets system is expected to throttle requests.
   359  type txDispatcher struct {
   360  	service.BaseService
   361  	evsw       events.EventSwitch
   362  	listenerID string
   363  	sub        <-chan events.Event
   364  
   365  	mtx     sync.Mutex
   366  	waiters map[string]*txWaiter // string(types.Tx) -> *txWaiter
   367  }
   368  
   369  func newTxDispatcher(evsw events.EventSwitch) *txDispatcher {
   370  	listenerID := fmt.Sprintf("txDispatcher#%v", random.RandStr(6))
   371  	sub := events.SubscribeToEvent(evsw, listenerID, types.EventTx{})
   372  
   373  	td := &txDispatcher{
   374  		evsw:       evsw,
   375  		listenerID: listenerID,
   376  		sub:        sub,
   377  		waiters:    make(map[string]*txWaiter),
   378  	}
   379  	td.BaseService = *service.NewBaseService(nil, "txDispatcher", td)
   380  	err := td.Start()
   381  	if err != nil {
   382  		panic(err)
   383  	}
   384  	return td
   385  }
   386  
   387  func (td *txDispatcher) OnStart() error {
   388  	go td.listenRoutine()
   389  	return nil
   390  }
   391  
   392  func (td *txDispatcher) OnStop() {
   393  	td.evsw.RemoveListener(td.listenerID)
   394  }
   395  
   396  func (td *txDispatcher) listenRoutine() {
   397  	for {
   398  		select {
   399  		case event, ok := <-td.sub:
   400  			if !ok {
   401  				td.Stop()
   402  				panic("txDispatcher subscription unexpectedly closed")
   403  			}
   404  			txEvent := event.(types.EventTx)
   405  			td.notifyTxEvent(txEvent)
   406  		case <-td.Quit():
   407  			return
   408  		}
   409  	}
   410  }
   411  
   412  func (td *txDispatcher) notifyTxEvent(txEvent types.EventTx) {
   413  	td.mtx.Lock()
   414  	defer td.mtx.Unlock()
   415  
   416  	tx := txEvent.Result.Tx
   417  	waiter, ok := td.waiters[string(tx)]
   418  	if !ok {
   419  		return // nothing to do
   420  	} else {
   421  		waiter.txRes = txEvent.Result
   422  		close(waiter.waitCh)
   423  	}
   424  }
   425  
   426  // blocking
   427  // If the tx is already being waited on, returns the result from the original request.
   428  // Upon result or timeout, the tx is forgotten from txDispatcher, and can be re-requested.
   429  // If the tx times out, an error is returned.
   430  // Quit can optionally be provided to terminate early (e.g. if the caller disconnects).
   431  func (td *txDispatcher) getTxResult(tx types.Tx, quit chan struct{}) (types.TxResult, error) {
   432  	// Get or create waiter.
   433  	td.mtx.Lock()
   434  	waiter, ok := td.waiters[string(tx)]
   435  	if !ok {
   436  		waiter = newTxWaiter(tx)
   437  		td.waiters[string(tx)] = waiter
   438  	}
   439  	td.mtx.Unlock()
   440  
   441  	select {
   442  	case <-waiter.waitCh:
   443  		return waiter.txRes, nil
   444  	case <-waiter.timeCh:
   445  		return types.TxResult{}, errors.New("request timeout")
   446  	case <-quit:
   447  		return types.TxResult{}, errors.New("caller quit")
   448  	}
   449  }
   450  
   451  type txWaiter struct {
   452  	tx     types.Tx
   453  	waitCh chan struct{}
   454  	timeCh <-chan time.Time
   455  	txRes  types.TxResult
   456  }
   457  
   458  func newTxWaiter(tx types.Tx) *txWaiter {
   459  	return &txWaiter{
   460  		tx:     tx,
   461  		waitCh: make(chan struct{}),
   462  		timeCh: time.After(config.TimeoutBroadcastTxCommit),
   463  	}
   464  }