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 }