diff --git a/internal/consensus/replay_stubs.go b/internal/consensus/replay_stubs.go index 9fce5090a2..99726c3fc0 100644 --- a/internal/consensus/replay_stubs.go +++ b/internal/consensus/replay_stubs.go @@ -39,6 +39,7 @@ func (emptyMempool) Update( ) error { return nil } +func (emptyMempool) GetTxByHash(_ types.TxKey) types.Tx { return nil } func (emptyMempool) Flush() {} func (emptyMempool) FlushAppConn(_ctx context.Context) error { return nil } func (emptyMempool) TxsAvailable() <-chan struct{} { return make(chan struct{}) } diff --git a/internal/mempool/mempool.go b/internal/mempool/mempool.go index c268dff0fd..fd2de9f79d 100644 --- a/internal/mempool/mempool.go +++ b/internal/mempool/mempool.go @@ -260,6 +260,17 @@ func (txmp *TxMempool) CheckTx( return txmp.addNewTransaction(wtx, rsp) } +// GetTxByHash returns transaction by key from the mempool. If the transaction +// does not exist, it returns nil. +func (txmp *TxMempool) GetTxByHash(txHash types.TxKey) types.Tx { + if elt, ok := txmp.txByKey[txHash]; ok { + w := elt.Value.(*WrappedTx) + return w.tx + } + + return nil +} + // RemoveTxByKey removes the transaction with the specified key from the // mempool. It reports an error if no such transaction exists. This operation // does not remove the transaction from the cache. diff --git a/internal/mempool/mocks/mempool.go b/internal/mempool/mocks/mempool.go index b8a04669f3..db1c0fdc98 100644 --- a/internal/mempool/mocks/mempool.go +++ b/internal/mempool/mocks/mempool.go @@ -186,6 +186,54 @@ func (_c *Mempool_FlushAppConn_Call) RunAndReturn(run func(context.Context) erro return _c } +// GetTxByHash provides a mock function with given fields: txHash +func (_m *Mempool) GetTxByHash(txHash types.TxKey) types.Tx { + ret := _m.Called(txHash) + + if len(ret) == 0 { + panic("no return value specified for GetTxByHash") + } + + var r0 types.Tx + if rf, ok := ret.Get(0).(func(types.TxKey) types.Tx); ok { + r0 = rf(txHash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(types.Tx) + } + } + + return r0 +} + +// Mempool_GetTxByHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTxByHash' +type Mempool_GetTxByHash_Call struct { + *mock.Call +} + +// GetTxByHash is a helper method to define mock.On call +// - txHash types.TxKey +func (_e *Mempool_Expecter) GetTxByHash(txHash interface{}) *Mempool_GetTxByHash_Call { + return &Mempool_GetTxByHash_Call{Call: _e.mock.On("GetTxByHash", txHash)} +} + +func (_c *Mempool_GetTxByHash_Call) Run(run func(txHash types.TxKey)) *Mempool_GetTxByHash_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(types.TxKey)) + }) + return _c +} + +func (_c *Mempool_GetTxByHash_Call) Return(_a0 types.Tx) *Mempool_GetTxByHash_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Mempool_GetTxByHash_Call) RunAndReturn(run func(types.TxKey) types.Tx) *Mempool_GetTxByHash_Call { + _c.Call.Return(run) + return _c +} + // Lock provides a mock function with no fields func (_m *Mempool) Lock() { _m.Called() diff --git a/internal/mempool/types.go b/internal/mempool/types.go index 48dc52041d..a51d5e400a 100644 --- a/internal/mempool/types.go +++ b/internal/mempool/types.go @@ -40,6 +40,9 @@ type Mempool interface { // from the mempool. RemoveTxByKey(txKey types.TxKey) error + // GetTxByHash returns a transaction from the mempool, if it exists, or nil. + GetTxByHash(txHash types.TxKey) types.Tx + // ReapMaxBytesMaxGas reaps transactions from the mempool up to maxBytes // bytes total with the condition that the total gasWanted must be less than // maxGas. diff --git a/internal/rpc/core/doc.go b/internal/rpc/core/doc.go index 77ace4e2cf..25cfe1365c 100644 --- a/internal/rpc/core/doc.go +++ b/internal/rpc/core/doc.go @@ -24,6 +24,7 @@ Available endpoints: /status /health /unconfirmed_txs +/unconfirmed_tx /unsafe_flush_mempool /validators diff --git a/internal/rpc/core/mempool.go b/internal/rpc/core/mempool.go index 464e15b3ef..844836a896 100644 --- a/internal/rpc/core/mempool.go +++ b/internal/rpc/core/mempool.go @@ -8,10 +8,12 @@ import ( "time" abci "github.com/dashpay/tenderdash/abci/types" + "github.com/dashpay/tenderdash/crypto" "github.com/dashpay/tenderdash/internal/mempool" "github.com/dashpay/tenderdash/internal/state/indexer" tmmath "github.com/dashpay/tenderdash/libs/math" "github.com/dashpay/tenderdash/rpc/coretypes" + "github.com/dashpay/tenderdash/types" ) //----------------------------------------------------------------------------- @@ -171,7 +173,7 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, req *coretypes.Re } } -// UnconfirmedTxs gets unconfirmed transactions from the mempool in order of priority +// UnconfirmedTxs gets unconfirmed transactions from the mempool in order of priority. // More: https://docs.tendermint.com/master/rpc/#/Info/unconfirmed_txs func (env *Environment) UnconfirmedTxs(_ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) { totalCount := env.Mempool.Size() @@ -182,7 +184,6 @@ func (env *Environment) UnconfirmedTxs(_ctx context.Context, req *coretypes.Requ } skipCount := validateSkipCount(page, perPage) - txs := env.Mempool.ReapMaxTxs(skipCount + tmmath.MinInt(perPage, totalCount-skipCount)) result := txs[skipCount:] @@ -194,6 +195,20 @@ func (env *Environment) UnconfirmedTxs(_ctx context.Context, req *coretypes.Requ }, nil } +// return single unconfirmed transaction, matching req.TxHash +func (env *Environment) UnconfirmedTx(_ctx context.Context, req *coretypes.RequestUnconfirmedTx) (*coretypes.ResultUnconfirmedTx, error) { + if req == nil || req.TxHash.IsZero() || len(req.TxHash) != crypto.HashSize { + return nil, fmt.Errorf("you must provide a valid %d-byte transaction hash in hash= argument", crypto.HashSize) + } + + tx := env.Mempool.GetTxByHash(types.TxKey(req.TxHash)) + if tx == nil { + return nil, fmt.Errorf("transaction %X not found", req.TxHash) + } + + return &coretypes.ResultUnconfirmedTx{Tx: tx}, nil +} + // NumUnconfirmedTxs gets number of unconfirmed transactions. // More: https://docs.tendermint.com/master/rpc/#/Info/num_unconfirmed_txs func (env *Environment) NumUnconfirmedTxs(_ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { diff --git a/internal/rpc/core/routes.go b/internal/rpc/core/routes.go index 49c493e1fa..e0cc9a6e8c 100644 --- a/internal/rpc/core/routes.go +++ b/internal/rpc/core/routes.go @@ -56,6 +56,7 @@ func NewRoutesMap(svc RPCService, opts *RouteOptions) RoutesMap { "consensus_state": rpc.NewRPCFunc(svc.GetConsensusState), "consensus_params": rpc.NewRPCFunc(svc.ConsensusParams), "unconfirmed_txs": rpc.NewRPCFunc(svc.UnconfirmedTxs), + "unconfirmed_tx": rpc.NewRPCFunc(svc.UnconfirmedTx), "num_unconfirmed_txs": rpc.NewRPCFunc(svc.NumUnconfirmedTxs), // tx broadcast API @@ -113,6 +114,7 @@ type RPCService interface { Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) + UnconfirmedTx(ctx context.Context, req *coretypes.RequestUnconfirmedTx) (*coretypes.ResultUnconfirmedTx, error) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUnsubscribe, error) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) diff --git a/light/proxy/routes.go b/light/proxy/routes.go index 3bc23f1251..5e83134058 100644 --- a/light/proxy/routes.go +++ b/light/proxy/routes.go @@ -140,6 +140,10 @@ func (p proxyService) UnconfirmedTxs(ctx context.Context, req *coretypes.Request return p.Client.UnconfirmedTxs(ctx, req.Page.IntPtr(), req.PerPage.IntPtr()) } +func (p proxyService) UnconfirmedTx(ctx context.Context, req *coretypes.RequestUnconfirmedTx) (*coretypes.ResultUnconfirmedTx, error) { + return p.Client.UnconfirmedTx(ctx, req.TxHash) +} + func (p proxyService) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) { return p.Client.UnsubscribeWS(ctx, req.Query) } diff --git a/light/rpc/client.go b/light/rpc/client.go index af282d6fa6..9a4702ec20 100644 --- a/light/rpc/client.go +++ b/light/rpc/client.go @@ -240,6 +240,10 @@ func (c *Client) UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coret return c.next.UnconfirmedTxs(ctx, page, perPage) } +func (c *Client) UnconfirmedTx(ctx context.Context, txHash []byte) (*coretypes.ResultUnconfirmedTx, error) { + return c.next.UnconfirmedTx(ctx, txHash) +} + func (c *Client) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { return c.next.NumUnconfirmedTxs(ctx) } diff --git a/rpc/client/http/http.go b/rpc/client/http/http.go index 7708e96196..069c79555c 100644 --- a/rpc/client/http/http.go +++ b/rpc/client/http/http.go @@ -7,7 +7,7 @@ import ( "net/http" "time" - "github.com/dashpay/tenderdash/libs/bytes" + tmbytes "github.com/dashpay/tenderdash/libs/bytes" rpcclient "github.com/dashpay/tenderdash/rpc/client" "github.com/dashpay/tenderdash/rpc/coretypes" jsonrpcclient "github.com/dashpay/tenderdash/rpc/jsonrpc/client" @@ -208,11 +208,11 @@ func (c *baseRPCClient) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo return result, nil } -func (c *baseRPCClient) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes) (*coretypes.ResultABCIQuery, error) { +func (c *baseRPCClient) ABCIQuery(ctx context.Context, path string, data tmbytes.HexBytes) (*coretypes.ResultABCIQuery, error) { return c.ABCIQueryWithOptions(ctx, path, data, rpcclient.DefaultABCIQueryOptions) } -func (c *baseRPCClient) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { +func (c *baseRPCClient) ABCIQueryWithOptions(ctx context.Context, path string, data tmbytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) { result := new(coretypes.ResultABCIQuery) if err := c.caller.Call(ctx, "abci_query", &coretypes.RequestABCIQuery{ Path: path, @@ -267,6 +267,17 @@ func (c *baseRPCClient) UnconfirmedTxs(ctx context.Context, page *int, perPage * return result, nil } +func (c *baseRPCClient) UnconfirmedTx(ctx context.Context, txHash []byte) (*coretypes.ResultUnconfirmedTx, error) { + result := new(coretypes.ResultUnconfirmedTx) + + if err := c.caller.Call(ctx, "unconfirmed_tx", &coretypes.RequestUnconfirmedTx{ + TxHash: tmbytes.HexBytes(txHash), + }, result); err != nil { + return nil, err + } + return result, nil +} + func (c *baseRPCClient) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { result := new(coretypes.ResultUnconfirmedTxs) if err := c.caller.Call(ctx, "num_unconfirmed_txs", nil, result); err != nil { @@ -379,7 +390,7 @@ func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*coretypes.Re return result, nil } -func (c *baseRPCClient) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) { +func (c *baseRPCClient) BlockByHash(ctx context.Context, hash tmbytes.HexBytes) (*coretypes.ResultBlock, error) { result := new(coretypes.ResultBlock) if err := c.caller.Call(ctx, "block_by_hash", &coretypes.RequestBlockByHash{Hash: hash}, result); err != nil { return nil, err @@ -407,7 +418,7 @@ func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*coretypes.R return result, nil } -func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { +func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash tmbytes.HexBytes) (*coretypes.ResultHeader, error) { result := new(coretypes.ResultHeader) if err := c.caller.Call(ctx, "header_by_hash", &coretypes.RequestBlockByHash{ Hash: hash, @@ -433,7 +444,7 @@ func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*coretypes.R return result, nil } -func (c *baseRPCClient) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { +func (c *baseRPCClient) Tx(ctx context.Context, hash tmbytes.HexBytes, prove bool) (*coretypes.ResultTx, error) { result := new(coretypes.ResultTx) if err := c.caller.Call(ctx, "tx", &coretypes.RequestTx{Hash: hash, Prove: prove}, result); err != nil { return nil, err diff --git a/rpc/client/interface.go b/rpc/client/interface.go index 7f011a828a..6cc4d3a04a 100644 --- a/rpc/client/interface.go +++ b/rpc/client/interface.go @@ -164,6 +164,7 @@ type SubscriptionClient interface { // MempoolClient shows us data about current mempool state. type MempoolClient interface { UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) + UnconfirmedTx(ctx context.Context, txHash []byte) (*coretypes.ResultUnconfirmedTx, error) NumUnconfirmedTxs(context.Context) (*coretypes.ResultUnconfirmedTxs, error) CheckTx(context.Context, types.Tx) (*coretypes.ResultCheckTx, error) RemoveTx(context.Context, types.TxKey) error diff --git a/rpc/client/local/local.go b/rpc/client/local/local.go index 6408a377ba..7b09d25df6 100644 --- a/rpc/client/local/local.go +++ b/rpc/client/local/local.go @@ -105,6 +105,9 @@ func (c *Local) UnconfirmedTxs(ctx context.Context, page, perPage *int) (*corety PerPage: coretypes.Int64Ptr(perPage), }) } +func (c *Local) UnconfirmedTx(ctx context.Context, txHash []byte) (*coretypes.ResultUnconfirmedTx, error) { + return c.env.UnconfirmedTx(ctx, &coretypes.RequestUnconfirmedTx{TxHash: txHash}) +} func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) { return c.env.NumUnconfirmedTxs(ctx) diff --git a/rpc/client/mocks/client.go b/rpc/client/mocks/client.go index 0b4d6fbf46..1b1aac0b9a 100644 --- a/rpc/client/mocks/client.go +++ b/rpc/client/mocks/client.go @@ -1911,6 +1911,65 @@ func (_c *Client_TxSearch_Call) RunAndReturn(run func(context.Context, string, b return _c } +// UnconfirmedTx provides a mock function with given fields: ctx, txHash +func (_m *Client) UnconfirmedTx(ctx context.Context, txHash []byte) (*coretypes.ResultUnconfirmedTx, error) { + ret := _m.Called(ctx, txHash) + + if len(ret) == 0 { + panic("no return value specified for UnconfirmedTx") + } + + var r0 *coretypes.ResultUnconfirmedTx + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte) (*coretypes.ResultUnconfirmedTx, error)); ok { + return rf(ctx, txHash) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte) *coretypes.ResultUnconfirmedTx); ok { + r0 = rf(ctx, txHash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultUnconfirmedTx) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + r1 = rf(ctx, txHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Client_UnconfirmedTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnconfirmedTx' +type Client_UnconfirmedTx_Call struct { + *mock.Call +} + +// UnconfirmedTx is a helper method to define mock.On call +// - ctx context.Context +// - txHash []byte +func (_e *Client_Expecter) UnconfirmedTx(ctx interface{}, txHash interface{}) *Client_UnconfirmedTx_Call { + return &Client_UnconfirmedTx_Call{Call: _e.mock.On("UnconfirmedTx", ctx, txHash)} +} + +func (_c *Client_UnconfirmedTx_Call) Run(run func(ctx context.Context, txHash []byte)) *Client_UnconfirmedTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]byte)) + }) + return _c +} + +func (_c *Client_UnconfirmedTx_Call) Return(_a0 *coretypes.ResultUnconfirmedTx, _a1 error) *Client_UnconfirmedTx_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Client_UnconfirmedTx_Call) RunAndReturn(run func(context.Context, []byte) (*coretypes.ResultUnconfirmedTx, error)) *Client_UnconfirmedTx_Call { + _c.Call.Return(run) + return _c +} + // UnconfirmedTxs provides a mock function with given fields: ctx, page, perPage func (_m *Client) UnconfirmedTxs(ctx context.Context, page *int, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) { ret := _m.Called(ctx, page, perPage) diff --git a/rpc/client/mocks/remoteclient.go b/rpc/client/mocks/remoteclient.go index 91f48561fc..53822bc2e5 100644 --- a/rpc/client/mocks/remoteclient.go +++ b/rpc/client/mocks/remoteclient.go @@ -1956,6 +1956,65 @@ func (_c *RemoteClient_TxSearch_Call) RunAndReturn(run func(context.Context, str return _c } +// UnconfirmedTx provides a mock function with given fields: ctx, txHash +func (_m *RemoteClient) UnconfirmedTx(ctx context.Context, txHash []byte) (*coretypes.ResultUnconfirmedTx, error) { + ret := _m.Called(ctx, txHash) + + if len(ret) == 0 { + panic("no return value specified for UnconfirmedTx") + } + + var r0 *coretypes.ResultUnconfirmedTx + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, []byte) (*coretypes.ResultUnconfirmedTx, error)); ok { + return rf(ctx, txHash) + } + if rf, ok := ret.Get(0).(func(context.Context, []byte) *coretypes.ResultUnconfirmedTx); ok { + r0 = rf(ctx, txHash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*coretypes.ResultUnconfirmedTx) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + r1 = rf(ctx, txHash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// RemoteClient_UnconfirmedTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnconfirmedTx' +type RemoteClient_UnconfirmedTx_Call struct { + *mock.Call +} + +// UnconfirmedTx is a helper method to define mock.On call +// - ctx context.Context +// - txHash []byte +func (_e *RemoteClient_Expecter) UnconfirmedTx(ctx interface{}, txHash interface{}) *RemoteClient_UnconfirmedTx_Call { + return &RemoteClient_UnconfirmedTx_Call{Call: _e.mock.On("UnconfirmedTx", ctx, txHash)} +} + +func (_c *RemoteClient_UnconfirmedTx_Call) Run(run func(ctx context.Context, txHash []byte)) *RemoteClient_UnconfirmedTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].([]byte)) + }) + return _c +} + +func (_c *RemoteClient_UnconfirmedTx_Call) Return(_a0 *coretypes.ResultUnconfirmedTx, _a1 error) *RemoteClient_UnconfirmedTx_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *RemoteClient_UnconfirmedTx_Call) RunAndReturn(run func(context.Context, []byte) (*coretypes.ResultUnconfirmedTx, error)) *RemoteClient_UnconfirmedTx_Call { + _c.Call.Return(run) + return _c +} + // UnconfirmedTxs provides a mock function with given fields: ctx, page, perPage func (_m *RemoteClient) UnconfirmedTxs(ctx context.Context, page *int, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) { ret := _m.Called(ctx, page, perPage) diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go index 7544c9f846..3478b16cb3 100644 --- a/rpc/client/rpc_test.go +++ b/rpc/client/rpc_test.go @@ -663,6 +663,41 @@ func TestClientMethodCallsAdvanced(t *testing.T) { pool.Flush() }) + + t.Run("UnconfirmedTx", func(t *testing.T) { + // populate mempool with 5 tx + txs := make([]types.Tx, 5) + ch := make(chan error, 5) + for i := 0; i < 5; i++ { + _, _, tx := MakeTxKV() + + txs[i] = tx + err := pool.CheckTx(ctx, tx, func(_ *abci.ResponseCheckTx) { ch <- nil }, mempool.TxInfo{}) + + require.NoError(t, err) + } + // wait for tx to arrive in mempoool. + for i := 0; i < 5; i++ { + select { + case <-ch: + case <-time.After(5 * time.Second): + t.Error("Timed out waiting for CheckTx callback") + } + } + close(ch) + + for _, c := range GetClients(t, n, conf) { + for _, tx := range txs { + mc := c.(client.MempoolClient) + res, err := mc.UnconfirmedTx(ctx, tx.Hash()) + require.NoError(t, err) + + assert.Equal(t, tx, res.Tx) + } + } + + pool.Flush() + }) t.Run("NumUnconfirmedTxs", func(t *testing.T) { ch := make(chan struct{}) diff --git a/rpc/coretypes/requests.go b/rpc/coretypes/requests.go index 18870f50f3..190601b989 100644 --- a/rpc/coretypes/requests.go +++ b/rpc/coretypes/requests.go @@ -80,6 +80,11 @@ type RequestUnconfirmedTxs struct { PerPage *Int64 `json:"per_page"` } +// RequestUnconfirmedTx is used to request a single unconfirmed transaction. +type RequestUnconfirmedTx struct { + TxHash bytes.HexBytes `json:"hash,omitempty"` +} + type RequestBroadcastTx struct { Tx types.Tx `json:"tx"` } diff --git a/rpc/coretypes/responses.go b/rpc/coretypes/responses.go index 329e1dc397..8444924bb9 100644 --- a/rpc/coretypes/responses.go +++ b/rpc/coretypes/responses.go @@ -312,6 +312,11 @@ type ResultUnconfirmedTxs struct { Txs []types.Tx `json:"txs"` } +// Fetch transaction from mempool +type ResultUnconfirmedTx struct { + Tx types.Tx `json:"tx"` +} + // Info abci msg type ResultABCIInfo struct { Response abci.ResponseInfo `json:"response"` diff --git a/rpc/openapi/openapi.yaml b/rpc/openapi/openapi.yaml index bedb64d886..b7a121ad6c 100644 --- a/rpc/openapi/openapi.yaml +++ b/rpc/openapi/openapi.yaml @@ -1,9 +1,6 @@ openapi: 3.0.0 info: - title: Tendermint RPC - contact: - name: Tendermint RPC - url: https://github.com/tendermint/tendermint/issues/new/choose + title: Tenderdash RPC description: | Tendermint supports the following RPC protocols: @@ -59,10 +56,8 @@ info: name: Apache 2.0 url: https://github.com/dashevo/tenderdash/blob/master/LICENSE servers: - - url: https://rpc.cosmos.network - description: Cosmos mainnet node to interact with the Tendermint RPC - url: http://localhost:26657 - description: Interact with the Tendermint RPC locally on your device + description: Interact with the Tenderdash RPC locally on your device tags: - name: Websocket description: Subscribe/unsubscribe are reserved for websocket events. @@ -1107,7 +1102,6 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" - /dump_consensus_state: get: summary: Get consensus state @@ -1224,6 +1218,36 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorResponse" + /unconfirmed_tx: + get: + summary: Get unconfirmed transaction by hash + operationId: unconfirmed_tx + parameters: + - in: query + name: hash + description: "Transaction hash" + required: true + schema: + type: string + example: "0xD70952032620CC4E2737EB8AC379806359D8E0B17B0488F627997A0B043ABDED" + + tags: + - Info + description: | + Get unconfirmed transaction by hash + responses: + "200": + description: Transaction + content: + application/json: + schema: + $ref: "#/components/schemas/UnconfirmedTransactionResponse" + "500": + description: Error + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" /num_unconfirmed_txs: get: summary: Get data about unconfirmed transactions @@ -2712,6 +2736,29 @@ components: - "gAPwYl3uCjCMTXENChSMnIkb5ZpYHBKIZqecFEV2tuZr7xIUA75/FmYq9WymsOBJ0XSJ8yV8zmQKMIxNcQ0KFIyciRvlmlgcEohmp5wURXa25mvvEhQbrvwbvlNiT+Yjr86G+YQNx7kRVgowjE1xDQoUjJyJG+WaWBwSiGannBRFdrbma+8SFK2m+1oxgILuQLO55n8mWfnbIzyPCjCMTXENChSMnIkb5ZpYHBKIZqecFEV2tuZr7xIUQNGfkmhTNMis4j+dyMDIWXdIPiYKMIxNcQ0KFIyciRvlmlgcEohmp5wURXa25mvvEhS8sL0D0wwgGCItQwVowak5YB38KRIUCg4KBXVhdG9tEgUxMDA1NBDoxRgaagom61rphyECn8x7emhhKdRCB2io7aS/6Cpuq5NbVqbODmqOT3jWw6kSQKUresk+d+Gw0BhjiggTsu8+1voW+VlDCQ1GRYnMaFOHXhyFv7BCLhFWxLxHSAYT8a5XqoMayosZf9mANKdXArA=" type: object + UnconfirmedTransactionResponse: + type: object + required: + - "jsonrpc" + - "id" + - "result" + properties: + jsonrpc: + type: string + example: "2.0" + id: + type: integer + example: 0 + result: + required: + - "tx" + properties: + tx: + type: string + example: "EjQSNFZ4kKvN7wmHZUMhAAA=" + description: Base-64 encoded transaction + type: object + TxSearchResponse: type: object required: diff --git a/types/tx.go b/types/tx.go index 27688ea9f5..6b69e931f8 100644 --- a/types/tx.go +++ b/types/tx.go @@ -2,7 +2,6 @@ package types import ( "bytes" - "crypto/sha256" "errors" "fmt" "sort" @@ -25,7 +24,11 @@ const maxLoggedTxs = 20 type Tx []byte // Key produces a fixed-length key for use in indexing. -func (tx Tx) Key() TxKey { return sha256.Sum256(tx) } +func (tx Tx) Key() TxKey { + // we must use the hash of the transaction as the key to + // make it easier to lookup transactions in the mempool + return TxKey(tx.Hash()) +} // Hash computes the TMHASH hash of the wire encoded transaction. func (tx Tx) Hash() tmbytes.HexBytes { return crypto.Checksum(tx) }