Merge remote-tracking branch 'origin' into jasmina/4457-blocksync-verification_part1

This commit is contained in:
Jasmina Malicevic
2022-04-28 16:41:07 +02:00
47 changed files with 1191 additions and 1022 deletions

View File

@@ -7,6 +7,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: gaurav-nelson/github-action-markdown-link-check@1.0.14
- uses: creachadair/github-action-markdown-link-check@master
with:
folder-path: "docs"

4
go.mod
View File

@@ -41,8 +41,8 @@ require (
github.com/creachadair/atomicfile v0.2.5
github.com/creachadair/taskgroup v0.3.2
github.com/golangci/golangci-lint v1.45.2
github.com/google/go-cmp v0.5.7
github.com/vektra/mockery/v2 v2.12.0
github.com/google/go-cmp v0.5.8
github.com/vektra/mockery/v2 v2.12.1
gotest.tools v2.2.0+incompatible
)

7
go.sum
View File

@@ -449,8 +449,9 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -1056,8 +1057,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vektra/mockery/v2 v2.12.0 h1:g3lq1ni5swlT9y6jYBx/CITVpwg3vsWrhKIuIweAxYI=
github.com/vektra/mockery/v2 v2.12.0/go.mod h1:8vf4KDDUptfkyypzdHLuE7OE2xA7Gdt60WgIS8PgD+U=
github.com/vektra/mockery/v2 v2.12.1 h1:BAJk2fGjVg/P9Fi+BxZD1/ZeKTOclpeAb/SKCc12zXc=
github.com/vektra/mockery/v2 v2.12.1/go.mod h1:8vf4KDDUptfkyypzdHLuE7OE2xA7Gdt60WgIS8PgD+U=
github.com/viki-org/dnscache v0.0.0-20130720023526-c70c1f23c5d8/go.mod h1:dniwbG03GafCjFohMDmz6Zc6oCuiqgH6tGNyXTkHzXE=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=

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

