diff --git a/.github/workflows/linkchecker.yml b/.github/workflows/linkchecker.yml index d143fd905..e2ba80861 100644 --- a/.github/workflows/linkchecker.yml +++ b/.github/workflows/linkchecker.yml @@ -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" diff --git a/go.mod b/go.mod index 33f5e5a6d..f03deab1a 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 1609c9a40..07dedbb7b 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/inspect/rpc/rpc.go b/internal/inspect/rpc/rpc.go index 40f0d5d26..00c3e52ef 100644 --- a/internal/inspect/rpc/rpc.go +++ b/internal/inspect/rpc/rpc.go @@ -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), } } diff --git a/internal/p2p/address.go b/internal/p2p/address.go index 7c084216e..0f4066faf 100644 --- a/internal/p2p/address.go +++ b/internal/p2p/address.go @@ -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, diff --git a/internal/p2p/address_test.go b/internal/p2p/address_test.go index d5f9e498e..d4c745d8d 100644 --- a/internal/p2p/address_test.go +++ b/internal/p2p/address_test.go @@ -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 diff --git a/internal/p2p/mocks/transport.go b/internal/p2p/mocks/transport.go index d08533186..34ebec20e 100644 --- a/internal/p2p/mocks/transport.go +++ b/internal/p2p/mocks/transport.go @@ -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) diff --git a/internal/p2p/p2ptest/network.go b/internal/p2p/p2ptest/network.go index cde14e721..85df029d8 100644 --- a/internal/p2p/p2ptest/network.go +++ b/internal/p2p/p2ptest/network.go @@ -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() diff --git a/internal/p2p/router.go b/internal/p2p/router.go index ca9536900..df096dbb6 100644 --- a/internal/p2p/router.go +++ b/internal/p2p/router.go @@ -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. diff --git a/internal/p2p/router_test.go b/internal/p2p/router_test.go index 7ef77a16d..663e6b81c 100644 --- a/internal/p2p/router_test.go +++ b/internal/p2p/router_test.go @@ -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{}, ) diff --git a/internal/p2p/transport.go b/internal/p2p/transport.go index 041bbda3a..7a965260a 100644 --- a/internal/p2p/transport.go +++ b/internal/p2p/transport.go @@ -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, diff --git a/internal/p2p/transport_mconn.go b/internal/p2p/transport_mconn.go index 6e6728f11..7bf17d1a0 100644 --- a/internal/p2p/transport_mconn.go +++ b/internal/p2p/transport_mconn.go @@ -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 } diff --git a/internal/p2p/transport_mconn_test.go b/internal/p2p/transport_mconn_test.go index 423f4e930..c478dbe1d 100644 --- a/internal/p2p/transport_mconn_test.go +++ b/internal/p2p/transport_mconn_test.go @@ -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() { diff --git a/internal/p2p/transport_memory.go b/internal/p2p/transport_memory.go index 528ce4bb6..3eb4c5b51 100644 --- a/internal/p2p/transport_memory.go +++ b/internal/p2p/transport_memory.go @@ -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) } diff --git a/internal/p2p/transport_test.go b/internal/p2p/transport_test.go index 8f4f90483..b4edf9bc9 100644 --- a/internal/p2p/transport_test.go +++ b/internal/p2p/transport_test.go @@ -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 diff --git a/internal/rpc/core/abci.go b/internal/rpc/core/abci.go index dc7a5bdfe..fa45c6b45 100644 --- a/internal/rpc/core/abci.go +++ b/internal/rpc/core/abci.go @@ -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 diff --git a/internal/rpc/core/blocks.go b/internal/rpc/core/blocks.go index 26044aef7..239344002 100644 --- a/internal/rpc/core/blocks.go +++ b/internal/rpc/core/blocks.go @@ -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 } diff --git a/internal/rpc/core/blocks_test.go b/internal/rpc/core/blocks_test.go index c48ac4c48..d95338332 100644 --- a/internal/rpc/core/blocks_test.go +++ b/internal/rpc/core/blocks_test.go @@ -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 { diff --git a/internal/rpc/core/consensus.go b/internal/rpc/core/consensus.go index f10f37ebc..46e220f05 100644 --- a/internal/rpc/core/consensus.go +++ b/internal/rpc/core/consensus.go @@ -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 } diff --git a/internal/rpc/core/events.go b/internal/rpc/core/events.go index 4e0d2ac8a..3f289bfa7 100644 --- a/internal/rpc/core/events.go +++ b/internal/rpc/core/events.go @@ -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 diff --git a/internal/rpc/core/evidence.go b/internal/rpc/core/evidence.go index c7e2bea8a..5de93d2c2 100644 --- a/internal/rpc/core/evidence.go +++ b/internal/rpc/core/evidence.go @@ -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 } diff --git a/internal/rpc/core/mempool.go b/internal/rpc/core/mempool.go index 450adf4d5..5fc2b9fcf 100644 --- a/internal/rpc/core/mempool.go +++ b/internal/rpc/core/mempool.go @@ -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) } diff --git a/internal/rpc/core/net.go b/internal/rpc/core/net.go index 5444b77b7..b18f1e2fc 100644 --- a/internal/rpc/core/net.go +++ b/internal/rpc/core/net.go @@ -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) diff --git a/internal/rpc/core/routes.go b/internal/rpc/core/routes.go index 945798ed7..4bc1ca414 100644 --- a/internal/rpc/core/routes.go +++ b/internal/rpc/core/routes.go @@ -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 diff --git a/internal/rpc/core/tx.go b/internal/rpc/core/tx.go index 73fa6d2c8..cd643b844 100644 --- a/internal/rpc/core/tx.go +++ b/internal/rpc/core/tx.go @@ -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)) } diff --git a/light/proxy/routes.go b/light/proxy/routes.go index 5dd934ed1..8331723e7 100644 --- a/light/proxy/routes.go +++ b/light/proxy/routes.go @@ -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()) } diff --git a/node/node.go b/node/node.go index 5c63a3253..56379d2e2 100644 --- a/node/node.go +++ b/node/node.go @@ -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() } diff --git a/node/setup.go b/node/setup.go index 512b02901..d6966800a 100644 --- a/node/setup.go +++ b/node/setup.go @@ -310,8 +310,8 @@ func createRouter( nodeKey.PrivKey, peerManager, nodeInfoProducer, - []p2p.Transport{transport}, - []p2p.Endpoint{ep}, + transport, + ep, getRouterConfig(cfg, appClient), ) } diff --git a/rpc/client/eventstream/eventstream_test.go b/rpc/client/eventstream/eventstream_test.go index ca27734e2..8cd9df30f 100644 --- a/rpc/client/eventstream/eventstream_test.go +++ b/rpc/client/eventstream/eventstream_test.go @@ -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) } diff --git a/rpc/client/http/http.go b/rpc/client/http/http.go index 435f80a5c..50d78d279 100644 --- a/rpc/client/http/http.go +++ b/rpc/client/http/http.go @@ -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 } diff --git a/rpc/client/http/request.go b/rpc/client/http/request.go deleted file mode 100644 index 746cb776d..000000000 --- a/rpc/client/http/request.go +++ /dev/null @@ -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"` -} diff --git a/rpc/client/local/local.go b/rpc/client/local/local.go index 24a9a6d7e..8718ee504 100644 --- a/rpc/client/local/local.go +++ b/rpc/client/local/local.go @@ -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) { diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go index b47ff1e76..a3272cb17 100644 --- a/rpc/client/mock/client.go +++ b/rpc/client/mock/client.go @@ -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}) } diff --git a/rpc/coretypes/requests.go b/rpc/coretypes/requests.go new file mode 100644 index 000000000..cd4d22726 --- /dev/null +++ b/rpc/coretypes/requests.go @@ -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 +} diff --git a/rpc/coretypes/responses.go b/rpc/coretypes/responses.go index 8968f9868..51d988f3a 100644 --- a/rpc/coretypes/responses.go +++ b/rpc/coretypes/responses.go @@ -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 diff --git a/rpc/jsonrpc/doc.go b/rpc/jsonrpc/doc.go index b014fe38d..58b522861 100644 --- a/rpc/jsonrpc/doc.go +++ b/rpc/jsonrpc/doc.go @@ -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: diff --git a/rpc/jsonrpc/jsonrpc_test.go b/rpc/jsonrpc/jsonrpc_test.go index 1ba853a62..236db9b32 100644 --- a/rpc/jsonrpc/jsonrpc_test.go +++ b/rpc/jsonrpc/jsonrpc_test.go @@ -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 diff --git a/rpc/jsonrpc/server/http_json_handler.go b/rpc/jsonrpc/server/http_json_handler.go index 94da974de..defcb7d9c 100644 --- a/rpc/jsonrpc/server/http_json_handler.go +++ b/rpc/jsonrpc/server/http_json_handler.go @@ -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) diff --git a/rpc/jsonrpc/server/http_json_handler_test.go b/rpc/jsonrpc/server/http_json_handler_test.go index 1f5d2c320..77c74ffbc 100644 --- a/rpc/jsonrpc/server/http_json_handler_test.go +++ b/rpc/jsonrpc/server/http_json_handler_test.go @@ -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 diff --git a/rpc/jsonrpc/server/http_uri_handler.go b/rpc/jsonrpc/server/http_uri_handler.go index ad0fbb7b3..7e1902ac1 100644 --- a/rpc/jsonrpc/server/http_uri_handler.go +++ b/rpc/jsonrpc/server/http_uri_handler.go @@ -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 -} diff --git a/rpc/jsonrpc/server/parse_test.go b/rpc/jsonrpc/server/parse_test.go index ee0ab5d79..e6667fb0a 100644 --- a/rpc/jsonrpc/server/parse_test.go +++ b/rpc/jsonrpc/server/parse_test.go @@ -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) + } + }) + } + }) } diff --git a/rpc/jsonrpc/server/rpc_func.go b/rpc/jsonrpc/server/rpc_func.go index a58973c6e..456d97bfc 100644 --- a/rpc/jsonrpc/server/rpc_func.go +++ b/rpc/jsonrpc/server/rpc_func.go @@ -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("[]")) } diff --git a/rpc/jsonrpc/server/ws_handler.go b/rpc/jsonrpc/server/ws_handler.go index 7f2221b24..3a259757b 100644 --- a/rpc/jsonrpc/server/ws_handler.go +++ b/rpc/jsonrpc/server/ws_handler.go @@ -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 { diff --git a/rpc/jsonrpc/server/ws_handler_test.go b/rpc/jsonrpc/server/ws_handler_test.go index ae73a953b..ce1bcd973 100644 --- a/rpc/jsonrpc/server/ws_handler_test.go +++ b/rpc/jsonrpc/server/ws_handler_test.go @@ -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) diff --git a/rpc/jsonrpc/test/main.go b/rpc/jsonrpc/test/main.go index 07a3b28f3..2ed013c17 100644 --- a/rpc/jsonrpc/test/main.go +++ b/rpc/jsonrpc/test/main.go @@ -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) { diff --git a/test/fuzz/tests/mempool_test.go b/test/fuzz/tests/mempool_test.go index a76b059ca..2c8623036 100644 --- a/test/fuzz/tests/mempool_test.go +++ b/test/fuzz/tests/mempool_test.go @@ -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{}) }) } diff --git a/test/fuzz/tests/rpc_jsonrpc_server_test.go b/test/fuzz/tests/rpc_jsonrpc_server_test.go index bc4e90881..67dee9ef2 100644 --- a/test/fuzz/tests/rpc_jsonrpc_server_test.go +++ b/test/fuzz/tests/rpc_jsonrpc_server_test.go @@ -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()