mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-05 11:31:16 +00:00
This changes adds an `MempoolError` field to the `ResponseCheckTx`. This will allow clients to understand that their transaction was rejected from the mempool despite passing the ABCI check. This change also updates the code to make use of early returns to prevent highly nested code blocks. Namely, it returns when the type assertion fails at the beginning of the method, instead of wrapping the entire method in a large if statement. This has a somewhat large effect on the diff as rendered by github. addresses: #3546
181 lines
6.1 KiB
Go
181 lines
6.1 KiB
Go
package core
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
mempl "github.com/tendermint/tendermint/internal/mempool"
|
|
tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
|
|
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
|
|
"github.com/tendermint/tendermint/types"
|
|
)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// NOTE: tx should be signed, but this is only checked at the app level (not by Tendermint!)
|
|
|
|
// BroadcastTxAsync returns right away, with no response. Does not wait for
|
|
// CheckTx nor DeliverTx results.
|
|
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_async
|
|
func (env *Environment) BroadcastTxAsync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
|
err := env.Mempool.CheckTx(ctx.Context(), tx, nil, mempl.TxInfo{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ctypes.ResultBroadcastTx{Hash: tx.Hash()}, nil
|
|
}
|
|
|
|
// BroadcastTxSync returns with the response from CheckTx. Does not wait for
|
|
// DeliverTx result.
|
|
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_sync
|
|
func (env *Environment) BroadcastTxSync(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTx, error) {
|
|
resCh := make(chan *abci.Response, 1)
|
|
err := env.Mempool.CheckTx(
|
|
ctx.Context(),
|
|
tx,
|
|
func(res *abci.Response) { resCh <- res },
|
|
mempl.TxInfo{},
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res := <-resCh
|
|
r := res.GetCheckTx()
|
|
|
|
return &ctypes.ResultBroadcastTx{
|
|
Code: r.Code,
|
|
Data: r.Data,
|
|
Log: r.Log,
|
|
Codespace: r.Codespace,
|
|
MempoolError: r.MempoolError,
|
|
Hash: tx.Hash(),
|
|
}, nil
|
|
}
|
|
|
|
// BroadcastTxCommit returns with the responses from CheckTx and DeliverTx.
|
|
// More: https://docs.tendermint.com/master/rpc/#/Tx/broadcast_tx_commit
|
|
func (env *Environment) BroadcastTxCommit(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) {
|
|
subscriber := ctx.RemoteAddr()
|
|
|
|
if env.EventBus.NumClients() >= env.Config.MaxSubscriptionClients {
|
|
return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients)
|
|
} else if env.EventBus.NumClientSubscriptions(subscriber) >= env.Config.MaxSubscriptionsPerClient {
|
|
return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient)
|
|
}
|
|
|
|
// Subscribe to tx being committed in block.
|
|
subCtx, cancel := context.WithTimeout(ctx.Context(), SubscribeTimeout)
|
|
defer cancel()
|
|
q := types.EventQueryTxFor(tx)
|
|
deliverTxSub, err := env.EventBus.Subscribe(subCtx, subscriber, q)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to subscribe to tx: %w", err)
|
|
env.Logger.Error("Error on broadcast_tx_commit", "err", err)
|
|
return nil, err
|
|
}
|
|
defer func() {
|
|
args := tmpubsub.UnsubscribeArgs{Subscriber: subscriber, Query: q}
|
|
if err := env.EventBus.Unsubscribe(context.Background(), args); err != nil {
|
|
env.Logger.Error("Error unsubscribing from eventBus", "err", err)
|
|
}
|
|
}()
|
|
|
|
// Broadcast tx and wait for CheckTx result
|
|
checkTxResCh := make(chan *abci.Response, 1)
|
|
err = env.Mempool.CheckTx(
|
|
ctx.Context(),
|
|
tx,
|
|
func(res *abci.Response) { checkTxResCh <- res },
|
|
mempl.TxInfo{},
|
|
)
|
|
if err != nil {
|
|
env.Logger.Error("Error on broadcastTxCommit", "err", err)
|
|
return nil, fmt.Errorf("error on broadcastTxCommit: %v", err)
|
|
}
|
|
|
|
checkTxResMsg := <-checkTxResCh
|
|
checkTxRes := checkTxResMsg.GetCheckTx()
|
|
|
|
if checkTxRes.Code != abci.CodeTypeOK {
|
|
return &ctypes.ResultBroadcastTxCommit{
|
|
CheckTx: *checkTxRes,
|
|
DeliverTx: abci.ResponseDeliverTx{},
|
|
Hash: tx.Hash(),
|
|
}, nil
|
|
}
|
|
|
|
// Wait for the tx to be included in a block or timeout.
|
|
select {
|
|
case msg := <-deliverTxSub.Out(): // The tx was included in a block.
|
|
deliverTxRes := msg.Data().(types.EventDataTx)
|
|
return &ctypes.ResultBroadcastTxCommit{
|
|
CheckTx: *checkTxRes,
|
|
DeliverTx: deliverTxRes.Result,
|
|
Hash: tx.Hash(),
|
|
Height: deliverTxRes.Height,
|
|
}, nil
|
|
case <-deliverTxSub.Canceled():
|
|
var reason string
|
|
if deliverTxSub.Err() == nil {
|
|
reason = "Tendermint exited"
|
|
} else {
|
|
reason = deliverTxSub.Err().Error()
|
|
}
|
|
err = fmt.Errorf("deliverTxSub was canceled (reason: %s)", reason)
|
|
env.Logger.Error("Error on broadcastTxCommit", "err", err)
|
|
return &ctypes.ResultBroadcastTxCommit{
|
|
CheckTx: *checkTxRes,
|
|
DeliverTx: abci.ResponseDeliverTx{},
|
|
Hash: tx.Hash(),
|
|
}, err
|
|
case <-time.After(env.Config.TimeoutBroadcastTxCommit):
|
|
err = errors.New("timed out waiting for tx to be included in a block")
|
|
env.Logger.Error("Error on broadcastTxCommit", "err", err)
|
|
return &ctypes.ResultBroadcastTxCommit{
|
|
CheckTx: *checkTxRes,
|
|
DeliverTx: abci.ResponseDeliverTx{},
|
|
Hash: tx.Hash(),
|
|
}, err
|
|
}
|
|
}
|
|
|
|
// UnconfirmedTxs gets unconfirmed transactions (maximum ?limit entries)
|
|
// including their number.
|
|
// More: https://docs.tendermint.com/master/rpc/#/Info/unconfirmed_txs
|
|
func (env *Environment) UnconfirmedTxs(ctx *rpctypes.Context, limitPtr *int) (*ctypes.ResultUnconfirmedTxs, error) {
|
|
// reuse per_page validator
|
|
limit := env.validatePerPage(limitPtr)
|
|
|
|
txs := env.Mempool.ReapMaxTxs(limit)
|
|
return &ctypes.ResultUnconfirmedTxs{
|
|
Count: len(txs),
|
|
Total: env.Mempool.Size(),
|
|
TotalBytes: env.Mempool.SizeBytes(),
|
|
Txs: txs}, nil
|
|
}
|
|
|
|
// NumUnconfirmedTxs gets number of unconfirmed transactions.
|
|
// More: https://docs.tendermint.com/master/rpc/#/Info/num_unconfirmed_txs
|
|
func (env *Environment) NumUnconfirmedTxs(ctx *rpctypes.Context) (*ctypes.ResultUnconfirmedTxs, error) {
|
|
return &ctypes.ResultUnconfirmedTxs{
|
|
Count: env.Mempool.Size(),
|
|
Total: env.Mempool.Size(),
|
|
TotalBytes: env.Mempool.SizeBytes()}, nil
|
|
}
|
|
|
|
// CheckTx checks the transaction without executing it. The transaction won't
|
|
// be added to the mempool either.
|
|
// More: https://docs.tendermint.com/master/rpc/#/Tx/check_tx
|
|
func (env *Environment) CheckTx(ctx *rpctypes.Context, tx types.Tx) (*ctypes.ResultCheckTx, error) {
|
|
res, err := env.ProxyAppMempool.CheckTxSync(ctx.Context(), abci.RequestCheckTx{Tx: tx})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ctypes.ResultCheckTx{ResponseCheckTx: *res}, nil
|
|
}
|