@@ -97,7 +97,7 @@ func ParseNodeAddress(urlString string) (NodeAddress, error) {
// Resolve resolves a NodeAddress into a set of Endpoints, by expanding
// out a DNS hostname to IP addresses.
func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) {
func (a NodeAddress) Resolve(ctx context.Context) ([]*Endpoint, error) {
if a.Protocol == "" {
return nil, errors.New("address has no protocol")
}
@@ -109,7 +109,7 @@ func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) {
if a.NodeID == "" {
return nil, errors.New("local address has no node ID")
}
return []Endpoint{{
return []*Endpoint{{
Protocol: a.Protocol,
Path: string(a.NodeID),
}}, nil
@@ -119,9 +119,9 @@ func (a NodeAddress) Resolve(ctx context.Context) ([]Endpoint, error) {
if err != nil {
return nil, err
}
endpoints := make([]Endpoint, len(ips))
endpoints := make([]*Endpoint, len(ips))
for i, ip := range ips {
endpoints[i] = Endpoint{
endpoints[i] = &Endpoint{
Protocol: a.Protocol,
IP: ip,
Port: a.Port,

View File

@@ -210,71 +210,71 @@ func TestNodeAddress_Resolve(t *testing.T) {
testcases := []struct {
address p2p.NodeAddress
expect p2p.Endpoint
expect *p2p.Endpoint
ok bool
}{
// Valid networked addresses (with hostname).
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1", Port: 80, Path: "/path"},
p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"},
&p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"},
true,
},
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"},
p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"},
&p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1), Port: 80, Path: "/path"},
true,
},
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "localhost", Port: 80, Path: "/path"},
p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback, Port: 80, Path: "/path"},
&p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback, Port: 80, Path: "/path"},
true,
},
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1"},
p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1)},
&p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(127, 0, 0, 1)},
true,
},
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "::1"},
p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback},
&p2p.Endpoint{Protocol: "tcp", IP: net.IPv6loopback},
true,
},
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "8.8.8.8"},
p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(8, 8, 8, 8)},
&p2p.Endpoint{Protocol: "tcp", IP: net.IPv4(8, 8, 8, 8)},
true,
},
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "2001:0db8::ff00:0042:8329"},
p2p.Endpoint{Protocol: "tcp", IP: []byte{
&p2p.Endpoint{Protocol: "tcp", IP: []byte{
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x42, 0x83, 0x29}},
true,
},
{
p2p.NodeAddress{Protocol: "tcp", Hostname: "some.missing.host.tendermint.com"},
p2p.Endpoint{},
&p2p.Endpoint{},
false,
},
// Valid non-networked addresses.
{
p2p.NodeAddress{Protocol: "memory", NodeID: id},
p2p.Endpoint{Protocol: "memory", Path: string(id)},
&p2p.Endpoint{Protocol: "memory", Path: string(id)},
true,
},
{
p2p.NodeAddress{Protocol: "memory", NodeID: id, Path: string(id)},
p2p.Endpoint{Protocol: "memory", Path: string(id)},
&p2p.Endpoint{Protocol: "memory", Path: string(id)},
true,
},
// Invalid addresses.
{p2p.NodeAddress{}, p2p.Endpoint{}, false},
{p2p.NodeAddress{Hostname: "127.0.0.1"}, p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1:80"}, p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "memory"}, p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "memory", Path: string(id)}, p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "tcp", Hostname: "💥"}, p2p.Endpoint{}, false},
{p2p.NodeAddress{}, &p2p.Endpoint{}, false},
{p2p.NodeAddress{Hostname: "127.0.0.1"}, &p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "tcp", Hostname: "127.0.0.1:80"}, &p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "memory"}, &p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "memory", Path: string(id)}, &p2p.Endpoint{}, false},
{p2p.NodeAddress{Protocol: "tcp", Hostname: "💥"}, &p2p.Endpoint{}, false},
}
for _, tc := range testcases {
tc := tc

View File

@@ -62,11 +62,11 @@ func (_m *Transport) Close() error {
}
// Dial provides a mock function with given fields: _a0, _a1
func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection, error) {
func (_m *Transport) Dial(_a0 context.Context, _a1 *p2p.Endpoint) (p2p.Connection, error) {
ret := _m.Called(_a0, _a1)
var r0 p2p.Connection
if rf, ok := ret.Get(0).(func(context.Context, p2p.Endpoint) p2p.Connection); ok {
if rf, ok := ret.Get(0).(func(context.Context, *p2p.Endpoint) p2p.Connection); ok {
r0 = rf(_a0, _a1)
} else {
if ret.Get(0) != nil {
@@ -75,7 +75,7 @@ func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, p2p.Endpoint) error); ok {
if rf, ok := ret.Get(1).(func(context.Context, *p2p.Endpoint) error); ok {
r1 = rf(_a0, _a1)
} else {
r1 = ret.Error(1)
@@ -84,28 +84,35 @@ func (_m *Transport) Dial(_a0 context.Context, _a1 p2p.Endpoint) (p2p.Connection
return r0, r1
}
// Endpoints provides a mock function with given fields:
func (_m *Transport) Endpoints() []p2p.Endpoint {
// Endpoint provides a mock function with given fields:
func (_m *Transport) Endpoint() (*p2p.Endpoint, error) {
ret := _m.Called()
var r0 []p2p.Endpoint
if rf, ok := ret.Get(0).(func() []p2p.Endpoint); ok {
var r0 *p2p.Endpoint
if rf, ok := ret.Get(0).(func() *p2p.Endpoint); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]p2p.Endpoint)
r0 = ret.Get(0).(*p2p.Endpoint)
}
}
return r0
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Listen provides a mock function with given fields: _a0
func (_m *Transport) Listen(_a0 p2p.Endpoint) error {
func (_m *Transport) Listen(_a0 *p2p.Endpoint) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(p2p.Endpoint) error); ok {
if rf, ok := ret.Get(0).(func(*p2p.Endpoint) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)

View File

@@ -247,7 +247,9 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions)
}
transport := n.memoryNetwork.CreateTransport(nodeID)
require.Len(t, transport.Endpoints(), 1, "transport not listening on 1 endpoint")
ep, err := transport.Endpoint()
require.NoError(t, err)
require.NotNil(t, ep, "transport not listening an endpoint")
peerManager, err := p2p.NewPeerManager(nodeID, dbm.NewMemDB(), p2p.PeerManagerOptions{
MinRetryTime: 10 * time.Millisecond,
@@ -264,8 +266,8 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions)
privKey,
peerManager,
func() *types.NodeInfo { return &nodeInfo },
[]p2p.Transport{transport},
transport.Endpoints(),
transport,
ep,
p2p.RouterOptions{DialSleep: func(_ context.Context) {}},
)
@@ -284,7 +286,7 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, opts NodeOptions)
return &Node{
NodeID: nodeID,
NodeInfo: nodeInfo,
NodeAddress: transport.Endpoints()[0].NodeAddress(nodeID),
NodeAddress: ep.NodeAddress(nodeID),
PrivKey: privKey,
Router: router,
PeerManager: peerManager,
@@ -304,7 +306,6 @@ func (n *Node) MakeChannel(
ctx, cancel := context.WithCancel(ctx)
channel, err := n.Router.OpenChannel(ctx, chDesc)
require.NoError(t, err)
require.Contains(t, n.Router.NodeInfo().Channels, byte(chDesc.ID))
t.Cleanup(func() {
RequireEmpty(ctx, t, channel)
cancel()

View File

@@ -148,15 +148,14 @@ type Router struct {
*service.BaseService
logger log.Logger
metrics *Metrics
options RouterOptions
privKey crypto.PrivKey
peerManager *PeerManager
chDescs []*ChannelDescriptor
transports []Transport
endpoints []Endpoint
connTracker connectionTracker
protocolTransports map[Protocol]Transport
metrics *Metrics
options RouterOptions
privKey crypto.PrivKey
peerManager *PeerManager
chDescs []*ChannelDescriptor
transport Transport
endpoint *Endpoint
connTracker connectionTracker
peerMtx sync.RWMutex
peerQueues map[types.NodeID]queue // outbound messages per peer for all channels
@@ -182,8 +181,8 @@ func NewRouter(
privKey crypto.PrivKey,
peerManager *PeerManager,
nodeInfoProducer func() *types.NodeInfo,
transports []Transport,
endpoints []Endpoint,
transport Transport,
endpoint *Endpoint,
options RouterOptions,
) (*Router, error) {
@@ -200,28 +199,19 @@ func NewRouter(
options.MaxIncomingConnectionAttempts,
options.IncomingConnectionWindow,
),
chDescs: make([]*ChannelDescriptor, 0),
transports: transports,
endpoints: endpoints,
protocolTransports: map[Protocol]Transport{},
peerManager: peerManager,
options: options,
channelQueues: map[ChannelID]queue{},
channelMessages: map[ChannelID]proto.Message{},
peerQueues: map[types.NodeID]queue{},
peerChannels: make(map[types.NodeID]ChannelIDSet),
chDescs: make([]*ChannelDescriptor, 0),
transport: transport,
endpoint: endpoint,
peerManager: peerManager,
options: options,
channelQueues: map[ChannelID]queue{},
channelMessages: map[ChannelID]proto.Message{},
peerQueues: map[types.NodeID]queue{},
peerChannels: make(map[types.NodeID]ChannelIDSet),
}
router.BaseService = service.NewBaseService(logger, "router", router)
for _, transport := range transports {
for _, protocol := range transport.Protocols() {
if _, ok := router.protocolTransports[protocol]; !ok {
router.protocolTransports[protocol] = transport
}
}
}
return router, nil
}
@@ -286,9 +276,7 @@ func (r *Router) OpenChannel(ctx context.Context, chDesc *ChannelDescriptor) (*C
// add the channel to the nodeInfo if it's not already there.
r.nodeInfoProducer().AddChannel(uint16(chDesc.ID))
for _, t := range r.transports {
t.AddChannelDescriptors([]*ChannelDescriptor{chDesc})
}
r.transport.AddChannelDescriptors([]*ChannelDescriptor{chDesc})
go func() {
defer func() {
@@ -670,12 +658,6 @@ func (r *Router) dialPeer(ctx context.Context, address NodeAddress) (Connection,
}
for _, endpoint := range endpoints {
transport, ok := r.protocolTransports[endpoint.Protocol]
if !ok {
r.logger.Error("no transport found for protocol", "endpoint", endpoint)
continue
}
dialCtx := ctx
if r.options.DialTimeout > 0 {
var cancel context.CancelFunc
@@ -690,7 +672,7 @@ func (r *Router) dialPeer(ctx context.Context, address NodeAddress) (Connection,
// by the peer's endpoint, since e.g. a peer on 192.168.0.0 can reach us
// on a private address on this endpoint, but a peer on the public
// Internet can't and needs a different public address.
conn, err := transport.Dial(dialCtx, endpoint)
conn, err := r.transport.Dial(dialCtx, endpoint)
if err != nil {
r.logger.Error("failed to dial endpoint", "peer", address.NodeID, "endpoint", endpoint, "err", err)
} else {
@@ -731,7 +713,7 @@ func (r *Router) handshakePeer(
return peerInfo, fmt.Errorf("expected to connect with peer %q, got %q",
expectID, peerInfo.NodeID)
}
if err := r.nodeInfoProducer().CompatibleWith(peerInfo); err != nil {
if err := nodeInfo.CompatibleWith(peerInfo); err != nil {
return peerInfo, ErrRejected{
err: err,
id: peerInfo.ID(),
@@ -929,11 +911,6 @@ func (r *Router) evictPeers(ctx context.Context) {
}
}
// NodeInfo returns a copy of the current NodeInfo. Used for testing.
func (r *Router) NodeInfo() types.NodeInfo {
return r.nodeInfoProducer().Copy()
}
func (r *Router) setupQueueFactory(ctx context.Context) error {
qf, err := r.createQueueFactory(ctx)
if err != nil {
@@ -950,29 +927,13 @@ func (r *Router) OnStart(ctx context.Context) error {
return err
}
for _, transport := range r.transports {
for _, endpoint := range r.endpoints {
if err := transport.Listen(endpoint); err != nil {
return err
}
}
if err := r.transport.Listen(r.endpoint); err != nil {
return err
}
nodeInfo := r.nodeInfoProducer()
r.logger.Info(
"starting router",
"node_id", nodeInfo.NodeID,
"channels", nodeInfo.Channels,
"listen_addr", nodeInfo.ListenAddr,
"transports", len(r.transports),
)
go r.dialPeers(ctx)
go r.evictPeers(ctx)
for _, transport := range r.transports {
go r.acceptPeers(ctx, transport)
}
go r.acceptPeers(ctx, r.transport)
return nil
}
@@ -985,10 +946,8 @@ func (r *Router) OnStart(ctx context.Context) error {
// sender's responsibility.
func (r *Router) OnStop() {
// Close transport listeners (unblocks Accept calls).
for _, transport := range r.transports {
if err := transport.Close(); err != nil {
r.logger.Error("failed to close transport", "transport", transport, "err", err)
}
if err := r.transport.Close(); err != nil {
r.logger.Error("failed to close transport", "err", err)
}
// Collect all remaining queues, and wait for them to close.

View File

@@ -105,14 +105,16 @@ func TestRouter_Channel_Basic(t *testing.T) {
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
require.NoError(t, err)
testnet := p2ptest.MakeNetwork(ctx, t, p2ptest.NetworkOptions{NumNodes: 1})
router, err := p2p.NewRouter(
log.NewNopLogger(),
p2p.NopMetrics(),
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
nil,
nil,
testnet.RandomNode().Transport,
&p2p.Endpoint{},
p2p.RouterOptions{},
)
require.NoError(t, err)
@@ -126,7 +128,6 @@ func TestRouter_Channel_Basic(t *testing.T) {
channel, err := router.OpenChannel(chctx, chDesc)
require.NoError(t, err)
require.Contains(t, router.NodeInfo().Channels, byte(chDesc.ID))
require.NotNil(t, channel)
// Opening the same channel again should fail.
@@ -136,9 +137,7 @@ func TestRouter_Channel_Basic(t *testing.T) {
// Opening a different channel should work.
chDesc2 := &p2p.ChannelDescriptor{ID: 2, MessageType: &p2ptest.Message{}}
_, err = router.OpenChannel(ctx, chDesc2)
require.NoError(t, err)
require.Contains(t, router.NodeInfo().Channels, byte(chDesc2.ID))
// Closing the channel, then opening it again should be fine.
chcancel()
@@ -396,10 +395,10 @@ func TestRouter_AcceptPeers(t *testing.T) {
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Close").Return(nil).Maybe()
mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil)
mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF)
mockTransport.On("Listen", mock.Anything).Return(nil)
// Set up and start the router.
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
@@ -413,7 +412,7 @@ func TestRouter_AcceptPeers(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)
@@ -453,9 +452,9 @@ func TestRouter_AcceptPeers_Error(t *testing.T) {
// the router from calling Accept again.
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Accept", mock.Anything).Once().Return(nil, errors.New("boom"))
mockTransport.On("Close").Return(nil)
mockTransport.On("Listen", mock.Anything).Return(nil)
// Set up and start the router.
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
@@ -467,7 +466,7 @@ func TestRouter_AcceptPeers_Error(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)
@@ -490,9 +489,9 @@ func TestRouter_AcceptPeers_ErrorEOF(t *testing.T) {
// the router from calling Accept again.
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF)
mockTransport.On("Close").Return(nil)
mockTransport.On("Listen", mock.Anything).Return(nil)
// Set up and start the router.
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
@@ -504,7 +503,7 @@ func TestRouter_AcceptPeers_ErrorEOF(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)
@@ -538,12 +537,12 @@ func TestRouter_AcceptPeers_HeadOfLineBlocking(t *testing.T) {
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Close").Return(nil)
mockTransport.On("Accept", mock.Anything).Times(3).Run(func(_ mock.Arguments) {
acceptCh <- true
}).Return(mockConnection, nil)
mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF)
mockTransport.On("Listen", mock.Anything).Return(nil)
// Set up and start the router.
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
@@ -555,7 +554,7 @@ func TestRouter_AcceptPeers_HeadOfLineBlocking(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)
@@ -611,7 +610,7 @@ func TestRouter_DialPeers(t *testing.T) {
defer cancel()
address := p2p.NodeAddress{Protocol: "mock", NodeID: tc.dialID}
endpoint := p2p.Endpoint{Protocol: "mock", Path: string(tc.dialID)}
endpoint := &p2p.Endpoint{Protocol: "mock", Path: string(tc.dialID)}
// Set up a mock transport that handshakes.
connCtx, connCancel := context.WithCancel(context.Background())
@@ -629,8 +628,8 @@ func TestRouter_DialPeers(t *testing.T) {
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Close").Return(nil).Maybe()
mockTransport.On("Listen", mock.Anything).Return(nil)
mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF)
if tc.dialErr == nil {
mockTransport.On("Dial", mock.Anything, endpoint).Once().Return(mockConnection, nil)
@@ -658,7 +657,7 @@ func TestRouter_DialPeers(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)
@@ -711,11 +710,11 @@ func TestRouter_DialPeers_Parallel(t *testing.T) {
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Close").Return(nil)
mockTransport.On("Listen", mock.Anything).Return(nil)
mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF)
for _, address := range []p2p.NodeAddress{a, b, c} {
endpoint := p2p.Endpoint{Protocol: address.Protocol, Path: string(address.NodeID)}
endpoint := &p2p.Endpoint{Protocol: address.Protocol, Path: string(address.NodeID)}
mockTransport.On("Dial", mock.Anything, endpoint).Run(func(_ mock.Arguments) {
dialCh <- true
}).Return(mockConnection, nil)
@@ -743,7 +742,7 @@ func TestRouter_DialPeers_Parallel(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{
DialSleep: func(_ context.Context) {},
@@ -800,10 +799,10 @@ func TestRouter_EvictPeers(t *testing.T) {
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Close").Return(nil)
mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil)
mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF)
mockTransport.On("Listen", mock.Anything).Return(nil)
// Set up and start the router.
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
@@ -817,7 +816,7 @@ func TestRouter_EvictPeers(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)
@@ -864,10 +863,10 @@ func TestRouter_ChannelCompatability(t *testing.T) {
mockTransport := &mocks.Transport{}
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Close").Return(nil)
mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil)
mockTransport.On("Accept", mock.Anything).Once().Return(nil, io.EOF)
mockTransport.On("Listen", mock.Anything).Return(nil)
// Set up and start the router.
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
@@ -879,7 +878,7 @@ func TestRouter_ChannelCompatability(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)
@@ -917,10 +916,10 @@ func TestRouter_DontSendOnInvalidChannel(t *testing.T) {
mockTransport := &mocks.Transport{}
mockTransport.On("AddChannelDescriptors", mock.Anything).Return()
mockTransport.On("String").Maybe().Return("mock")
mockTransport.On("Protocols").Return([]p2p.Protocol{"mock"})
mockTransport.On("Close").Return(nil)
mockTransport.On("Accept", mock.Anything).Once().Return(mockConnection, nil)
mockTransport.On("Accept", mock.Anything).Maybe().Return(nil, io.EOF)
mockTransport.On("Listen", mock.Anything).Return(nil)
// Set up and start the router.
peerManager, err := p2p.NewPeerManager(selfID, dbm.NewMemDB(), p2p.PeerManagerOptions{})
@@ -934,7 +933,7 @@ func TestRouter_DontSendOnInvalidChannel(t *testing.T) {
selfKey,
peerManager,
func() *types.NodeInfo { return &selfInfo },
[]p2p.Transport{mockTransport},
mockTransport,
nil,
p2p.RouterOptions{},
)

View File

@@ -24,7 +24,7 @@ type Protocol string
// Transport is a connection-oriented mechanism for exchanging data with a peer.
type Transport interface {
// Listen starts the transport on the specified endpoint.
Listen(Endpoint) error
Listen(*Endpoint) error
// Protocols returns the protocols supported by the transport. The Router
// uses this to pick a transport for an Endpoint.
@@ -34,7 +34,7 @@ type Transport interface {
//
// How to listen is transport-dependent, e.g. MConnTransport uses Listen() while
// MemoryTransport starts listening via MemoryNetwork.CreateTransport().
Endpoints() []Endpoint
Endpoint() (*Endpoint, error)
// Accept waits for the next inbound connection on a listening endpoint, blocking
// until either a connection is available or the transport is closed. On closure,
@@ -42,7 +42,7 @@ type Transport interface {
Accept(context.Context) (Connection, error)
// Dial creates an outbound connection to an endpoint.
Dial(context.Context, Endpoint) (Connection, error)
Dial(context.Context, *Endpoint) (Connection, error)
// Close stops accepting new connections, but does not close active connections.
Close() error
@@ -129,13 +129,13 @@ type Endpoint struct {
}
// NewEndpoint constructs an Endpoint from a types.NetAddress structure.
func NewEndpoint(addr string) (Endpoint, error) {
func NewEndpoint(addr string) (*Endpoint, error) {
ip, port, err := types.ParseAddressString(addr)
if err != nil {
return Endpoint{}, err
return nil, err
}
return Endpoint{
return &Endpoint{
Protocol: MConnProtocol,
IP: ip,
Port: port,

View File

@@ -78,25 +78,25 @@ func (m *MConnTransport) Protocols() []Protocol {
return []Protocol{MConnProtocol, TCPProtocol}
}
// Endpoints implements Transport.
func (m *MConnTransport) Endpoints() []Endpoint {
// Endpoint implements Transport.
func (m *MConnTransport) Endpoint() (*Endpoint, error) {
if m.listener == nil {
return []Endpoint{}
return nil, errors.New("listenter not defined")
}
select {
case <-m.doneCh:
return []Endpoint{}
return nil, errors.New("transport closed")
default:
}
endpoint := Endpoint{
endpoint := &Endpoint{
Protocol: MConnProtocol,
}
if addr, ok := m.listener.Addr().(*net.TCPAddr); ok {
endpoint.IP = addr.IP
endpoint.Port = uint16(addr.Port)
}
return []Endpoint{endpoint}
return endpoint, nil
}
// Listen asynchronously listens for inbound connections on the given endpoint.
@@ -106,7 +106,7 @@ func (m *MConnTransport) Endpoints() []Endpoint {
// FIXME: Listen currently only supports listening on a single endpoint, it
// might be useful to support listening on multiple addresses (e.g. IPv4 and
// IPv6, or a private and public address) via multiple Listen() calls.
func (m *MConnTransport) Listen(endpoint Endpoint) error {
func (m *MConnTransport) Listen(endpoint *Endpoint) error {
if m.listener != nil {
return errors.New("transport is already listening")
}
@@ -170,7 +170,7 @@ func (m *MConnTransport) Accept(ctx context.Context) (Connection, error) {
}
// Dial implements Transport.
func (m *MConnTransport) Dial(ctx context.Context, endpoint Endpoint) (Connection, error) {
func (m *MConnTransport) Dial(ctx context.Context, endpoint *Endpoint) (Connection, error) {
if err := m.validateEndpoint(endpoint); err != nil {
return nil, err
}
@@ -217,7 +217,7 @@ func (m *MConnTransport) AddChannelDescriptors(channelDesc []*ChannelDescriptor)
}
// validateEndpoint validates an endpoint.
func (m *MConnTransport) validateEndpoint(endpoint Endpoint) error {
func (m *MConnTransport) validateEndpoint(endpoint *Endpoint) error {
if err := endpoint.Validate(); err != nil {
return err
}

View File

@@ -25,7 +25,7 @@ func init() {
[]*p2p.ChannelDescriptor{{ID: chID, Priority: 1}},
p2p.MConnTransportOptions{},
)
err := transport.Listen(p2p.Endpoint{
err := transport.Listen(&p2p.Endpoint{
Protocol: p2p.MConnProtocol,
IP: net.IPv4(127, 0, 0, 1),
Port: 0, // assign a random port
@@ -73,13 +73,14 @@ func TestMConnTransport_AcceptMaxAcceptedConnections(t *testing.T) {
t.Cleanup(func() {
_ = transport.Close()
})
err := transport.Listen(p2p.Endpoint{
err := transport.Listen(&p2p.Endpoint{
Protocol: p2p.MConnProtocol,
IP: net.IPv4(127, 0, 0, 1),
})
require.NoError(t, err)
require.NotEmpty(t, transport.Endpoints())
endpoint := transport.Endpoints()[0]
endpoint, err := transport.Endpoint()
require.NoError(t, err)
require.NotNil(t, endpoint)
// Start a goroutine to just accept any connections.
acceptCh := make(chan p2p.Connection, 10)
@@ -132,20 +133,20 @@ func TestMConnTransport_Listen(t *testing.T) {
defer cancel()
testcases := []struct {
endpoint p2p.Endpoint
endpoint *p2p.Endpoint
ok bool
}{
// Valid v4 and v6 addresses, with mconn and tcp protocols.
{p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero}, true},
{p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1)}, true},
{p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6zero}, true},
{p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6loopback}, true},
{p2p.Endpoint{Protocol: p2p.TCPProtocol, IP: net.IPv4zero}, true},
{&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero}, true},
{&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4(127, 0, 0, 1)}, true},
{&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6zero}, true},
{&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv6loopback}, true},
{&p2p.Endpoint{Protocol: p2p.TCPProtocol, IP: net.IPv4zero}, true},
// Invalid endpoints.
{p2p.Endpoint{}, false},
{p2p.Endpoint{Protocol: p2p.MConnProtocol, Path: "foo"}, false},
{p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero, Path: "foo"}, false},
{&p2p.Endpoint{}, false},
{&p2p.Endpoint{Protocol: p2p.MConnProtocol, Path: "foo"}, false},
{&p2p.Endpoint{Protocol: p2p.MConnProtocol, IP: net.IPv4zero, Path: "foo"}, false},
}
for _, tc := range testcases {
tc := tc
@@ -160,10 +161,12 @@ func TestMConnTransport_Listen(t *testing.T) {
)
// Transport should not listen on any endpoints yet.
require.Empty(t, transport.Endpoints())
endpoint, err := transport.Endpoint()
require.Error(t, err)
require.Nil(t, endpoint)
// Start listening, and check any expected errors.
err := transport.Listen(tc.endpoint)
err = transport.Listen(tc.endpoint)
if !tc.ok {
require.Error(t, err)
return
@@ -171,9 +174,9 @@ func TestMConnTransport_Listen(t *testing.T) {
require.NoError(t, err)
// Check the endpoint.
endpoints := transport.Endpoints()
require.Len(t, endpoints, 1)
endpoint := endpoints[0]
endpoint, err = transport.Endpoint()
require.NoError(t, err)
require.NotNil(t, endpoint)
require.Equal(t, p2p.MConnProtocol, endpoint.Protocol)
if tc.endpoint.IP.IsUnspecified() {

View File

@@ -119,7 +119,7 @@ func (t *MemoryTransport) String() string {
return string(MemoryProtocol)
}
func (*MemoryTransport) Listen(Endpoint) error { return nil }
func (*MemoryTransport) Listen(*Endpoint) error { return nil }
func (t *MemoryTransport) AddChannelDescriptors([]*ChannelDescriptor) {}
@@ -129,19 +129,19 @@ func (t *MemoryTransport) Protocols() []Protocol {
}
// Endpoints implements Transport.
func (t *MemoryTransport) Endpoints() []Endpoint {
func (t *MemoryTransport) Endpoint() (*Endpoint, error) {
if n := t.network.GetTransport(t.nodeID); n == nil {
return []Endpoint{}
return nil, errors.New("node not defined")
}
return []Endpoint{{
return &Endpoint{
Protocol: MemoryProtocol,
Path: string(t.nodeID),
// An arbitrary IP and port is used in order for the pex
// reactor to be able to send addresses to one another.
IP: net.IPv4zero,
Port: 0,
}}
}, nil
}
// Accept implements Transport.
@@ -158,7 +158,7 @@ func (t *MemoryTransport) Accept(ctx context.Context) (Connection, error) {
}
// Dial implements Transport.
func (t *MemoryTransport) Dial(ctx context.Context, endpoint Endpoint) (Connection, error) {
func (t *MemoryTransport) Dial(ctx context.Context, endpoint *Endpoint) (Connection, error) {
if endpoint.Protocol != MemoryProtocol {
return nil, fmt.Errorf("invalid protocol %q", endpoint.Protocol)
}

View File

@@ -87,9 +87,9 @@ func TestTransport_DialEndpoints(t *testing.T) {
withTransports(ctx, t, func(ctx context.Context, t *testing.T, makeTransport transportFactory) {
a := makeTransport(t)
endpoints := a.Endpoints()
require.NotEmpty(t, endpoints)
endpoint := endpoints[0]
endpoint, err := a.Endpoint()
require.NoError(t, err)
require.NotNil(t, endpoint)
// Spawn a goroutine to simply accept any connections until closed.
go func() {
@@ -108,19 +108,19 @@ func TestTransport_DialEndpoints(t *testing.T) {
require.NoError(t, conn.Close())
// Dialing empty endpoint should error.
_, err = a.Dial(ctx, p2p.Endpoint{})
_, err = a.Dial(ctx, &p2p.Endpoint{})
require.Error(t, err)
// Dialing without protocol should error.
noProtocol := endpoint
noProtocol := *endpoint
noProtocol.Protocol = ""
_, err = a.Dial(ctx, noProtocol)
_, err = a.Dial(ctx, &noProtocol)
require.Error(t, err)
// Dialing with invalid protocol should error.
fooProtocol := endpoint
fooProtocol := *endpoint
fooProtocol.Protocol = "foo"
_, err = a.Dial(ctx, fooProtocol)
_, err = a.Dial(ctx, &fooProtocol)
require.Error(t, err)
// Tests for networked endpoints (with IP).
@@ -129,11 +129,12 @@ func TestTransport_DialEndpoints(t *testing.T) {
tc := tc
t.Run(tc.ip.String(), func(t *testing.T) {
e := endpoint
require.NotNil(t, e)
e.IP = tc.ip
conn, err := a.Dial(ctx, e)
if tc.ok {
require.NoError(t, conn.Close())
require.NoError(t, err)
require.NoError(t, conn.Close())
} else {
require.Error(t, err, "endpoint=%s", e)
}
@@ -167,16 +168,18 @@ func TestTransport_Dial(t *testing.T) {
a := makeTransport(t)
b := makeTransport(t)
require.NotEmpty(t, a.Endpoints())
require.NotEmpty(t, b.Endpoints())
aEndpoint := a.Endpoints()[0]
bEndpoint := b.Endpoints()[0]
aEndpoint, err := a.Endpoint()
require.NoError(t, err)
require.NotNil(t, aEndpoint)
bEndpoint, err := b.Endpoint()
require.NoError(t, err)
require.NotNil(t, bEndpoint)
// Context cancellation should error. We can't test timeouts since we'd
// need a non-responsive endpoint.
cancelCtx, cancel := context.WithCancel(ctx)
cancel()
_, err := a.Dial(cancelCtx, bEndpoint)
_, err = a.Dial(cancelCtx, bEndpoint)
require.Error(t, err)
// Unavailable endpoint should error.
@@ -210,21 +213,26 @@ func TestTransport_Endpoints(t *testing.T) {
b := makeTransport(t)
// Both transports return valid and different endpoints.
aEndpoints := a.Endpoints()
bEndpoints := b.Endpoints()
require.NotEmpty(t, aEndpoints)
require.NotEmpty(t, bEndpoints)
require.NotEqual(t, aEndpoints, bEndpoints)
for _, endpoint := range append(aEndpoints, bEndpoints...) {
aEndpoint, err := a.Endpoint()
require.NoError(t, err)
require.NotNil(t, aEndpoint)
bEndpoint, err := b.Endpoint()
require.NoError(t, err)
require.NotNil(t, bEndpoint)
require.NotEqual(t, aEndpoint, bEndpoint)
for _, endpoint := range []*p2p.Endpoint{aEndpoint, bEndpoint} {
err := endpoint.Validate()
require.NoError(t, err, "invalid endpoint %q", endpoint)
}
// When closed, the transport should no longer return any endpoints.
err := a.Close()
require.NoError(t, a.Close())
aEndpoint, err = a.Endpoint()
require.Error(t, err)
require.Nil(t, aEndpoint)
bEndpoint, err = b.Endpoint()
require.NoError(t, err)
require.Empty(t, a.Endpoints())
require.NotEmpty(t, b.Endpoints())
require.NotNil(t, bEndpoint)
})
}
@@ -235,13 +243,12 @@ func TestTransport_Protocols(t *testing.T) {
withTransports(ctx, t, func(ctx context.Context, t *testing.T, makeTransport transportFactory) {
a := makeTransport(t)
protocols := a.Protocols()
endpoints := a.Endpoints()
endpoint, err := a.Endpoint()
require.NoError(t, err)
require.NotEmpty(t, protocols)
require.NotEmpty(t, endpoints)
require.NotNil(t, endpoint)
for _, endpoint := range endpoints {
require.Contains(t, protocols, endpoint.Protocol)
}
require.Contains(t, protocols, endpoint.Protocol)
})
}
@@ -595,8 +602,9 @@ func TestEndpoint_Validate(t *testing.T) {
func dialAccept(ctx context.Context, t *testing.T, a, b p2p.Transport) (p2p.Connection, p2p.Connection) {
t.Helper()
endpoints := b.Endpoints()
require.NotEmpty(t, endpoints, "peer not listening on any endpoints")
endpoint, err := b.Endpoint()
require.NoError(t, err)
require.NotNil(t, endpoint, "peer not listening on any endpoints")
ctx, cancel := context.WithTimeout(ctx, time.Second)
defer cancel()
@@ -609,7 +617,7 @@ func dialAccept(ctx context.Context, t *testing.T, a, b p2p.Transport) (p2p.Conn
acceptCh <- conn
}()
dialConn, err := a.Dial(ctx, endpoints[0])
dialConn, err := a.Dial(ctx, endpoint)
require.NoError(t, err)
acceptConn := <-acceptCh

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

@@ -488,17 +488,6 @@ func (n *nodeImpl) OnStart(ctx context.Context) error {
return err
}
n.rpcEnv.NodeInfo = n.nodeInfo
// Start the RPC server before the P2P server
// so we can eg. receive txs for the first block
if n.config.RPC.ListenAddress != "" {
var err error
n.rpcListeners, err = n.rpcEnv.StartService(ctx, n.config)
if err != nil {
return err
}
}
if n.config.Instrumentation.Prometheus && n.config.Instrumentation.PrometheusListenAddr != "" {
n.prometheusSrv = n.startPrometheusServer(ctx, n.config.Instrumentation.PrometheusListenAddr)
}
@@ -515,12 +504,31 @@ func (n *nodeImpl) OnStart(ctx context.Context) error {
}
}
n.rpcEnv.NodeInfo = n.nodeInfo
// Start the RPC server before the P2P server
// so we can eg. receive txs for the first block
if n.config.RPC.ListenAddress != "" {
var err error
n.rpcListeners, err = n.rpcEnv.StartService(ctx, n.config)
if err != nil {
return err
}
}
return nil
}
// OnStop stops the Node. It implements service.Service.
func (n *nodeImpl) OnStop() {
n.logger.Info("Stopping Node")
// stop the listeners / external services first
for _, l := range n.rpcListeners {
n.logger.Info("Closing rpc listener", "listener", l)
if err := l.Close(); err != nil {
n.logger.Error("error closing listener", "listener", l, "err", err)
}
}
for _, es := range n.eventSinks {
if err := es.Stop(); err != nil {
n.logger.Error("failed to stop event sink", "err", err)
@@ -534,14 +542,6 @@ func (n *nodeImpl) OnStop() {
n.router.Wait()
n.rpcEnv.IsListening = false
// finally stop the listeners / external services
for _, l := range n.rpcListeners {
n.logger.Info("Closing rpc listener", "listener", l)
if err := l.Close(); err != nil {
n.logger.Error("error closing listener", "listener", l, "err", err)
}
}
if pvsc, ok := n.privValidator.(service.Service); ok {
pvsc.Wait()
}

View File

@@ -310,8 +310,8 @@ func createRouter(
nodeKey.PrivKey,
peerManager,
nodeInfoProducer,
[]p2p.Transport{transport},
[]p2p.Endpoint{ep},
transport,
ep,
getRouterConfig(cfg, appClient),
)
}

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

@@ -13,10 +13,7 @@ import (
"github.com/tendermint/tendermint/libs/log"
)
var mp *mempool.TxMempool
var getMp func() mempool.Mempool
func init() {
func FuzzMempool(f *testing.F) {
app := kvstore.NewApplication()
logger := log.NewNopLogger()
conn := abciclient.NewLocalClient(logger, app)
@@ -28,19 +25,9 @@ func init() {
cfg := config.DefaultMempoolConfig()
cfg.Broadcast = false
getMp = func() mempool.Mempool {
if mp == nil {
mp = mempool.NewTxMempool(logger, cfg, conn)
}
return mp
}
}
mp := mempool.NewTxMempool(logger, cfg, conn)
func FuzzMempool(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
err := getMp().CheckTx(context.Background(), data, nil, mempool.TxInfo{})
if err != nil {
panic(err)
}
_ = mp.CheckTx(context.Background(), data, nil, mempool.TxInfo{})
})
}

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()