Unify RPC method signatures and parameter decoding (#8397)

Pass all parameters from JSON-RPC requests to their corresponding handlers
using struct types instead of positional parameters. This allows us to control
encoding of arguments using only the standard library, and to eliminate the
remaining special-purpose JSON encoding hooks in the server.

To support existing use, the server still allows arguments to be encoded in
JSON as either an array or an object.

Related changes:

- Rework the RPCFunc constructor to reduce reflection during RPC call service.
- Add request parameter wrappers for each RPC service method.
- Update the RPC Environment methods to use these types.
- Update the interfaces and shims derived from Environment to the new
  signatures.
- Update and extend test cases.
This commit is contained in:
M. J. Fromberger
2022-04-27 07:53:51 -07:00
committed by GitHub
parent e741d01231
commit da1b871808
30 changed files with 983 additions and 779 deletions

View File

@@ -38,16 +38,16 @@ func Routes(cfg config.RPCConfig, s state.Store, bs state.BlockStore, es []index
Logger: logger,
}
return core.RoutesMap{
"blockchain": server.NewRPCFunc(env.BlockchainInfo, "minHeight", "maxHeight"),
"consensus_params": server.NewRPCFunc(env.ConsensusParams, "height"),
"block": server.NewRPCFunc(env.Block, "height"),
"block_by_hash": server.NewRPCFunc(env.BlockByHash, "hash"),
"block_results": server.NewRPCFunc(env.BlockResults, "height"),
"commit": server.NewRPCFunc(env.Commit, "height"),
"validators": server.NewRPCFunc(env.Validators, "height", "page", "per_page"),
"tx": server.NewRPCFunc(env.Tx, "hash", "prove"),
"tx_search": server.NewRPCFunc(env.TxSearch, "query", "prove", "page", "per_page", "order_by"),
"block_search": server.NewRPCFunc(env.BlockSearch, "query", "page", "per_page", "order_by"),
"blockchain": server.NewRPCFunc(env.BlockchainInfo),
"consensus_params": server.NewRPCFunc(env.ConsensusParams),
"block": server.NewRPCFunc(env.Block),
"block_by_hash": server.NewRPCFunc(env.BlockByHash),
"block_results": server.NewRPCFunc(env.BlockResults),
"commit": server.NewRPCFunc(env.Commit),
"validators": server.NewRPCFunc(env.Validators),
"tx": server.NewRPCFunc(env.Tx),
"tx_search": server.NewRPCFunc(env.TxSearch),
"block_search": server.NewRPCFunc(env.BlockSearch),
}
}

View File

@@ -5,24 +5,17 @@ import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/internal/proxy"
"github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/rpc/coretypes"
)
// ABCIQuery queries the application for some information.
// More: https://docs.tendermint.com/master/rpc/#/ABCI/abci_query
func (env *Environment) ABCIQuery(
ctx context.Context,
path string,
data bytes.HexBytes,
height int64,
prove bool,
) (*coretypes.ResultABCIQuery, error) {
func (env *Environment) ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) {
resQuery, err := env.ProxyApp.Query(ctx, &abci.RequestQuery{
Path: path,
Data: data,
Height: height,
Prove: prove,
Path: req.Path,
Data: req.Data,
Height: int64(req.Height),
Prove: req.Prove,
})
if err != nil {
return nil, err

View File

@@ -7,7 +7,6 @@ import (
tmquery "github.com/tendermint/tendermint/internal/pubsub/query"
"github.com/tendermint/tendermint/internal/state/indexer"
"github.com/tendermint/tendermint/libs/bytes"
tmmath "github.com/tendermint/tendermint/libs/math"
"github.com/tendermint/tendermint/rpc/coretypes"
"github.com/tendermint/tendermint/types"
@@ -23,17 +22,15 @@ import (
// order (highest first).
//
// More: https://docs.tendermint.com/master/rpc/#/Info/blockchain
func (env *Environment) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
const limit int64 = 20
var err error
minHeight, maxHeight, err = filterMinMax(
func (env *Environment) BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) {
const limit = 20
minHeight, maxHeight, err := filterMinMax(
env.BlockStore.Base(),
env.BlockStore.Height(),
minHeight,
maxHeight,
limit)
int64(req.MinHeight),
int64(req.MaxHeight),
limit,
)
if err != nil {
return nil, err
}
@@ -90,8 +87,8 @@ func filterMinMax(base, height, min, max, limit int64) (int64, int64, error) {
// Block gets block at a given height.
// If no height is provided, it will fetch the latest block.
// More: https://docs.tendermint.com/master/rpc/#/Info/block
func (env *Environment) Block(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlock, error) {
height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
func (env *Environment) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) {
height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
if err != nil {
return nil, err
}
@@ -107,12 +104,8 @@ func (env *Environment) Block(ctx context.Context, heightPtr *int64) (*coretypes
// BlockByHash gets block by hash.
// More: https://docs.tendermint.com/master/rpc/#/Info/block_by_hash
func (env *Environment) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) {
// N.B. The hash parameter is HexBytes so that the reflective parameter
// decoding logic in the HTTP service will correctly translate from JSON.
// See https://github.com/tendermint/tendermint/issues/6802 for context.
block := env.BlockStore.LoadBlockByHash(hash)
func (env *Environment) BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) {
block := env.BlockStore.LoadBlockByHash(req.Hash)
if block == nil {
return &coretypes.ResultBlock{BlockID: types.BlockID{}, Block: nil}, nil
}
@@ -124,8 +117,8 @@ func (env *Environment) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*
// Header gets block header at a given height.
// If no height is provided, it will fetch the latest header.
// More: https://docs.tendermint.com/master/rpc/#/Info/header
func (env *Environment) Header(ctx context.Context, heightPtr *int64) (*coretypes.ResultHeader, error) {
height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
func (env *Environment) Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) {
height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
if err != nil {
return nil, err
}
@@ -140,12 +133,8 @@ func (env *Environment) Header(ctx context.Context, heightPtr *int64) (*coretype
// HeaderByHash gets header by hash.
// More: https://docs.tendermint.com/master/rpc/#/Info/header_by_hash
func (env *Environment) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) {
// N.B. The hash parameter is HexBytes so that the reflective parameter
// decoding logic in the HTTP service will correctly translate from JSON.
// See https://github.com/tendermint/tendermint/issues/6802 for context.
blockMeta := env.BlockStore.LoadBlockMetaByHash(hash)
func (env *Environment) HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) {
blockMeta := env.BlockStore.LoadBlockMetaByHash(req.Hash)
if blockMeta == nil {
return &coretypes.ResultHeader{}, nil
}
@@ -156,8 +145,8 @@ func (env *Environment) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (
// Commit gets block commit at a given height.
// If no height is provided, it will fetch the commit for the latest block.
// More: https://docs.tendermint.com/master/rpc/#/Info/commit
func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretypes.ResultCommit, error) {
height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
func (env *Environment) Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) {
height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
if err != nil {
return nil, err
}
@@ -192,8 +181,8 @@ func (env *Environment) Commit(ctx context.Context, heightPtr *int64) (*coretype
//
// Results are for the height of the block containing the txs.
// More: https://docs.tendermint.com/master/rpc/#/Info/block_results
func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error) {
height, err := env.getHeight(env.BlockStore.Height(), heightPtr)
func (env *Environment) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) {
height, err := env.getHeight(env.BlockStore.Height(), (*int64)(req.Height))
if err != nil {
return nil, err
}
@@ -218,20 +207,13 @@ func (env *Environment) BlockResults(ctx context.Context, heightPtr *int64) (*co
}, nil
}
// BlockSearch searches for a paginated set of blocks matching the provided
// query.
func (env *Environment) BlockSearch(
ctx context.Context,
query string,
pagePtr, perPagePtr *int,
orderBy string,
) (*coretypes.ResultBlockSearch, error) {
// BlockSearch searches for a paginated set of blocks matching the provided query.
func (env *Environment) BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) {
if !indexer.KVSinkEnabled(env.EventSinks) {
return nil, fmt.Errorf("block searching is disabled due to no kvEventSink")
}
q, err := tmquery.New(query)
q, err := tmquery.New(req.Query)
if err != nil {
return nil, err
}
@@ -249,7 +231,7 @@ func (env *Environment) BlockSearch(
}
// sort results (must be done before pagination)
switch orderBy {
switch req.OrderBy {
case "desc", "":
sort.Slice(results, func(i, j int) bool { return results[i] > results[j] })
@@ -262,9 +244,9 @@ func (env *Environment) BlockSearch(
// paginate results
totalCount := len(results)
perPage := env.validatePerPage(perPagePtr)
perPage := env.validatePerPage(req.PerPage.IntPtr())
page, err := validatePage(pagePtr, perPage, totalCount)
page, err := validatePage(req.Page.IntPtr(), perPage, totalCount)
if err != nil {
return nil, err
}

View File

@@ -109,7 +109,9 @@ func TestBlockResults(t *testing.T) {
ctx := context.Background()
for _, tc := range testCases {
res, err := env.BlockResults(ctx, &tc.height)
res, err := env.BlockResults(ctx, &coretypes.RequestBlockInfo{
Height: (*coretypes.Int64)(&tc.height),
})
if tc.wantErr {
assert.Error(t, err)
} else {

View File

@@ -14,10 +14,9 @@ import (
// for the validators in the set as used in computing their Merkle root.
//
// More: https://docs.tendermint.com/master/rpc/#/Info/validators
func (env *Environment) Validators(ctx context.Context, heightPtr *int64, pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error) {
func (env *Environment) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) {
// The latest validator that we know is the NextValidator of the last block.
height, err := env.getHeight(env.latestUncommittedHeight(), heightPtr)
height, err := env.getHeight(env.latestUncommittedHeight(), (*int64)(req.Height))
if err != nil {
return nil, err
}
@@ -28,8 +27,8 @@ func (env *Environment) Validators(ctx context.Context, heightPtr *int64, pagePt
}
totalCount := len(validators.Validators)
perPage := env.validatePerPage(perPagePtr)
page, err := validatePage(pagePtr, perPage, totalCount)
perPage := env.validatePerPage(req.PerPage.IntPtr())
page, err := validatePage(req.Page.IntPtr(), perPage, totalCount)
if err != nil {
return nil, err
}
@@ -42,7 +41,8 @@ func (env *Environment) Validators(ctx context.Context, heightPtr *int64, pagePt
BlockHeight: height,
Validators: v,
Count: len(v),
Total: totalCount}, nil
Total: totalCount,
}, nil
}
// DumpConsensusState dumps consensus state.
@@ -99,11 +99,10 @@ func (env *Environment) GetConsensusState(ctx context.Context) (*coretypes.Resul
// ConsensusParams gets the consensus parameters at the given block height.
// If no height is provided, it will fetch the latest consensus params.
// More: https://docs.tendermint.com/master/rpc/#/Info/consensus_params
func (env *Environment) ConsensusParams(ctx context.Context, heightPtr *int64) (*coretypes.ResultConsensusParams, error) {
// The latest consensus params that we know is the consensus params after the
// last block.
height, err := env.getHeight(env.latestUncommittedHeight(), heightPtr)
func (env *Environment) ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) {
// The latest consensus params that we know is the consensus params after
// the last block.
height, err := env.getHeight(env.latestUncommittedHeight(), (*int64)(req.Height))
if err != nil {
return nil, err
}

View File

@@ -26,7 +26,7 @@ const (
// Subscribe for events via WebSocket.
// More: https://docs.tendermint.com/master/rpc/#/Websocket/subscribe
func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) {
func (env *Environment) Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) {
callInfo := rpctypes.GetCallInfo(ctx)
addr := callInfo.RemoteAddr()
@@ -34,15 +34,15 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes
return nil, fmt.Errorf("max_subscription_clients %d reached", env.Config.MaxSubscriptionClients)
} else if env.EventBus.NumClientSubscriptions(addr) >= env.Config.MaxSubscriptionsPerClient {
return nil, fmt.Errorf("max_subscriptions_per_client %d reached", env.Config.MaxSubscriptionsPerClient)
} else if len(query) > maxQueryLength {
} else if len(req.Query) > maxQueryLength {
return nil, errors.New("maximum query length exceeded")
}
env.Logger.Info("WARNING: Websocket subscriptions are deprecated and will be removed " +
"in Tendermint v0.37. See https://tinyurl.com/adr075 for more information.")
env.Logger.Info("Subscribe to query", "remote", addr, "query", query)
env.Logger.Info("Subscribe to query", "remote", addr, "query", req.Query)
q, err := tmquery.New(query)
q, err := tmquery.New(req.Query)
if err != nil {
return nil, fmt.Errorf("failed to parse query: %w", err)
}
@@ -83,7 +83,7 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes
// We have a message to deliver to the client.
resp := callInfo.RPCRequest.MakeResponse(&coretypes.ResultEvent{
Query: query,
Query: req.Query,
Data: msg.Data(),
Events: msg.Events(),
})
@@ -102,15 +102,15 @@ func (env *Environment) Subscribe(ctx context.Context, query string) (*coretypes
// Unsubscribe from events via WebSocket.
// More: https://docs.tendermint.com/master/rpc/#/Websocket/unsubscribe
func (env *Environment) Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) {
func (env *Environment) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) {
args := tmpubsub.UnsubscribeArgs{Subscriber: rpctypes.GetCallInfo(ctx).RemoteAddr()}
env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", query)
env.Logger.Info("Unsubscribe from query", "remote", args.Subscriber, "subscription", req.Query)
var err error
args.Query, err = tmquery.New(query)
args.Query, err = tmquery.New(req.Query)
if err != nil {
args.ID = query
args.ID = req.Query
}
err = env.EventBus.Unsubscribe(ctx, args)
@@ -148,17 +148,13 @@ func (env *Environment) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUn
// If maxItems ≤ 0, a default positive number of events is chosen. The values
// of maxItems and waitTime may be capped to sensible internal maxima without
// reporting an error to the caller.
func (env *Environment) Events(ctx context.Context,
filter *coretypes.EventFilter,
maxItems int,
before, after cursor.Cursor,
waitTime time.Duration,
) (*coretypes.ResultEvents, error) {
func (env *Environment) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) {
if env.EventLog == nil {
return nil, errors.New("the event log is not enabled")
}
// Parse and validate parameters.
maxItems := req.MaxItems
if maxItems <= 0 {
maxItems = 10
} else if maxItems > 100 {
@@ -167,6 +163,8 @@ func (env *Environment) Events(ctx context.Context,
const minWaitTime = 1 * time.Second
const maxWaitTime = 30 * time.Second
waitTime := req.WaitTime
if waitTime < minWaitTime {
waitTime = minWaitTime
} else if waitTime > maxWaitTime {
@@ -174,14 +172,22 @@ func (env *Environment) Events(ctx context.Context,
}
query := tmquery.All
if filter != nil && filter.Query != "" {
q, err := tmquery.New(filter.Query)
if req.Filter != nil && req.Filter.Query != "" {
q, err := tmquery.New(req.Filter.Query)
if err != nil {
return nil, fmt.Errorf("invalid filter query: %w", err)
}
query = q
}
var before, after cursor.Cursor
if err := before.UnmarshalText([]byte(req.Before)); err != nil {
return nil, fmt.Errorf("invalid cursor %q: %w", req.Before, err)
}
if err := after.UnmarshalText([]byte(req.After)); err != nil {
return nil, fmt.Errorf("invalid cursor %q: %w", req.After, err)
}
var info eventlog.Info
var items []*eventlog.Item
var err error

View File

@@ -9,18 +9,15 @@ import (
// BroadcastEvidence broadcasts evidence of the misbehavior.
// More: https://docs.tendermint.com/master/rpc/#/Evidence/broadcast_evidence
func (env *Environment) BroadcastEvidence(
ctx context.Context,
ev coretypes.Evidence,
) (*coretypes.ResultBroadcastEvidence, error) {
if ev.Value == nil {
func (env *Environment) BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) {
if req.Evidence == nil {
return nil, fmt.Errorf("%w: no evidence was provided", coretypes.ErrInvalidRequest)
}
if err := ev.Value.ValidateBasic(); err != nil {
if err := req.Evidence.ValidateBasic(); err != nil {
return nil, fmt.Errorf("evidence.ValidateBasic failed: %w", err)
}
if err := env.EvidencePool.AddEvidence(ctx, ev.Value); err != nil {
if err := env.EvidencePool.AddEvidence(ctx, req.Evidence); err != nil {
return nil, fmt.Errorf("failed to add evidence: %w", err)
}
return &coretypes.ResultBroadcastEvidence{Hash: ev.Value.Hash()}, nil
return &coretypes.ResultBroadcastEvidence{Hash: req.Evidence.Hash()}, nil
}

View File

@@ -12,7 +12,6 @@ import (
"github.com/tendermint/tendermint/internal/state/indexer"
tmmath "github.com/tendermint/tendermint/libs/math"
"github.com/tendermint/tendermint/rpc/coretypes"
"github.com/tendermint/tendermint/types"
)
//-----------------------------------------------------------------------------
@@ -21,23 +20,23 @@ import (
// 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 context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
err := env.Mempool.CheckTx(ctx, tx, nil, mempool.TxInfo{})
func (env *Environment) BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
err := env.Mempool.CheckTx(ctx, req.Tx, nil, mempool.TxInfo{})
if err != nil {
return nil, err
}
return &coretypes.ResultBroadcastTx{Hash: tx.Hash()}, nil
return &coretypes.ResultBroadcastTx{Hash: req.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 context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
func (env *Environment) BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
resCh := make(chan *abci.ResponseCheckTx, 1)
err := env.Mempool.CheckTx(
ctx,
tx,
req.Tx,
func(res *abci.ResponseCheckTx) {
select {
case <-ctx.Done():
@@ -60,19 +59,18 @@ func (env *Environment) BroadcastTxSync(ctx context.Context, tx types.Tx) (*core
Log: r.Log,
Codespace: r.Codespace,
MempoolError: r.MempoolError,
Hash: tx.Hash(),
Hash: req.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 context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) {
func (env *Environment) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) {
resCh := make(chan *abci.ResponseCheckTx, 1)
err := env.Mempool.CheckTx(
ctx,
tx,
req.Tx,
func(res *abci.ResponseCheckTx) {
select {
case <-ctx.Done():
@@ -92,14 +90,14 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co
if r.Code != abci.CodeTypeOK {
return &coretypes.ResultBroadcastTxCommit{
CheckTx: *r,
Hash: tx.Hash(),
Hash: req.Tx.Hash(),
}, fmt.Errorf("transaction encountered error (%s)", r.MempoolError)
}
if !indexer.KVSinkEnabled(env.EventSinks) {
return &coretypes.ResultBroadcastTxCommit{
CheckTx: *r,
Hash: tx.Hash(),
Hash: req.Tx.Hash(),
},
errors.New("cannot confirm transaction because kvEventSink is not enabled")
}
@@ -118,11 +116,14 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co
"err", err)
return &coretypes.ResultBroadcastTxCommit{
CheckTx: *r,
Hash: tx.Hash(),
Hash: req.Tx.Hash(),
}, fmt.Errorf("timeout waiting for commit of tx %s (%s)",
tx.Hash(), time.Since(startAt))
req.Tx.Hash(), time.Since(startAt))
case <-timer.C:
txres, err := env.Tx(ctx, tx.Hash(), false)
txres, err := env.Tx(ctx, &coretypes.RequestTx{
Hash: req.Tx.Hash(),
Prove: false,
})
if err != nil {
jitter := 100*time.Millisecond + time.Duration(rand.Int63n(int64(time.Second))) // nolint: gosec
backoff := 100 * time.Duration(count) * time.Millisecond
@@ -133,7 +134,7 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co
return &coretypes.ResultBroadcastTxCommit{
CheckTx: *r,
TxResult: txres.TxResult,
Hash: tx.Hash(),
Hash: req.Tx.Hash(),
Height: txres.Height,
}, nil
}
@@ -143,10 +144,10 @@ func (env *Environment) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*co
// 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, pagePtr, perPagePtr *int) (*coretypes.ResultUnconfirmedTxs, error) {
func (env *Environment) UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) {
totalCount := env.Mempool.Size()
perPage := env.validatePerPage(perPagePtr)
page, err := validatePage(pagePtr, perPage, totalCount)
perPage := env.validatePerPage(req.PerPage.IntPtr())
page, err := validatePage(req.Page.IntPtr(), perPage, totalCount)
if err != nil {
return nil, err
}
@@ -160,7 +161,8 @@ func (env *Environment) UnconfirmedTxs(ctx context.Context, pagePtr, perPagePtr
Count: len(result),
Total: totalCount,
TotalBytes: env.Mempool.SizeBytes(),
Txs: result}, nil
Txs: result,
}, nil
}
// NumUnconfirmedTxs gets number of unconfirmed transactions.
@@ -175,14 +177,14 @@ func (env *Environment) NumUnconfirmedTxs(ctx context.Context) (*coretypes.Resul
// 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 context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) {
res, err := env.ProxyApp.CheckTx(ctx, &abci.RequestCheckTx{Tx: tx})
func (env *Environment) CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) {
res, err := env.ProxyApp.CheckTx(ctx, &abci.RequestCheckTx{Tx: req.Tx})
if err != nil {
return nil, err
}
return &coretypes.ResultCheckTx{ResponseCheckTx: *res}, nil
}
func (env *Environment) RemoveTx(ctx context.Context, txkey types.TxKey) error {
return env.Mempool.RemoveTxByKey(txkey)
func (env *Environment) RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error {
return env.Mempool.RemoveTxByKey(req.TxKey)
}

View File

@@ -44,7 +44,7 @@ func (env *Environment) Genesis(ctx context.Context) (*coretypes.ResultGenesis,
return &coretypes.ResultGenesis{Genesis: env.GenDoc}, nil
}
func (env *Environment) GenesisChunked(ctx context.Context, chunk uint) (*coretypes.ResultGenesisChunk, error) {
func (env *Environment) GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) {
if env.genChunks == nil {
return nil, fmt.Errorf("service configuration error, genesis chunks are not initialized")
}
@@ -53,7 +53,7 @@ func (env *Environment) GenesisChunked(ctx context.Context, chunk uint) (*corety
return nil, fmt.Errorf("service configuration error, there are no chunks")
}
id := int(chunk)
id := int(req.Chunk)
if id > len(env.genChunks)-1 {
return nil, fmt.Errorf("there are %d chunks, %d is invalid", len(env.genChunks)-1, id)

View File

@@ -2,13 +2,9 @@ package core
import (
"context"
"time"
"github.com/tendermint/tendermint/internal/eventlog/cursor"
"github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/rpc/coretypes"
rpc "github.com/tendermint/tendermint/rpc/jsonrpc/server"
"github.com/tendermint/tendermint/types"
)
// TODO: better system than "unsafe" prefix
@@ -32,47 +28,47 @@ func NewRoutesMap(svc RPCService, opts *RouteOptions) RoutesMap {
out := RoutesMap{
// Event subscription. Note that subscribe, unsubscribe, and
// unsubscribe_all are only available via the websocket endpoint.
"events": rpc.NewRPCFunc(svc.Events, "filter", "maxItems", "before", "after", "waitTime"),
"subscribe": rpc.NewWSRPCFunc(svc.Subscribe, "query"),
"unsubscribe": rpc.NewWSRPCFunc(svc.Unsubscribe, "query"),
"events": rpc.NewRPCFunc(svc.Events),
"subscribe": rpc.NewWSRPCFunc(svc.Subscribe),
"unsubscribe": rpc.NewWSRPCFunc(svc.Unsubscribe),
"unsubscribe_all": rpc.NewWSRPCFunc(svc.UnsubscribeAll),
// info API
"health": rpc.NewRPCFunc(svc.Health),
"status": rpc.NewRPCFunc(svc.Status),
"net_info": rpc.NewRPCFunc(svc.NetInfo),
"blockchain": rpc.NewRPCFunc(svc.BlockchainInfo, "minHeight", "maxHeight"),
"blockchain": rpc.NewRPCFunc(svc.BlockchainInfo),
"genesis": rpc.NewRPCFunc(svc.Genesis),
"genesis_chunked": rpc.NewRPCFunc(svc.GenesisChunked, "chunk"),
"header": rpc.NewRPCFunc(svc.Header, "height"),
"header_by_hash": rpc.NewRPCFunc(svc.HeaderByHash, "hash"),
"block": rpc.NewRPCFunc(svc.Block, "height"),
"block_by_hash": rpc.NewRPCFunc(svc.BlockByHash, "hash"),
"block_results": rpc.NewRPCFunc(svc.BlockResults, "height"),
"commit": rpc.NewRPCFunc(svc.Commit, "height"),
"check_tx": rpc.NewRPCFunc(svc.CheckTx, "tx"),
"remove_tx": rpc.NewRPCFunc(svc.RemoveTx, "txkey"),
"tx": rpc.NewRPCFunc(svc.Tx, "hash", "prove"),
"tx_search": rpc.NewRPCFunc(svc.TxSearch, "query", "prove", "page", "per_page", "order_by"),
"block_search": rpc.NewRPCFunc(svc.BlockSearch, "query", "page", "per_page", "order_by"),
"validators": rpc.NewRPCFunc(svc.Validators, "height", "page", "per_page"),
"genesis_chunked": rpc.NewRPCFunc(svc.GenesisChunked),
"header": rpc.NewRPCFunc(svc.Header),
"header_by_hash": rpc.NewRPCFunc(svc.HeaderByHash),
"block": rpc.NewRPCFunc(svc.Block),
"block_by_hash": rpc.NewRPCFunc(svc.BlockByHash),
"block_results": rpc.NewRPCFunc(svc.BlockResults),
"commit": rpc.NewRPCFunc(svc.Commit),
"check_tx": rpc.NewRPCFunc(svc.CheckTx),
"remove_tx": rpc.NewRPCFunc(svc.RemoveTx),
"tx": rpc.NewRPCFunc(svc.Tx),
"tx_search": rpc.NewRPCFunc(svc.TxSearch),
"block_search": rpc.NewRPCFunc(svc.BlockSearch),
"validators": rpc.NewRPCFunc(svc.Validators),
"dump_consensus_state": rpc.NewRPCFunc(svc.DumpConsensusState),
"consensus_state": rpc.NewRPCFunc(svc.GetConsensusState),
"consensus_params": rpc.NewRPCFunc(svc.ConsensusParams, "height"),
"unconfirmed_txs": rpc.NewRPCFunc(svc.UnconfirmedTxs, "page", "per_page"),
"consensus_params": rpc.NewRPCFunc(svc.ConsensusParams),
"unconfirmed_txs": rpc.NewRPCFunc(svc.UnconfirmedTxs),
"num_unconfirmed_txs": rpc.NewRPCFunc(svc.NumUnconfirmedTxs),
// tx broadcast API
"broadcast_tx_commit": rpc.NewRPCFunc(svc.BroadcastTxCommit, "tx"),
"broadcast_tx_sync": rpc.NewRPCFunc(svc.BroadcastTxSync, "tx"),
"broadcast_tx_async": rpc.NewRPCFunc(svc.BroadcastTxAsync, "tx"),
"broadcast_tx_commit": rpc.NewRPCFunc(svc.BroadcastTxCommit),
"broadcast_tx_sync": rpc.NewRPCFunc(svc.BroadcastTxSync),
"broadcast_tx_async": rpc.NewRPCFunc(svc.BroadcastTxAsync),
// abci API
"abci_query": rpc.NewRPCFunc(svc.ABCIQuery, "path", "data", "height", "prove"),
"abci_query": rpc.NewRPCFunc(svc.ABCIQuery),
"abci_info": rpc.NewRPCFunc(svc.ABCIInfo),
// evidence API
"broadcast_evidence": rpc.NewRPCFunc(svc.BroadcastEvidence, "evidence"),
"broadcast_evidence": rpc.NewRPCFunc(svc.BroadcastEvidence),
}
if u, ok := svc.(RPCUnsafe); ok && opts.Unsafe {
out["unsafe_flush_mempool"] = rpc.NewRPCFunc(u.UnsafeFlushMempool)
@@ -84,38 +80,38 @@ func NewRoutesMap(svc RPCService, opts *RouteOptions) RoutesMap {
// implementation, for use in constructing a routing table.
type RPCService interface {
ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error)
ABCIQuery(ctx context.Context, path string, data bytes.HexBytes, height int64, prove bool) (*coretypes.ResultABCIQuery, error)
Block(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlock, error)
BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error)
BlockResults(ctx context.Context, heightPtr *int64) (*coretypes.ResultBlockResults, error)
BlockSearch(ctx context.Context, query string, pagePtr, perPagePtr *int, orderBy string) (*coretypes.ResultBlockSearch, error)
BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error)
BroadcastEvidence(ctx context.Context, ev coretypes.Evidence) (*coretypes.ResultBroadcastEvidence, error)
BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error)
BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error)
BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error)
CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error)
Commit(ctx context.Context, heightPtr *int64) (*coretypes.ResultCommit, error)
ConsensusParams(ctx context.Context, heightPtr *int64) (*coretypes.ResultConsensusParams, error)
ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error)
Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error)
BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error)
BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error)
BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error)
BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error)
BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error)
BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error)
BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error)
CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error)
Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error)
ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error)
DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error)
Events(ctx context.Context, filter *coretypes.EventFilter, maxItems int, before, after cursor.Cursor, waitTime time.Duration) (*coretypes.ResultEvents, error)
Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error)
Genesis(ctx context.Context) (*coretypes.ResultGenesis, error)
GenesisChunked(ctx context.Context, chunk uint) (*coretypes.ResultGenesisChunk, error)
GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error)
GetConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error)
Header(ctx context.Context, heightPtr *int64) (*coretypes.ResultHeader, error)
HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error)
Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error)
HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error)
Health(ctx context.Context) (*coretypes.ResultHealth, error)
NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error)
NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error)
RemoveTx(ctx context.Context, txkey types.TxKey) error
RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error
Status(ctx context.Context) (*coretypes.ResultStatus, error)
Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error)
Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error)
TxSearch(ctx context.Context, query string, prove bool, pagePtr, perPagePtr *int, orderBy string) (*coretypes.ResultTxSearch, error)
UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error)
Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error)
Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error)
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)
Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error)
UnsubscribeAll(ctx context.Context) (*coretypes.ResultUnsubscribe, error)
Validators(ctx context.Context, heightPtr *int64, pagePtr, perPagePtr *int) (*coretypes.ResultValidators, error)
Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error)
}
// RPCUnsafe defines the set of "unsafe" methods that may optionally be

View File

@@ -8,7 +8,6 @@ import (
tmquery "github.com/tendermint/tendermint/internal/pubsub/query"
"github.com/tendermint/tendermint/internal/state/indexer"
"github.com/tendermint/tendermint/libs/bytes"
tmmath "github.com/tendermint/tendermint/libs/math"
"github.com/tendermint/tendermint/rpc/coretypes"
"github.com/tendermint/tendermint/types"
@@ -18,32 +17,27 @@ import (
// transaction is in the mempool, invalidated, or was not sent in the first
// place.
// More: https://docs.tendermint.com/master/rpc/#/Info/tx
func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) {
func (env *Environment) Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) {
// if index is disabled, return error
// N.B. The hash parameter is HexBytes so that the reflective parameter
// decoding logic in the HTTP service will correctly translate from JSON.
// See https://github.com/tendermint/tendermint/issues/6802 for context.
if !indexer.KVSinkEnabled(env.EventSinks) {
return nil, errors.New("transaction querying is disabled due to no kvEventSink")
}
for _, sink := range env.EventSinks {
if sink.Type() == indexer.KV {
r, err := sink.GetTxByHash(hash)
r, err := sink.GetTxByHash(req.Hash)
if r == nil {
return nil, fmt.Errorf("tx (%X) not found, err: %w", hash, err)
return nil, fmt.Errorf("tx (%X) not found, err: %w", req.Hash, err)
}
var proof types.TxProof
if prove {
if req.Prove {
block := env.BlockStore.LoadBlock(r.Height)
proof = block.Data.Txs.Proof(int(r.Index))
}
return &coretypes.ResultTx{
Hash: hash,
Hash: req.Hash,
Height: r.Height,
Index: r.Index,
TxResult: r.Result,
@@ -59,21 +53,14 @@ func (env *Environment) Tx(ctx context.Context, hash bytes.HexBytes, prove bool)
// TxSearch allows you to query for multiple transactions results. It returns a
// list of transactions (maximum ?per_page entries) and the total count.
// More: https://docs.tendermint.com/master/rpc/#/Info/tx_search
func (env *Environment) TxSearch(
ctx context.Context,
query string,
prove bool,
pagePtr, perPagePtr *int,
orderBy string,
) (*coretypes.ResultTxSearch, error) {
func (env *Environment) TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) {
if !indexer.KVSinkEnabled(env.EventSinks) {
return nil, fmt.Errorf("transaction searching is disabled due to no kvEventSink")
} else if len(query) > maxQueryLength {
} else if len(req.Query) > maxQueryLength {
return nil, errors.New("maximum query length exceeded")
}
q, err := tmquery.New(query)
q, err := tmquery.New(req.Query)
if err != nil {
return nil, err
}
@@ -86,7 +73,7 @@ func (env *Environment) TxSearch(
}
// sort results (must be done before pagination)
switch orderBy {
switch req.OrderBy {
case "desc", "":
sort.Slice(results, func(i, j int) bool {
if results[i].Height == results[j].Height {
@@ -107,9 +94,9 @@ func (env *Environment) TxSearch(
// paginate results
totalCount := len(results)
perPage := env.validatePerPage(perPagePtr)
perPage := env.validatePerPage(req.PerPage.IntPtr())
page, err := validatePage(pagePtr, perPage, totalCount)
page, err := validatePage(req.Page.IntPtr(), perPage, totalCount)
if err != nil {
return nil, err
}
@@ -122,7 +109,7 @@ func (env *Environment) TxSearch(
r := results[i]
var proof types.TxProof
if prove {
if req.Prove {
block := env.BlockStore.LoadBlock(r.Height)
proof = block.Data.Txs.Proof(int(r.Index))
}

View File

@@ -2,60 +2,148 @@ package proxy
import (
"context"
"time"
"github.com/tendermint/tendermint/internal/eventlog/cursor"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
lrpc "github.com/tendermint/tendermint/light/rpc"
rpcclient "github.com/tendermint/tendermint/rpc/client"
"github.com/tendermint/tendermint/rpc/coretypes"
)
// proxyService wraps a light RPC client to export the RPC service interfaces.
// This is needed because the service and the client use different signatures
// for some of the methods.
// The interfaces are implemented by delegating to the underlying node via the
// specified client.
type proxyService struct {
*lrpc.Client
Client *lrpc.Client
}
func (p proxyService) ABCIQuery(ctx context.Context, path string, data tmbytes.HexBytes, height int64, prove bool) (*coretypes.ResultABCIQuery, error) {
return p.ABCIQueryWithOptions(ctx, path, data, rpcclient.ABCIQueryOptions{
Height: height,
Prove: prove,
func (p proxyService) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) { panic("ok") }
func (p proxyService) ABCIQuery(ctx context.Context, req *coretypes.RequestABCIQuery) (*coretypes.ResultABCIQuery, error) {
return p.Client.ABCIQueryWithOptions(ctx, req.Path, req.Data, rpcclient.ABCIQueryOptions{
Height: int64(req.Height),
Prove: req.Prove,
})
}
func (p proxyService) Block(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlock, error) {
return p.Client.Block(ctx, (*int64)(req.Height))
}
func (p proxyService) BlockByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultBlock, error) {
return p.Client.BlockByHash(ctx, req.Hash)
}
func (p proxyService) BlockResults(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultBlockResults, error) {
return p.Client.BlockResults(ctx, (*int64)(req.Height))
}
func (p proxyService) BlockSearch(ctx context.Context, req *coretypes.RequestBlockSearch) (*coretypes.ResultBlockSearch, error) {
return p.Client.BlockSearch(ctx, req.Query, req.Page.IntPtr(), req.PerPage.IntPtr(), req.OrderBy)
}
func (p proxyService) BlockchainInfo(ctx context.Context, req *coretypes.RequestBlockchainInfo) (*coretypes.ResultBlockchainInfo, error) {
return p.Client.BlockchainInfo(ctx, int64(req.MinHeight), int64(req.MaxHeight))
}
func (p proxyService) BroadcastEvidence(ctx context.Context, req *coretypes.RequestBroadcastEvidence) (*coretypes.ResultBroadcastEvidence, error) {
return p.Client.BroadcastEvidence(ctx, req.Evidence)
}
func (p proxyService) BroadcastTxAsync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
return p.Client.BroadcastTxAsync(ctx, req.Tx)
}
func (p proxyService) BroadcastTxCommit(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTxCommit, error) {
return p.Client.BroadcastTxCommit(ctx, req.Tx)
}
func (p proxyService) BroadcastTxSync(ctx context.Context, req *coretypes.RequestBroadcastTx) (*coretypes.ResultBroadcastTx, error) {
return p.Client.BroadcastTxSync(ctx, req.Tx)
}
func (p proxyService) CheckTx(ctx context.Context, req *coretypes.RequestCheckTx) (*coretypes.ResultCheckTx, error) {
return p.Client.CheckTx(ctx, req.Tx)
}
func (p proxyService) Commit(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultCommit, error) {
return p.Client.Commit(ctx, (*int64)(req.Height))
}
func (p proxyService) ConsensusParams(ctx context.Context, req *coretypes.RequestConsensusParams) (*coretypes.ResultConsensusParams, error) {
return p.Client.ConsensusParams(ctx, (*int64)(req.Height))
}
func (p proxyService) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpConsensusState, error) {
return p.Client.DumpConsensusState(ctx)
}
func (p proxyService) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) {
return p.Client.Events(ctx, req)
}
func (p proxyService) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) {
return p.Client.Genesis(ctx)
}
func (p proxyService) GenesisChunked(ctx context.Context, req *coretypes.RequestGenesisChunked) (*coretypes.ResultGenesisChunk, error) {
return p.Client.GenesisChunked(ctx, uint(req.Chunk))
}
func (p proxyService) GetConsensusState(ctx context.Context) (*coretypes.ResultConsensusState, error) {
return p.ConsensusState(ctx)
return p.Client.ConsensusState(ctx)
}
func (p proxyService) Events(ctx context.Context,
filter *coretypes.EventFilter,
maxItems int,
before, after cursor.Cursor,
waitTime time.Duration,
) (*coretypes.ResultEvents, error) {
return p.Client.Events(ctx, &coretypes.RequestEvents{
Filter: filter,
MaxItems: maxItems,
Before: before.String(),
After: after.String(),
WaitTime: waitTime,
})
func (p proxyService) Header(ctx context.Context, req *coretypes.RequestBlockInfo) (*coretypes.ResultHeader, error) {
return p.Client.Header(ctx, (*int64)(req.Height))
}
func (p proxyService) Subscribe(ctx context.Context, query string) (*coretypes.ResultSubscribe, error) {
return p.SubscribeWS(ctx, query)
func (p proxyService) HeaderByHash(ctx context.Context, req *coretypes.RequestBlockByHash) (*coretypes.ResultHeader, error) {
return p.Client.HeaderByHash(ctx, req.Hash)
}
func (p proxyService) Unsubscribe(ctx context.Context, query string) (*coretypes.ResultUnsubscribe, error) {
return p.UnsubscribeWS(ctx, query)
func (p proxyService) Health(ctx context.Context) (*coretypes.ResultHealth, error) {
return p.Client.Health(ctx)
}
func (p proxyService) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) {
return p.Client.NetInfo(ctx)
}
func (p proxyService) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) {
return p.Client.NumUnconfirmedTxs(ctx)
}
func (p proxyService) RemoveTx(ctx context.Context, req *coretypes.RequestRemoveTx) error {
return p.Client.RemoveTx(ctx, req.TxKey)
}
func (p proxyService) Status(ctx context.Context) (*coretypes.ResultStatus, error) {
return p.Client.Status(ctx)
}
func (p proxyService) Subscribe(ctx context.Context, req *coretypes.RequestSubscribe) (*coretypes.ResultSubscribe, error) {
return p.Client.SubscribeWS(ctx, req.Query)
}
func (p proxyService) Tx(ctx context.Context, req *coretypes.RequestTx) (*coretypes.ResultTx, error) {
return p.Client.Tx(ctx, req.Hash, req.Prove)
}
func (p proxyService) TxSearch(ctx context.Context, req *coretypes.RequestTxSearch) (*coretypes.ResultTxSearch, error) {
return p.Client.TxSearch(ctx, req.Query, req.Prove, req.Page.IntPtr(), req.PerPage.IntPtr(), req.OrderBy)
}
func (p proxyService) UnconfirmedTxs(ctx context.Context, req *coretypes.RequestUnconfirmedTxs) (*coretypes.ResultUnconfirmedTxs, error) {
return p.Client.UnconfirmedTxs(ctx, req.Page.IntPtr(), req.PerPage.IntPtr())
}
func (p proxyService) Unsubscribe(ctx context.Context, req *coretypes.RequestUnsubscribe) (*coretypes.ResultUnsubscribe, error) {
return p.Client.UnsubscribeWS(ctx, req.Query)
}
func (p proxyService) UnsubscribeAll(ctx context.Context) (*coretypes.ResultUnsubscribe, error) {
return p.UnsubscribeAllWS(ctx)
return p.Client.UnsubscribeAllWS(ctx)
}
func (p proxyService) BroadcastEvidence(ctx context.Context, ev coretypes.Evidence) (*coretypes.ResultBroadcastEvidence, error) {
return p.Client.BroadcastEvidence(ctx, ev.Value)
func (p proxyService) Validators(ctx context.Context, req *coretypes.RequestValidators) (*coretypes.ResultValidators, error) {
return p.Client.Validators(ctx, (*int64)(req.Height), req.Page.IntPtr(), req.PerPage.IntPtr())
}

View File

@@ -103,13 +103,16 @@ func TestMinPollTime(t *testing.T) {
// wait time and reports no events.
ctx := context.Background()
filter := &coretypes.EventFilter{Query: `tm.event = 'good'`}
var zero cursor.Cursor
t.Run("NoneMatch", func(t *testing.T) {
start := time.Now()
// Request a very short delay, and affirm we got the server's minimum.
rsp, err := s.env.Events(ctx, filter, 1, zero, zero, 10*time.Millisecond)
rsp, err := s.env.Events(ctx, &coretypes.RequestEvents{
Filter: filter,
MaxItems: 1,
WaitTime: 10 * time.Millisecond,
})
if err != nil {
t.Fatalf("Events failed: %v", err)
} else if elapsed := time.Since(start); elapsed < time.Second {
@@ -128,7 +131,11 @@ func TestMinPollTime(t *testing.T) {
// Request a long-ish delay and affirm we don't block for it.
// Check for this by ensuring we return sooner than the minimum delay,
// since we don't know the exact timing.
rsp, err := s.env.Events(ctx, filter, 1, zero, zero, 10*time.Second)
rsp, err := s.env.Events(ctx, &coretypes.RequestEvents{
Filter: filter,
MaxItems: 1,
WaitTime: 10 * time.Second,
})
if err != nil {
t.Fatalf("Events failed: %v", err)
} else if elapsed := time.Since(start); elapsed > 500*time.Millisecond {
@@ -263,12 +270,5 @@ func (s *streamTester) advance(d time.Duration) { s.clock += int64(d) }
// environment as if it were a local RPC client. This works because the Events
// method only requires the event log, the other fields are unused.
func (s *streamTester) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) {
var before, after cursor.Cursor
if err := before.UnmarshalText([]byte(req.Before)); err != nil {
return nil, err
}
if err := after.UnmarshalText([]byte(req.After)); err != nil {
return nil, err
}
return s.env.Events(ctx, req.Filter, req.MaxItems, before, after, req.WaitTime)
return s.env.Events(ctx, req)
}

View File

@@ -213,10 +213,10 @@ func (c *baseRPCClient) ABCIQuery(ctx context.Context, path string, data bytes.H
func (c *baseRPCClient) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) {
result := new(coretypes.ResultABCIQuery)
if err := c.caller.Call(ctx, "abci_query", abciQueryArgs{
if err := c.caller.Call(ctx, "abci_query", &coretypes.RequestABCIQuery{
Path: path,
Data: data,
Height: opts.Height,
Height: coretypes.Int64(opts.Height),
Prove: opts.Prove,
}, result); err != nil {
return nil, err
@@ -226,7 +226,9 @@ func (c *baseRPCClient) ABCIQueryWithOptions(ctx context.Context, path string, d
func (c *baseRPCClient) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) {
result := new(coretypes.ResultBroadcastTxCommit)
if err := c.caller.Call(ctx, "broadcast_tx_commit", txArgs{Tx: tx}, result); err != nil {
if err := c.caller.Call(ctx, "broadcast_tx_commit", &coretypes.RequestBroadcastTx{
Tx: tx,
}, result); err != nil {
return nil, err
}
return result, nil
@@ -242,7 +244,7 @@ func (c *baseRPCClient) BroadcastTxSync(ctx context.Context, tx types.Tx) (*core
func (c *baseRPCClient) broadcastTX(ctx context.Context, route string, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
result := new(coretypes.ResultBroadcastTx)
if err := c.caller.Call(ctx, route, txArgs{Tx: tx}, result); err != nil {
if err := c.caller.Call(ctx, route, &coretypes.RequestBroadcastTx{Tx: tx}, result); err != nil {
return nil, err
}
return result, nil
@@ -251,7 +253,10 @@ func (c *baseRPCClient) broadcastTX(ctx context.Context, route string, tx types.
func (c *baseRPCClient) UnconfirmedTxs(ctx context.Context, page *int, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) {
result := new(coretypes.ResultUnconfirmedTxs)
if err := c.caller.Call(ctx, "unconfirmed_txs", unconfirmedArgs{Page: page, PerPage: perPage}, result); err != nil {
if err := c.caller.Call(ctx, "unconfirmed_txs", &coretypes.RequestUnconfirmedTxs{
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
}, result); err != nil {
return nil, err
}
return result, nil
@@ -267,14 +272,14 @@ func (c *baseRPCClient) NumUnconfirmedTxs(ctx context.Context) (*coretypes.Resul
func (c *baseRPCClient) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) {
result := new(coretypes.ResultCheckTx)
if err := c.caller.Call(ctx, "check_tx", txArgs{Tx: tx}, result); err != nil {
if err := c.caller.Call(ctx, "check_tx", &coretypes.RequestCheckTx{Tx: tx}, result); err != nil {
return nil, err
}
return result, nil
}
func (c *baseRPCClient) RemoveTx(ctx context.Context, txKey types.TxKey) error {
if err := c.caller.Call(ctx, "remove_tx", txKeyArgs{TxKey: txKey[:]}, nil); err != nil {
if err := c.caller.Call(ctx, "remove_tx", &coretypes.RequestRemoveTx{TxKey: txKey}, nil); err != nil {
return err
}
return nil
@@ -306,7 +311,9 @@ func (c *baseRPCClient) ConsensusState(ctx context.Context) (*coretypes.ResultCo
func (c *baseRPCClient) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) {
result := new(coretypes.ResultConsensusParams)
if err := c.caller.Call(ctx, "consensus_params", heightArgs{Height: height}, result); err != nil {
if err := c.caller.Call(ctx, "consensus_params", &coretypes.RequestConsensusParams{
Height: (*coretypes.Int64)(height),
}, result); err != nil {
return nil, err
}
return result, nil
@@ -330,9 +337,9 @@ func (c *baseRPCClient) Health(ctx context.Context) (*coretypes.ResultHealth, er
func (c *baseRPCClient) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
result := new(coretypes.ResultBlockchainInfo)
if err := c.caller.Call(ctx, "blockchain", blockchainInfoArgs{
MinHeight: minHeight,
MaxHeight: maxHeight,
if err := c.caller.Call(ctx, "blockchain", &coretypes.RequestBlockchainInfo{
MinHeight: coretypes.Int64(minHeight),
MaxHeight: coretypes.Int64(maxHeight),
}, result); err != nil {
return nil, err
}
@@ -349,7 +356,9 @@ func (c *baseRPCClient) Genesis(ctx context.Context) (*coretypes.ResultGenesis,
func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*coretypes.ResultGenesisChunk, error) {
result := new(coretypes.ResultGenesisChunk)
if err := c.caller.Call(ctx, "genesis_chunked", genesisChunkArgs{Chunk: id}, result); err != nil {
if err := c.caller.Call(ctx, "genesis_chunked", &coretypes.RequestGenesisChunked{
Chunk: coretypes.Int64(id),
}, result); err != nil {
return nil, err
}
return result, nil
@@ -357,7 +366,9 @@ func (c *baseRPCClient) GenesisChunked(ctx context.Context, id uint) (*coretypes
func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) {
result := new(coretypes.ResultBlock)
if err := c.caller.Call(ctx, "block", heightArgs{Height: height}, result); err != nil {
if err := c.caller.Call(ctx, "block", &coretypes.RequestBlockInfo{
Height: (*coretypes.Int64)(height),
}, result); err != nil {
return nil, err
}
return result, nil
@@ -365,7 +376,7 @@ func (c *baseRPCClient) Block(ctx context.Context, height *int64) (*coretypes.Re
func (c *baseRPCClient) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) {
result := new(coretypes.ResultBlock)
if err := c.caller.Call(ctx, "block_by_hash", hashArgs{Hash: hash}, result); err != nil {
if err := c.caller.Call(ctx, "block_by_hash", &coretypes.RequestBlockByHash{Hash: hash}, result); err != nil {
return nil, err
}
return result, nil
@@ -373,7 +384,9 @@ func (c *baseRPCClient) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*
func (c *baseRPCClient) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) {
result := new(coretypes.ResultBlockResults)
if err := c.caller.Call(ctx, "block_results", heightArgs{Height: height}, result); err != nil {
if err := c.caller.Call(ctx, "block_results", &coretypes.RequestBlockInfo{
Height: (*coretypes.Int64)(height),
}, result); err != nil {
return nil, err
}
return result, nil
@@ -381,7 +394,9 @@ func (c *baseRPCClient) BlockResults(ctx context.Context, height *int64) (*coret
func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) {
result := new(coretypes.ResultHeader)
if err := c.caller.Call(ctx, "header", heightArgs{Height: height}, result); err != nil {
if err := c.caller.Call(ctx, "header", &coretypes.RequestBlockInfo{
Height: (*coretypes.Int64)(height),
}, result); err != nil {
return nil, err
}
return result, nil
@@ -389,7 +404,9 @@ func (c *baseRPCClient) Header(ctx context.Context, height *int64) (*coretypes.R
func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) {
result := new(coretypes.ResultHeader)
if err := c.caller.Call(ctx, "header_by_hash", hashArgs{Hash: hash}, result); err != nil {
if err := c.caller.Call(ctx, "header_by_hash", &coretypes.RequestBlockByHash{
Hash: hash,
}, result); err != nil {
return nil, err
}
return result, nil
@@ -397,7 +414,9 @@ func (c *baseRPCClient) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (
func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) {
result := new(coretypes.ResultCommit)
if err := c.caller.Call(ctx, "commit", heightArgs{Height: height}, result); err != nil {
if err := c.caller.Call(ctx, "commit", &coretypes.RequestBlockInfo{
Height: (*coretypes.Int64)(height),
}, result); err != nil {
return nil, err
}
return result, nil
@@ -405,7 +424,7 @@ func (c *baseRPCClient) Commit(ctx context.Context, height *int64) (*coretypes.R
func (c *baseRPCClient) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) {
result := new(coretypes.ResultTx)
if err := c.caller.Call(ctx, "tx", hashArgs{Hash: hash, Prove: prove}, result); err != nil {
if err := c.caller.Call(ctx, "tx", &coretypes.RequestTx{Hash: hash, Prove: prove}, result); err != nil {
return nil, err
}
return result, nil
@@ -413,12 +432,12 @@ func (c *baseRPCClient) Tx(ctx context.Context, hash bytes.HexBytes, prove bool)
func (c *baseRPCClient) TxSearch(ctx context.Context, query string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) {
result := new(coretypes.ResultTxSearch)
if err := c.caller.Call(ctx, "tx_search", searchArgs{
if err := c.caller.Call(ctx, "tx_search", &coretypes.RequestTxSearch{
Query: query,
Prove: prove,
OrderBy: orderBy,
Page: page,
PerPage: perPage,
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
}, result); err != nil {
return nil, err
}
@@ -428,11 +447,11 @@ func (c *baseRPCClient) TxSearch(ctx context.Context, query string, prove bool,
func (c *baseRPCClient) BlockSearch(ctx context.Context, query string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) {
result := new(coretypes.ResultBlockSearch)
if err := c.caller.Call(ctx, "block_search", searchArgs{
if err := c.caller.Call(ctx, "block_search", &coretypes.RequestBlockSearch{
Query: query,
OrderBy: orderBy,
Page: page,
PerPage: perPage,
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
}, result); err != nil {
return nil, err
}
@@ -442,10 +461,10 @@ func (c *baseRPCClient) BlockSearch(ctx context.Context, query string, page, per
func (c *baseRPCClient) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) {
result := new(coretypes.ResultValidators)
if err := c.caller.Call(ctx, "validators", validatorArgs{
Height: height,
Page: page,
PerPage: perPage,
if err := c.caller.Call(ctx, "validators", &coretypes.RequestValidators{
Height: (*coretypes.Int64)(height),
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
}, result); err != nil {
return nil, err
}
@@ -454,8 +473,8 @@ func (c *baseRPCClient) Validators(ctx context.Context, height *int64, page, per
func (c *baseRPCClient) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) {
result := new(coretypes.ResultBroadcastEvidence)
if err := c.caller.Call(ctx, "broadcast_evidence", evidenceArgs{
Evidence: coretypes.Evidence{Value: ev},
if err := c.caller.Call(ctx, "broadcast_evidence", &coretypes.RequestBroadcastEvidence{
Evidence: ev,
}, result); err != nil {
return nil, err
}

View File

@@ -1,65 +0,0 @@
package http
// The types in this file define the JSON encoding for RPC method parameters
// from the client to the server.
import (
"github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/rpc/coretypes"
)
type abciQueryArgs struct {
Path string `json:"path"`
Data bytes.HexBytes `json:"data"`
Height int64 `json:"height,string"`
Prove bool `json:"prove"`
}
type txArgs struct {
Tx []byte `json:"tx"`
}
type txKeyArgs struct {
TxKey []byte `json:"tx_key"`
}
type unconfirmedArgs struct {
Page *int `json:"page,string,omitempty"`
PerPage *int `json:"per_page,string,omitempty"`
}
type heightArgs struct {
Height *int64 `json:"height,string,omitempty"`
}
type hashArgs struct {
Hash bytes.HexBytes `json:"hash"`
Prove bool `json:"prove,omitempty"`
}
type blockchainInfoArgs struct {
MinHeight int64 `json:"minHeight,string"`
MaxHeight int64 `json:"maxHeight,string"`
}
type genesisChunkArgs struct {
Chunk uint `json:"chunk,string"`
}
type searchArgs struct {
Query string `json:"query"`
Prove bool `json:"prove,omitempty"`
OrderBy string `json:"order_by,omitempty"`
Page *int `json:"page,string,omitempty"`
PerPage *int `json:"per_page,string,omitempty"`
}
type validatorArgs struct {
Height *int64 `json:"height,string,omitempty"`
Page *int `json:"page,string,omitempty"`
PerPage *int `json:"per_page,string,omitempty"`
}
type evidenceArgs struct {
Evidence coretypes.Evidence `json:"evidence"`
}

View File

@@ -7,7 +7,6 @@ import (
"time"
"github.com/tendermint/tendermint/internal/eventbus"
"github.com/tendermint/tendermint/internal/eventlog/cursor"
"github.com/tendermint/tendermint/internal/pubsub"
"github.com/tendermint/tendermint/internal/pubsub/query"
rpccore "github.com/tendermint/tendermint/internal/rpc/core"
@@ -79,23 +78,28 @@ func (c *Local) ABCIQuery(ctx context.Context, path string, data bytes.HexBytes)
}
func (c *Local) ABCIQueryWithOptions(ctx context.Context, path string, data bytes.HexBytes, opts rpcclient.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) {
return c.env.ABCIQuery(ctx, path, data, opts.Height, opts.Prove)
return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{
Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove,
})
}
func (c *Local) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) {
return c.env.BroadcastTxCommit(ctx, tx)
return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
}
func (c *Local) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
return c.env.BroadcastTxAsync(ctx, tx)
return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
}
func (c *Local) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
return c.env.BroadcastTxSync(ctx, tx)
return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
}
func (c *Local) UnconfirmedTxs(ctx context.Context, page, perPage *int) (*coretypes.ResultUnconfirmedTxs, error) {
return c.env.UnconfirmedTxs(ctx, page, perPage)
return c.env.UnconfirmedTxs(ctx, &coretypes.RequestUnconfirmedTxs{
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
})
}
func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfirmedTxs, error) {
@@ -103,7 +107,7 @@ func (c *Local) NumUnconfirmedTxs(ctx context.Context) (*coretypes.ResultUnconfi
}
func (c *Local) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) {
return c.env.CheckTx(ctx, tx)
return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx})
}
func (c *Local) RemoveTx(ctx context.Context, txKey types.TxKey) error {
@@ -123,18 +127,11 @@ func (c *Local) ConsensusState(ctx context.Context) (*coretypes.ResultConsensusS
}
func (c *Local) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) {
return c.env.ConsensusParams(ctx, height)
return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)})
}
func (c *Local) Events(ctx context.Context, req *coretypes.RequestEvents) (*coretypes.ResultEvents, error) {
var before, after cursor.Cursor
if err := before.UnmarshalText([]byte(req.Before)); err != nil {
return nil, err
}
if err := after.UnmarshalText([]byte(req.After)); err != nil {
return nil, err
}
return c.env.Events(ctx, req.Filter, req.MaxItems, before, after, req.WaitTime)
return c.env.Events(ctx, req)
}
func (c *Local) Health(ctx context.Context) (*coretypes.ResultHealth, error) {
@@ -142,7 +139,10 @@ func (c *Local) Health(ctx context.Context) (*coretypes.ResultHealth, error) {
}
func (c *Local) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
return c.env.BlockchainInfo(ctx, minHeight, maxHeight)
return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{
MinHeight: coretypes.Int64(minHeight),
MaxHeight: coretypes.Int64(maxHeight),
})
}
func (c *Local) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) {
@@ -150,51 +150,66 @@ func (c *Local) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) {
}
func (c *Local) GenesisChunked(ctx context.Context, id uint) (*coretypes.ResultGenesisChunk, error) {
return c.env.GenesisChunked(ctx, id)
return c.env.GenesisChunked(ctx, &coretypes.RequestGenesisChunked{Chunk: coretypes.Int64(id)})
}
func (c *Local) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) {
return c.env.Block(ctx, height)
return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
}
func (c *Local) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) {
return c.env.BlockByHash(ctx, hash)
return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash})
}
func (c *Local) BlockResults(ctx context.Context, height *int64) (*coretypes.ResultBlockResults, error) {
return c.env.BlockResults(ctx, height)
return c.env.BlockResults(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
}
func (c *Local) Header(ctx context.Context, height *int64) (*coretypes.ResultHeader, error) {
return c.env.Header(ctx, height)
return c.env.Header(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
}
func (c *Local) HeaderByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) {
return c.env.HeaderByHash(ctx, hash)
return c.env.HeaderByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash})
}
func (c *Local) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) {
return c.env.Commit(ctx, height)
return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
}
func (c *Local) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) {
return c.env.Validators(ctx, height, page, perPage)
return c.env.Validators(ctx, &coretypes.RequestValidators{
Height: (*coretypes.Int64)(height),
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
})
}
func (c *Local) Tx(ctx context.Context, hash bytes.HexBytes, prove bool) (*coretypes.ResultTx, error) {
return c.env.Tx(ctx, hash, prove)
return c.env.Tx(ctx, &coretypes.RequestTx{Hash: hash, Prove: prove})
}
func (c *Local) TxSearch(ctx context.Context, queryString string, prove bool, page, perPage *int, orderBy string) (*coretypes.ResultTxSearch, error) {
return c.env.TxSearch(ctx, queryString, prove, page, perPage, orderBy)
return c.env.TxSearch(ctx, &coretypes.RequestTxSearch{
Query: queryString,
Prove: prove,
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
OrderBy: orderBy,
})
}
func (c *Local) BlockSearch(ctx context.Context, queryString string, page, perPage *int, orderBy string) (*coretypes.ResultBlockSearch, error) {
return c.env.BlockSearch(ctx, queryString, page, perPage, orderBy)
return c.env.BlockSearch(ctx, &coretypes.RequestBlockSearch{
Query: queryString,
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
OrderBy: orderBy,
})
}
func (c *Local) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) {
return c.env.BroadcastEvidence(ctx, coretypes.Evidence{Value: ev})
return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev})
}
func (c *Local) Subscribe(ctx context.Context, subscriber, queryString string, capacity ...int) (<-chan coretypes.ResultEvent, error) {

View File

@@ -91,23 +91,25 @@ func (c Client) ABCIQueryWithOptions(
path string,
data bytes.HexBytes,
opts client.ABCIQueryOptions) (*coretypes.ResultABCIQuery, error) {
return c.env.ABCIQuery(ctx, path, data, opts.Height, opts.Prove)
return c.env.ABCIQuery(ctx, &coretypes.RequestABCIQuery{
Path: path, Data: data, Height: coretypes.Int64(opts.Height), Prove: opts.Prove,
})
}
func (c Client) BroadcastTxCommit(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTxCommit, error) {
return c.env.BroadcastTxCommit(ctx, tx)
return c.env.BroadcastTxCommit(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
}
func (c Client) BroadcastTxAsync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
return c.env.BroadcastTxAsync(ctx, tx)
return c.env.BroadcastTxAsync(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
}
func (c Client) BroadcastTxSync(ctx context.Context, tx types.Tx) (*coretypes.ResultBroadcastTx, error) {
return c.env.BroadcastTxSync(ctx, tx)
return c.env.BroadcastTxSync(ctx, &coretypes.RequestBroadcastTx{Tx: tx})
}
func (c Client) CheckTx(ctx context.Context, tx types.Tx) (*coretypes.ResultCheckTx, error) {
return c.env.CheckTx(ctx, tx)
return c.env.CheckTx(ctx, &coretypes.RequestCheckTx{Tx: tx})
}
func (c Client) NetInfo(ctx context.Context) (*coretypes.ResultNetInfo, error) {
@@ -123,7 +125,7 @@ func (c Client) DumpConsensusState(ctx context.Context) (*coretypes.ResultDumpCo
}
func (c Client) ConsensusParams(ctx context.Context, height *int64) (*coretypes.ResultConsensusParams, error) {
return c.env.ConsensusParams(ctx, height)
return c.env.ConsensusParams(ctx, &coretypes.RequestConsensusParams{Height: (*coretypes.Int64)(height)})
}
func (c Client) Health(ctx context.Context) (*coretypes.ResultHealth, error) {
@@ -131,7 +133,10 @@ func (c Client) Health(ctx context.Context) (*coretypes.ResultHealth, error) {
}
func (c Client) BlockchainInfo(ctx context.Context, minHeight, maxHeight int64) (*coretypes.ResultBlockchainInfo, error) {
return c.env.BlockchainInfo(ctx, minHeight, maxHeight)
return c.env.BlockchainInfo(ctx, &coretypes.RequestBlockchainInfo{
MinHeight: coretypes.Int64(minHeight),
MaxHeight: coretypes.Int64(maxHeight),
})
}
func (c Client) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) {
@@ -139,21 +144,25 @@ func (c Client) Genesis(ctx context.Context) (*coretypes.ResultGenesis, error) {
}
func (c Client) Block(ctx context.Context, height *int64) (*coretypes.ResultBlock, error) {
return c.env.Block(ctx, height)
return c.env.Block(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
}
func (c Client) BlockByHash(ctx context.Context, hash bytes.HexBytes) (*coretypes.ResultBlock, error) {
return c.env.BlockByHash(ctx, hash)
return c.env.BlockByHash(ctx, &coretypes.RequestBlockByHash{Hash: hash})
}
func (c Client) Commit(ctx context.Context, height *int64) (*coretypes.ResultCommit, error) {
return c.env.Commit(ctx, height)
return c.env.Commit(ctx, &coretypes.RequestBlockInfo{Height: (*coretypes.Int64)(height)})
}
func (c Client) Validators(ctx context.Context, height *int64, page, perPage *int) (*coretypes.ResultValidators, error) {
return c.env.Validators(ctx, height, page, perPage)
return c.env.Validators(ctx, &coretypes.RequestValidators{
Height: (*coretypes.Int64)(height),
Page: coretypes.Int64Ptr(page),
PerPage: coretypes.Int64Ptr(perPage),
})
}
func (c Client) BroadcastEvidence(ctx context.Context, ev types.Evidence) (*coretypes.ResultBroadcastEvidence, error) {
return c.env.BroadcastEvidence(ctx, coretypes.Evidence{Value: ev})
return c.env.BroadcastEvidence(ctx, &coretypes.RequestBroadcastEvidence{Evidence: ev})
}

188
rpc/coretypes/requests.go Normal file
View File

@@ -0,0 +1,188 @@
package coretypes
import (
"encoding/json"
"strconv"
"time"
"github.com/tendermint/tendermint/internal/jsontypes"
"github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/types"
)
type RequestSubscribe struct {
Query string `json:"query"`
}
type RequestUnsubscribe struct {
Query string `json:"query"`
}
type RequestBlockchainInfo struct {
MinHeight Int64 `json:"minHeight"`
MaxHeight Int64 `json:"maxHeight"`
}
type RequestGenesisChunked struct {
Chunk Int64 `json:"chunk"`
}
type RequestBlockInfo struct {
Height *Int64 `json:"height"`
}
type RequestBlockByHash struct {
Hash bytes.HexBytes `json:"hash"`
}
type RequestCheckTx struct {
Tx types.Tx `json:"tx"`
}
type RequestRemoveTx struct {
TxKey types.TxKey `json:"txkey"`
}
type RequestTx struct {
Hash bytes.HexBytes `json:"hash"`
Prove bool `json:"prove"`
}
type RequestTxSearch struct {
Query string `json:"query"`
Prove bool `json:"prove"`
Page *Int64 `json:"page"`
PerPage *Int64 `json:"per_page"`
OrderBy string `json:"order_by"`
}
type RequestBlockSearch struct {
Query string `json:"query"`
Page *Int64 `json:"page"`
PerPage *Int64 `json:"per_page"`
OrderBy string `json:"order_by"`
}
type RequestValidators struct {
Height *Int64 `json:"height"`
Page *Int64 `json:"page"`
PerPage *Int64 `json:"per_page"`
}
type RequestConsensusParams struct {
Height *Int64 `json:"height"`
}
type RequestUnconfirmedTxs struct {
Page *Int64 `json:"page"`
PerPage *Int64 `json:"per_page"`
}
type RequestBroadcastTx struct {
Tx types.Tx `json:"tx"`
}
type RequestABCIQuery struct {
Path string `json:"path"`
Data bytes.HexBytes `json:"data"`
Height Int64 `json:"height"`
Prove bool `json:"prove"`
}
type RequestBroadcastEvidence struct {
Evidence types.Evidence
}
type requestBroadcastEvidenceJSON struct {
Evidence json.RawMessage `json:"evidence"`
}
func (r RequestBroadcastEvidence) MarshalJSON() ([]byte, error) {
ev, err := jsontypes.Marshal(r.Evidence)
if err != nil {
return nil, err
}
return json.Marshal(requestBroadcastEvidenceJSON{
Evidence: ev,
})
}
func (r *RequestBroadcastEvidence) UnmarshalJSON(data []byte) error {
var val requestBroadcastEvidenceJSON
if err := json.Unmarshal(data, &val); err != nil {
return err
}
if err := jsontypes.Unmarshal(val.Evidence, &r.Evidence); err != nil {
return err
}
return nil
}
// RequestEvents is the argument for the "/events" RPC endpoint.
type RequestEvents struct {
// Optional filter spec. If nil or empty, all items are eligible.
Filter *EventFilter `json:"filter"`
// The maximum number of eligible items to return.
// If zero or negative, the server will report a default number.
MaxItems int `json:"maxItems"`
// Return only items after this cursor. If empty, the limit is just
// before the the beginning of the event log.
After string `json:"after"`
// Return only items before this cursor. If empty, the limit is just
// after the head of the event log.
Before string `json:"before"`
// Wait for up to this long for events to be available.
WaitTime time.Duration `json:"waitTime"`
}
// An EventFilter specifies which events are selected by an /events request.
type EventFilter struct {
Query string `json:"query"`
}
// Int64 is a wrapper for int64 that encodes to JSON as a string and can be
// decoded from either a string or a number value.
type Int64 int64
func (z *Int64) UnmarshalJSON(data []byte) error {
var s string
if len(data) != 0 && data[0] == '"' {
if err := json.Unmarshal(data, &s); err != nil {
return err
}
} else {
s = string(data)
}
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return err
}
*z = Int64(v)
return nil
}
func (z Int64) MarshalJSON() ([]byte, error) {
return []byte(strconv.FormatInt(int64(z), 10)), nil
}
// IntPtr returns a pointer to the value of *z as an int, or nil if z == nil.
func (z *Int64) IntPtr() *int {
if z == nil {
return nil
}
v := int(*z)
return &v
}
// Int64Ptr returns an *Int64 that points to the same value as v, or nil.
func Int64Ptr(v *int) *Int64 {
if v == nil {
return nil
}
z := Int64(*v)
return &z
}

View File

@@ -357,32 +357,6 @@ type Evidence struct {
func (e Evidence) MarshalJSON() ([]byte, error) { return jsontypes.Marshal(e.Value) }
func (e *Evidence) UnmarshalJSON(data []byte) error { return jsontypes.Unmarshal(data, &e.Value) }
// RequestEvents is the argument for the "/events" RPC endpoint.
type RequestEvents struct {
// Optional filter spec. If nil or empty, all items are eligible.
Filter *EventFilter `json:"filter"`
// The maximum number of eligible items to return.
// If zero or negative, the server will report a default number.
MaxItems int `json:"maxItems"`
// Return only items after this cursor. If empty, the limit is just
// before the the beginning of the event log.
After string `json:"after"`
// Return only items before this cursor. If empty, the limit is just
// after the head of the event log.
Before string `json:"before"`
// Wait for up to this long for events to be available.
WaitTime time.Duration `json:"waitTime"`
}
// An EventFilter specifies which events are selected by an /events request.
type EventFilter struct {
Query string `json:"query"`
}
// ResultEvents is the response from the "/events" RPC endpoint.
type ResultEvents struct {
// The items matching the request parameters, from newest

View File

@@ -55,7 +55,7 @@
// Define some routes
//
// var Routes = map[string]*rpcserver.RPCFunc{
// "status": rpcserver.NewRPCFunc(Status, "arg"),
// "status": rpcserver.NewRPCFunc(Status),
// }
//
// An rpc function:

View File

@@ -34,49 +34,65 @@ const (
testVal = "acbd"
)
type RequestEcho struct {
Value string `json:"arg"`
}
type ResultEcho struct {
Value string `json:"value"`
}
type RequestEchoInt struct {
Value int `json:"arg"`
}
type ResultEchoInt struct {
Value int `json:"value"`
}
type RequestEchoBytes struct {
Value []byte `json:"arg"`
}
type ResultEchoBytes struct {
Value []byte `json:"value"`
}
type RequestEchoDataBytes struct {
Value tmbytes.HexBytes `json:"arg"`
}
type ResultEchoDataBytes struct {
Value tmbytes.HexBytes `json:"value"`
}
// Define some routes
var Routes = map[string]*server.RPCFunc{
"echo": server.NewRPCFunc(EchoResult, "arg"),
"echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"),
"echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"),
"echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult, "arg"),
"echo_int": server.NewRPCFunc(EchoIntResult, "arg"),
"echo": server.NewRPCFunc(EchoResult),
"echo_ws": server.NewWSRPCFunc(EchoWSResult),
"echo_bytes": server.NewRPCFunc(EchoBytesResult),
"echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult),
"echo_int": server.NewRPCFunc(EchoIntResult),
}
func EchoResult(ctx context.Context, v string) (*ResultEcho, error) {
return &ResultEcho{v}, nil
func EchoResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) {
return &ResultEcho{v.Value}, nil
}
func EchoWSResult(ctx context.Context, v string) (*ResultEcho, error) {
return &ResultEcho{v}, nil
func EchoWSResult(ctx context.Context, v *RequestEcho) (*ResultEcho, error) {
return &ResultEcho{v.Value}, nil
}
func EchoIntResult(ctx context.Context, v int) (*ResultEchoInt, error) {
return &ResultEchoInt{v}, nil
func EchoIntResult(ctx context.Context, v *RequestEchoInt) (*ResultEchoInt, error) {
return &ResultEchoInt{v.Value}, nil
}
func EchoBytesResult(ctx context.Context, v []byte) (*ResultEchoBytes, error) {
return &ResultEchoBytes{v}, nil
func EchoBytesResult(ctx context.Context, v *RequestEchoBytes) (*ResultEchoBytes, error) {
return &ResultEchoBytes{v.Value}, nil
}
func EchoDataBytesResult(ctx context.Context, v tmbytes.HexBytes) (*ResultEchoDataBytes, error) {
return &ResultEchoDataBytes{v}, nil
func EchoDataBytesResult(ctx context.Context, v *RequestEchoDataBytes) (*ResultEchoDataBytes, error) {
return &ResultEchoDataBytes{v.Value}, nil
}
// launch unix and tcp servers

View File

@@ -2,15 +2,11 @@ package server
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"net/http"
"reflect"
"strconv"
"strings"
"github.com/tendermint/tendermint/libs/log"
@@ -70,19 +66,11 @@ func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.Han
RPCRequest: &req,
HTTPRequest: hreq,
})
args, err := parseParams(ctx, rpcFunc, req.Params)
result, err := rpcFunc.Call(ctx, req.Params)
if err != nil {
responses = append(responses,
req.MakeErrorf(rpctypes.CodeInvalidParams, "converting JSON parameters: %v", err))
continue
}
returns := rpcFunc.f.Call(args)
result, err := unreflectResult(returns)
if err == nil {
responses = append(responses, req.MakeResponse(result))
} else {
responses = append(responses, req.MakeError(err))
} else {
responses = append(responses, req.MakeResponse(result))
}
}
@@ -124,103 +112,6 @@ func parseRequests(data []byte) ([]rpctypes.RPCRequest, error) {
return reqs, nil
}
// parseParams parses the JSON parameters of rpcReq into the arguments of fn,
// returning the corresponding argument values or an error.
func parseParams(ctx context.Context, fn *RPCFunc, paramData []byte) ([]reflect.Value, error) {
params, err := parseJSONParams(fn, paramData)
if err != nil {
return nil, err
}
args := make([]reflect.Value, 1+len(params))
args[0] = reflect.ValueOf(ctx)
for i, param := range params {
ptype := fn.args[i+1]
if len(param) == 0 {
args[i+1] = reflect.Zero(ptype)
continue
}
var pval reflect.Value
isPtr := ptype.Kind() == reflect.Ptr
if isPtr {
pval = reflect.New(ptype.Elem())
} else {
pval = reflect.New(ptype)
}
baseType := pval.Type().Elem()
if isIntType(baseType) && isStringValue(param) {
var z int64String
if err := json.Unmarshal(param, &z); err != nil {
return nil, fmt.Errorf("decoding string %q: %w", fn.argNames[i], err)
}
pval.Elem().Set(reflect.ValueOf(z).Convert(baseType))
} else if err := json.Unmarshal(param, pval.Interface()); err != nil {
return nil, fmt.Errorf("decoding %q: %w", fn.argNames[i], err)
}
if isPtr {
args[i+1] = pval
} else {
args[i+1] = pval.Elem()
}
}
return args, nil
}
// parseJSONParams parses data and returns a slice of JSON values matching the
// positional parameters of fn. It reports an error if data is not "null" and
// does not encode an object or an array, or if the number of array parameters
// does not match the argument list of fn (excluding the context).
func parseJSONParams(fn *RPCFunc, data []byte) ([]json.RawMessage, error) {
base := bytes.TrimSpace(data)
if bytes.HasPrefix(base, []byte("{")) {
var m map[string]json.RawMessage
if err := json.Unmarshal(base, &m); err != nil {
return nil, fmt.Errorf("decoding parameter object: %w", err)
}
out := make([]json.RawMessage, len(fn.argNames))
for i, name := range fn.argNames {
if p, ok := m[name]; ok {
out[i] = p
}
}
return out, nil
} else if bytes.HasPrefix(base, []byte("[")) {
var m []json.RawMessage
if err := json.Unmarshal(base, &m); err != nil {
return nil, fmt.Errorf("decoding parameter array: %w", err)
}
if len(m) != len(fn.argNames) {
return nil, fmt.Errorf("got %d parameters, want %d", len(m), len(fn.argNames))
}
return m, nil
} else if bytes.Equal(base, []byte("null")) {
return make([]json.RawMessage, len(fn.argNames)), nil
}
return nil, errors.New("parameters must be an object or an array")
}
// isStringValue reports whether data is a JSON string value.
func isStringValue(data json.RawMessage) bool {
return len(data) != 0 && data[0] == '"'
}
type int64String int64
func (z *int64String) UnmarshalText(data []byte) error {
v, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
return err
}
*z = int64String(v)
return nil
}
// writes a list of available rpc endpoints as an html page
func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) {
hasArgs := make(map[string]string)

View File

@@ -17,9 +17,16 @@ import (
)
func testMux() *http.ServeMux {
type testArgs struct {
S string `json:"s"`
I json.Number `json:"i"`
}
type blockArgs struct {
H json.Number `json:"h"`
}
funcMap := map[string]*RPCFunc{
"c": NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) { return "foo", nil }, "s", "i"),
"block": NewRPCFunc(func(ctx context.Context, h int) (string, error) { return "block", nil }, "height"),
"c": NewRPCFunc(func(ctx context.Context, arg *testArgs) (string, error) { return "foo", nil }),
"block": NewRPCFunc(func(ctx context.Context, arg *blockArgs) (string, error) { return "block", nil }),
}
mux := http.NewServeMux()
logger := log.NewNopLogger()
@@ -46,7 +53,7 @@ func TestRPCParams(t *testing.T) {
// id not captured in JSON parsing failures
{`{"method": "c", "id": "0", "params": a}`, "invalid character", ""},
{`{"method": "c", "id": "0", "params": ["a"]}`, "got 1", `"0"`},
{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid syntax", `"0"`},
{`{"method": "c", "id": "0", "params": ["a", "b"]}`, "invalid number", `"0"`},
{`{"method": "c", "id": "0", "params": [1, 1]}`, "of type string", `"0"`},
// no ID - notification

View File

@@ -1,13 +1,11 @@
package server
import (
"context"
"encoding"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
@@ -25,7 +23,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit
ctx := rpctypes.WithCallInfo(req.Context(), &rpctypes.CallInfo{
HTTPRequest: req,
})
args, err := parseURLParams(ctx, rpcFunc, req)
args, err := parseURLParams(rpcFunc.argNames, req)
if err != nil {
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusBadRequest)
@@ -33,10 +31,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit
return
}
jreq := rpctypes.NewRequest(uriReqID)
outs := rpcFunc.f.Call(args)
logger.Debug("HTTPRestRPC", "method", req.URL.Path, "args", args, "returns", outs)
result, err := unreflectResult(outs)
result, err := rpcFunc.Call(ctx, args)
if err == nil {
writeHTTPResponse(w, logger, jreq.MakeResponse(result))
} else {
@@ -45,7 +40,7 @@ func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWrit
}
}
func parseURLParams(ctx context.Context, rf *RPCFunc, req *http.Request) ([]reflect.Value, error) {
func parseURLParams(argNames []string, req *http.Request) ([]byte, error) {
if err := req.ParseForm(); err != nil {
return nil, fmt.Errorf("invalid HTTP request: %w", err)
}
@@ -56,112 +51,35 @@ func parseURLParams(ctx context.Context, rf *RPCFunc, req *http.Request) ([]refl
return "", false
}
vals := make([]reflect.Value, len(rf.argNames)+1)
vals[0] = reflect.ValueOf(ctx)
for i, name := range rf.argNames {
atype := rf.args[i+1]
text, ok := getArg(name)
params := make(map[string]interface{})
for _, name := range argNames {
v, ok := getArg(name)
if !ok {
vals[i+1] = reflect.Zero(atype)
continue
}
val, err := parseArgValue(atype, text)
if err != nil {
return nil, fmt.Errorf("decoding parameter %q: %w", name, err)
if z, err := decodeInteger(v); err == nil {
params[name] = z
} else if b, err := strconv.ParseBool(v); err == nil {
params[name] = b
} else if lc := strings.ToLower(v); strings.HasPrefix(lc, "0x") {
dec, err := hex.DecodeString(lc[2:])
if err != nil {
return nil, fmt.Errorf("invalid hex string: %w", err)
} else if len(dec) == 0 {
return nil, errors.New("invalid empty hex string")
}
params[name] = dec
} else if isQuotedString(v) {
var dec string
if err := json.Unmarshal([]byte(v), &dec); err != nil {
return nil, fmt.Errorf("invalid quoted string: %w", err)
}
params[name] = dec
} else {
params[name] = v
}
vals[i+1] = val
}
return vals, nil
}
func parseArgValue(atype reflect.Type, text string) (reflect.Value, error) {
// Regardless whether the argument is a pointer type, allocate a pointer so
// we can set the computed value.
var out reflect.Value
isPtr := atype.Kind() == reflect.Ptr
if isPtr {
out = reflect.New(atype.Elem())
} else {
out = reflect.New(atype)
}
baseType := out.Type().Elem()
if isIntType(baseType) {
// Integral type: Require a base-10 digit string. For compatibility with
// existing use allow quotation marks.
v, err := decodeInteger(text)
if err != nil {
return reflect.Value{}, fmt.Errorf("invalid integer: %w", err)
}
out.Elem().Set(reflect.ValueOf(v).Convert(baseType))
} else if isStringOrBytes(baseType) {
// String or byte slice: Check for quotes, hex encoding.
dec, err := decodeString(text)
if err != nil {
return reflect.Value{}, err
}
out.Elem().Set(reflect.ValueOf(dec).Convert(baseType))
} else if baseType.Kind() == reflect.Bool {
b, err := strconv.ParseBool(text)
if err != nil {
return reflect.Value{}, fmt.Errorf("invalid boolean: %w", err)
}
out.Elem().Set(reflect.ValueOf(b))
} else if out.Type().Implements(textUnmarshalerType) {
s, err := decodeString(text)
if err != nil {
return reflect.Value{}, err
}
v := reflect.New(baseType)
dec := v.Interface().(encoding.TextUnmarshaler)
if err := dec.UnmarshalText(s); err != nil {
return reflect.Value{}, fmt.Errorf("invalid text: %w", err)
}
out.Elem().Set(v.Elem())
} else {
// We don't know how to represent other types.
return reflect.Value{}, fmt.Errorf("unsupported argument type %v", baseType)
}
// If the argument wants a pointer, return the value as-is, otherwise
// indirect the pointer back off.
if isPtr {
return out, nil
}
return out.Elem(), nil
}
var (
uint64Type = reflect.TypeOf(uint64(0))
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
)
// isIntType reports whether atype is an integer-shaped type.
func isIntType(atype reflect.Type) bool {
switch atype.Kind() {
case reflect.Float32, reflect.Float64:
return false
default:
return atype.ConvertibleTo(uint64Type)
}
}
// isStringOrBytes reports whether atype is a string or []byte.
func isStringOrBytes(atype reflect.Type) bool {
switch atype.Kind() {
case reflect.String:
return true
case reflect.Slice:
return atype.Elem().Kind() == reflect.Uint8
default:
return false
}
return json.Marshal(params)
}
// isQuotedString reports whether s is enclosed in double quotes.
@@ -177,19 +95,3 @@ func decodeInteger(s string) (int64, error) {
}
return strconv.ParseInt(s, 10, 64)
}
// decodeString decodes s into a byte slice. If s has an 0x prefix, it is
// treated as a hex-encoded string. If it is "double quoted" it is treated as a
// JSON string value. Otherwise, s is converted to bytes directly.
func decodeString(s string) ([]byte, error) {
if lc := strings.ToLower(s); strings.HasPrefix(lc, "0x") {
return hex.DecodeString(lc[2:])
} else if isQuotedString(s) {
var dec string
if err := json.Unmarshal([]byte(s), &dec); err != nil {
return nil, fmt.Errorf("invalid quoted string: %w", err)
}
return []byte(dec), nil
}
return []byte(s), nil
}

View File

@@ -3,7 +3,6 @@ package server
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strconv"
"testing"
@@ -134,8 +133,12 @@ func TestParseJSONArray(t *testing.T) {
}
func TestParseJSONRPC(t *testing.T) {
demo := func(ctx context.Context, height int, name string) error { return nil }
call := NewRPCFunc(demo, "height", "name")
type demoArgs struct {
Height int `json:"height,string"`
Name string `json:"name"`
}
demo := func(ctx context.Context, _ *demoArgs) error { return nil }
rfunc := NewRPCFunc(demo)
cases := []struct {
raw string
@@ -156,14 +159,16 @@ func TestParseJSONRPC(t *testing.T) {
ctx := context.Background()
for idx, tc := range cases {
i := strconv.Itoa(idx)
vals, err := parseParams(ctx, call, []byte(tc.raw))
vals, err := rfunc.parseParams(ctx, []byte(tc.raw))
if tc.fail {
assert.Error(t, err, i)
} else {
assert.NoError(t, err, "%s: %+v", i, err)
if assert.Equal(t, 3, len(vals), i) { // ctx, height, name
assert.Equal(t, tc.height, vals[1].Int(), i)
assert.Equal(t, tc.name, vals[2].String(), i)
assert.Equal(t, 2, len(vals), i)
p, ok := vals[1].Interface().(*demoArgs)
if assert.True(t, ok) {
assert.Equal(t, tc.height, int64(p.Height), i)
assert.Equal(t, tc.name, p.Name, i)
}
}
@@ -171,50 +176,147 @@ func TestParseJSONRPC(t *testing.T) {
}
func TestParseURI(t *testing.T) {
demo := func(ctx context.Context, height int, name string) error { return nil }
call := NewRPCFunc(demo, "height", "name")
// URI parameter parsing happens in two phases:
//
// Phase 1 swizzles the query parameters into JSON. The result of this
// phase must be valid JSON, but may fail the second stage.
//
// Phase 2 decodes the JSON to obtain the actual arguments. A failure at
// this stage means the JSON is not compatible with the target.
cases := []struct {
raw []string
height int64
name string
fail bool
}{
// can parse numbers unquoted and strings quoted
{[]string{"7", `"flew"`}, 7, "flew", false},
{[]string{"22", `"john"`}, 22, "john", false},
{[]string{"-10", `"bob"`}, -10, "bob", false},
// can parse numbers quoted, too
{[]string{`"7"`, `"flew"`}, 7, "flew", false},
{[]string{`"-10"`, `"bob"`}, -10, "bob", false},
// can parse strings hex-escaped, in either case
{[]string{`-9`, `0x626f62`}, -9, "bob", false},
{[]string{`-9`, `0X646F7567`}, -9, "doug", false},
// can parse strings unquoted (as per OpenAPI docs)
{[]string{`0`, `hey you`}, 0, "hey you", false},
// fail for invalid numbers, strings, hex
{[]string{`"-xx"`, `bob`}, 0, "", true}, // bad number
{[]string{`"95""`, `"bob`}, 0, "", true}, // bad string
{[]string{`15`, `0xa`}, 0, "", true}, // bad hex
}
for idx, tc := range cases {
i := strconv.Itoa(idx)
// data := []byte(tc.raw)
url := fmt.Sprintf(
"test.com/method?height=%v&name=%v",
tc.raw[0], tc.raw[1])
req, err := http.NewRequest("GET", url, nil)
assert.NoError(t, err)
vals, err := parseURLParams(context.Background(), call, req)
if tc.fail {
assert.Error(t, err, i)
} else {
assert.NoError(t, err, "%s: %+v", i, err)
if assert.Equal(t, 3, len(vals), i) {
assert.Equal(t, tc.height, vals[1].Int(), i)
assert.Equal(t, tc.name, vals[2].String(), i)
}
t.Run("Swizzle", func(t *testing.T) {
tests := []struct {
name string
url string
args []string
want string
fail bool
}{
{
name: "quoted numbers and strings",
url: `http://localhost?num="7"&str="flew"&neg="-10"`,
args: []string{"neg", "num", "str", "other"},
want: `{"neg":-10,"num":7,"str":"flew"}`,
},
{
name: "unquoted numbers and strings",
url: `http://localhost?num1=7&str1=cabbage&num2=-199&str2=hey+you`,
args: []string{"num1", "num2", "str1", "str2", "other"},
want: `{"num1":7,"num2":-199,"str1":"cabbage","str2":"hey you"}`,
},
{
name: "byte strings in hex",
url: `http://localhost?lower=0x626f62&upper=0X646F7567`,
args: []string{"upper", "lower", "other"},
want: `{"lower":"Ym9i","upper":"ZG91Zw=="}`,
},
{
name: "invalid hex odd length",
url: `http://localhost?bad=0xa`,
args: []string{"bad", "superbad"},
fail: true,
},
{
name: "invalid hex empty",
url: `http://localhost?bad=0x`,
args: []string{"bad"},
fail: true,
},
{
name: "invalid quoted string",
url: `http://localhost?bad="double""`,
args: []string{"bad"},
fail: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
hreq, err := http.NewRequest("GET", test.url, nil)
if err != nil {
t.Fatalf("NewRequest for %q: %v", test.url, err)
}
bits, err := parseURLParams(test.args, hreq)
if err != nil && !test.fail {
t.Fatalf("Parse %q: unexpected error: %v", test.url, err)
} else if err == nil && test.fail {
t.Fatalf("Parse %q: got %#q, wanted error", test.url, string(bits))
}
if got := string(bits); got != test.want {
t.Errorf("Parse %q: got %#q, want %#q", test.url, got, test.want)
}
})
}
})
t.Run("Decode", func(t *testing.T) {
type argValue struct {
Height json.Number `json:"height"`
Name string `json:"name"`
Flag bool `json:"flag"`
}
}
echo := NewRPCFunc(func(_ context.Context, arg *argValue) (*argValue, error) {
return arg, nil
})
tests := []struct {
name string
url string
fail string
want interface{}
}{
{
name: "valid all args",
url: `http://localhost?height=235&flag=true&name="bogart"`,
want: &argValue{
Height: "235",
Flag: true,
Name: "bogart",
},
},
{
name: "valid partial args",
url: `http://localhost?height="1987"&name=free+willy`,
want: &argValue{
Height: "1987",
Name: "free willy",
},
},
{
name: "invalid quoted number",
url: `http://localhost?height="-xx"`,
fail: "invalid number literal",
},
{
name: "invalid unquoted number",
url: `http://localhost?height=25*q`,
fail: "invalid number literal",
},
{
name: "invalid boolean",
url: `http://localhost?flag="garbage"`,
fail: "flag of type bool",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
hreq, err := http.NewRequest("GET", test.url, nil)
if err != nil {
t.Fatalf("NewRequest for %q: %v", test.url, err)
}
bits, err := parseURLParams(echo.argNames, hreq)
if err != nil {
t.Fatalf("Parse %#q: unexpected error: %v", test.url, err)
}
rsp, err := echo.Call(context.Background(), bits)
if test.want != nil {
assert.Equal(t, test.want, rsp)
}
if test.fail != "" {
assert.ErrorContains(t, err, test.fail)
}
})
}
})
}

View File

@@ -1,13 +1,17 @@
package server
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"strings"
"github.com/tendermint/tendermint/libs/log"
rpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
)
// RegisterRPCFuncs adds a route to mux for each non-websocket function in the
@@ -28,27 +32,97 @@ func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger lo
// RPCFunc contains the introspected type information for a function.
type RPCFunc struct {
f reflect.Value // underlying rpc function
args []reflect.Type // type of each function arg
returns []reflect.Type // type of each return arg
argNames []string // name of each argument
ws bool // websocket only
f reflect.Value // underlying rpc function
param reflect.Type // the parameter struct, or nil
result reflect.Type // the non-error result type, or nil
argNames []string // name of each argument (for display)
ws bool // websocket only
}
// Call parses the given JSON parameters and calls the function wrapped by rf
// with the resulting argument value. It reports an error if parameter parsing
// fails, otherwise it returns the result from the wrapped function.
func (rf *RPCFunc) Call(ctx context.Context, params json.RawMessage) (interface{}, error) {
args, err := rf.parseParams(ctx, params)
if err != nil {
return nil, err
}
returns := rf.f.Call(args)
// Case 1: There is no non-error result type.
if rf.result == nil {
if oerr := returns[0].Interface(); oerr != nil {
return nil, oerr.(error)
}
return nil, nil
}
// Case 2: There is a non-error result.
if oerr := returns[1].Interface(); oerr != nil {
// In case of error, report the error and ignore the result.
return nil, oerr.(error)
}
return returns[0].Interface(), nil
}
// parseParams parses the parameters of a JSON-RPC request and returns the
// corresponding argument values. On success, the first argument value will be
// the value of ctx.
func (rf *RPCFunc) parseParams(ctx context.Context, params json.RawMessage) ([]reflect.Value, error) {
// If rf does not accept parameters, there is no decoding to do, but verify
// that no parameters were passed.
if rf.param == nil {
if !isNullOrEmpty(params) {
return nil, invalidParamsError("no parameters accepted for this method")
}
return []reflect.Value{reflect.ValueOf(ctx)}, nil
}
bits, err := rf.adjustParams(params)
if err != nil {
return nil, invalidParamsError(err.Error())
}
arg := reflect.New(rf.param)
if err := json.Unmarshal(bits, arg.Interface()); err != nil {
return nil, invalidParamsError(err.Error())
}
return []reflect.Value{reflect.ValueOf(ctx), arg}, nil
}
// adjustParams checks whether data is encoded as a JSON array, and if so
// adjusts the values to match the corresponding parameter names.
func (rf *RPCFunc) adjustParams(data []byte) (json.RawMessage, error) {
base := bytes.TrimSpace(data)
if bytes.HasPrefix(base, []byte("[")) {
var args []json.RawMessage
if err := json.Unmarshal(base, &args); err != nil {
return nil, err
} else if len(args) != len(rf.argNames) {
return nil, fmt.Errorf("got %d arguments, want %d", len(args), len(rf.argNames))
}
m := make(map[string]json.RawMessage)
for i, arg := range args {
m[rf.argNames[i]] = arg
}
return json.Marshal(m)
} else if bytes.HasPrefix(base, []byte("{")) || bytes.Equal(base, []byte("null")) {
return base, nil
}
return nil, errors.New("parameters must be an object or an array")
}
// NewRPCFunc constructs an RPCFunc for f, which must be a function whose type
// signature matches one of these schemes:
//
// func(context.Context, T1, T2, ...) error
// func(context.Context, T1, T2, ...) (R, error)
// func(context.Context) error
// func(context.Context) (R, error)
// func(context.Context, *T) error
// func(context.Context, *T) (R, error)
//
// for arbitrary types T_i and R. The number of argNames must exactly match the
// number of non-context arguments to f. Otherwise, NewRPCFunc panics.
//
// The parameter names given are used to map JSON object keys to the
// corresonding parameter of the function. The names do not need to match the
// declared names, but must match what the client sends in a request.
func NewRPCFunc(f interface{}, argNames ...string) *RPCFunc {
rf, err := newRPCFunc(f, argNames)
// for an arbitrary struct type T and type R. NewRPCFunc will panic if f does
// not have one of these forms.
func NewRPCFunc(f interface{}) *RPCFunc {
rf, err := newRPCFunc(f)
if err != nil {
panic("invalid RPC function: " + err.Error())
}
@@ -57,8 +131,8 @@ func NewRPCFunc(f interface{}, argNames ...string) *RPCFunc {
// NewWSRPCFunc behaves as NewRPCFunc, but marks the resulting function for use
// via websocket.
func NewWSRPCFunc(f interface{}, argNames ...string) *RPCFunc {
rf := NewRPCFunc(f, argNames...)
func NewWSRPCFunc(f interface{}) *RPCFunc {
rf := NewRPCFunc(f)
rf.ws = true
return rf
}
@@ -69,7 +143,7 @@ var (
)
// newRPCFunc constructs an RPCFunc for f. See the comment at NewRPCFunc.
func newRPCFunc(f interface{}, argNames []string) (*RPCFunc, error) {
func newRPCFunc(f interface{}) (*RPCFunc, error) {
if f == nil {
return nil, errors.New("nil function")
}
@@ -80,49 +154,74 @@ func newRPCFunc(f interface{}, argNames []string) (*RPCFunc, error) {
return nil, errors.New("not a function")
}
var ptype reflect.Type
ft := fv.Type()
if np := ft.NumIn(); np == 0 {
if np := ft.NumIn(); np == 0 || np > 2 {
return nil, errors.New("wrong number of parameters")
} else if ft.In(0) != ctxType {
return nil, errors.New("first parameter is not context.Context")
} else if np-1 != len(argNames) {
return nil, fmt.Errorf("have %d names for %d parameters", len(argNames), np-1)
} else if np == 2 {
ptype = ft.In(1)
if ptype.Kind() != reflect.Ptr {
return nil, errors.New("parameter type is not a pointer")
}
ptype = ptype.Elem()
if ptype.Kind() != reflect.Struct {
return nil, errors.New("parameter type is not a struct")
}
}
var rtype reflect.Type
if no := ft.NumOut(); no < 1 || no > 2 {
return nil, errors.New("wrong number of results")
} else if ft.Out(no-1) != errType {
return nil, errors.New("last result is not error")
} else if no == 2 {
rtype = ft.Out(0)
}
args := make([]reflect.Type, ft.NumIn())
for i := 0; i < ft.NumIn(); i++ {
args[i] = ft.In(i)
}
outs := make([]reflect.Type, ft.NumOut())
for i := 0; i < ft.NumOut(); i++ {
outs[i] = ft.Out(i)
var argNames []string
if ptype != nil {
for i := 0; i < ptype.NumField(); i++ {
field := ptype.Field(i)
if tag := strings.SplitN(field.Tag.Get("json"), ",", 2)[0]; tag != "" && tag != "-" {
argNames = append(argNames, tag)
} else if tag == "-" {
// If the tag is "-" the field should explicitly be ignored, even
// if it is otherwise eligible.
} else if field.IsExported() && !field.Anonymous {
// Examples: Name → name, MaxEffort → maxEffort.
// Note that this is an aesthetic choice; the standard decoder will
// match without regard to case anyway.
name := strings.ToLower(field.Name[:1]) + field.Name[1:]
argNames = append(argNames, name)
}
}
}
return &RPCFunc{
f: fv,
args: args,
returns: outs,
param: ptype,
result: rtype,
argNames: argNames,
}, nil
}
//-------------------------------------------------------------
// NOTE: assume returns is result struct and error. If error is not nil, return it
func unreflectResult(returns []reflect.Value) (interface{}, error) {
errV := returns[1]
if err, ok := errV.Interface().(error); ok && err != nil {
return nil, err
// invalidParamsError returns an RPC invalid parameters error with the given
// detail message.
func invalidParamsError(msg string, args ...interface{}) error {
return &rpctypes.RPCError{
Code: int(rpctypes.CodeInvalidParams),
Message: rpctypes.CodeInvalidParams.String(),
Data: fmt.Sprintf(msg, args...),
}
rv := returns[0]
// the result is a registered interface,
// we need a pointer to it so we can marshal with type byte
rvp := reflect.New(rv.Type())
rvp.Elem().Set(rv)
return rvp.Interface(), nil
}
// isNullOrEmpty reports whether params is either itself empty or represents an
// empty parameter (null, empty object, or empty array).
func isNullOrEmpty(params json.RawMessage) bool {
return len(params) == 0 ||
bytes.Equal(params, []byte("null")) ||
bytes.Equal(params, []byte("{}")) ||
bytes.Equal(params, []byte("[]"))
}

View File

@@ -331,22 +331,8 @@ func (wsc *wsConnection) readRoutine(ctx context.Context) {
RPCRequest: &request,
WSConn: wsc,
})
args, err := parseParams(fctx, rpcFunc, request.Params)
if err != nil {
if err := wsc.WriteRPCResponse(writeCtx, request.MakeErrorf(rpctypes.CodeInvalidParams,
"converting JSON parameters: %v", err)); err != nil {
wsc.Logger.Error("error writing RPC response", "err", err)
}
continue
}
returns := rpcFunc.f.Call(args)
// TODO: Need to encode args/returns to string if we want to log them
wsc.Logger.Info("WSJSONRPC", "method", request.Method)
var resp rpctypes.RPCResponse
result, err := unreflectResult(returns)
result, err := rpcFunc.Call(fctx, request.Params)
if err == nil {
resp = request.MakeResponse(result)
} else {

View File

@@ -2,6 +2,7 @@ package server
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
@@ -44,8 +45,12 @@ func TestWebsocketManagerHandler(t *testing.T) {
}
func newWSServer(t *testing.T, logger log.Logger) *httptest.Server {
type args struct {
S string `json:"s"`
I json.Number `json:"i"`
}
funcMap := map[string]*RPCFunc{
"c": NewWSRPCFunc(func(ctx context.Context, s string, i int) (string, error) { return "foo", nil }, "s", "i"),
"c": NewWSRPCFunc(func(context.Context, *args) (string, error) { return "foo", nil }),
}
wm := NewWebsocketManager(logger, funcMap)

View File

@@ -14,7 +14,7 @@ import (
)
var routes = map[string]*rpcserver.RPCFunc{
"hello_world": rpcserver.NewRPCFunc(HelloWorld, "name", "num"),
"hello_world": rpcserver.NewRPCFunc(HelloWorld),
}
func HelloWorld(ctx context.Context, name string, num int) (Result, error) {

View File

@@ -17,10 +17,14 @@ import (
)
func FuzzRPCJSONRPCServer(f *testing.F) {
type args struct {
S string `json:"s"`
I int `json:"i"`
}
var rpcFuncMap = map[string]*rpcserver.RPCFunc{
"c": rpcserver.NewRPCFunc(func(ctx context.Context, s string, i int) (string, error) {
"c": rpcserver.NewRPCFunc(func(context.Context, *args) (string, error) {
return "foo", nil
}, "s", "i"),
}),
}
mux := http.NewServeMux()