diff --git a/agreement/gossip/network_test.go b/agreement/gossip/network_test.go index 1a6fa80be9..e1c5f49613 100644 --- a/agreement/gossip/network_test.go +++ b/agreement/gossip/network_test.go @@ -18,7 +18,6 @@ package gossip import ( "context" - "net" "net/http" "sync" "sync/atomic" @@ -156,7 +155,7 @@ func (w *whiteholeNetwork) GetPeers(options ...network.PeerOption) []network.Pee } func (w *whiteholeNetwork) RegisterHTTPHandler(path string, handler http.Handler) { } -func (w *whiteholeNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { +func (w *whiteholeNetwork) GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettable) { return nil } diff --git a/catchup/ledgerFetcher.go b/catchup/ledgerFetcher.go index 3225d1deaf..269aed1ff4 100644 --- a/catchup/ledgerFetcher.go +++ b/catchup/ledgerFetcher.go @@ -74,13 +74,18 @@ func makeLedgerFetcher(net network.GossipNode, accessor ledger.CatchpointCatchup } func (lf *ledgerFetcher) requestLedger(ctx context.Context, peer network.HTTPPeer, round basics.Round, method string) (*http.Response, error) { - parsedURL, err := network.ParseHostOrURL(peer.GetAddress()) - if err != nil { - return nil, err - } + var ledgerURL string + if network.IsMultiaddr(peer.GetAddress()) { + ledgerURL = network.SubstituteGenesisID(lf.net, "/v1/{genesisID}/ledger/"+strconv.FormatUint(uint64(round), 36)) + } else { - parsedURL.Path = network.SubstituteGenesisID(lf.net, path.Join(parsedURL.Path, "/v1/{genesisID}/ledger/"+strconv.FormatUint(uint64(round), 36))) - ledgerURL := parsedURL.String() + parsedURL, err := network.ParseHostOrURL(peer.GetAddress()) + if err != nil { + return nil, err + } + parsedURL.Path = network.SubstituteGenesisID(lf.net, path.Join(parsedURL.Path, "/v1/{genesisID}/ledger/"+strconv.FormatUint(uint64(round), 36))) + ledgerURL = parsedURL.String() + } lf.log.Debugf("ledger %s %#v peer %#v %T", method, ledgerURL, peer, peer) request, err := http.NewRequestWithContext(ctx, method, ledgerURL, nil) if err != nil { diff --git a/catchup/ledgerFetcher_test.go b/catchup/ledgerFetcher_test.go index 6bbde32120..321227890b 100644 --- a/catchup/ledgerFetcher_test.go +++ b/catchup/ledgerFetcher_test.go @@ -17,6 +17,7 @@ package catchup import ( + "archive/tar" "context" "fmt" "net" @@ -30,6 +31,8 @@ import ( "github.com/algorand/go-algorand/data/basics" "github.com/algorand/go-algorand/ledger" "github.com/algorand/go-algorand/logging" + p2ptesting "github.com/algorand/go-algorand/network/p2p/testing" + "github.com/algorand/go-algorand/rpcs" "github.com/algorand/go-algorand/test/partitiontest" ) @@ -125,7 +128,7 @@ func TestLedgerFetcherErrorResponseHandling(t *testing.T) { } } -func TestLedgerFetcherHeadLedger(t *testing.T) { +func TestLedgerFetcher(t *testing.T) { partitiontest.PartitionTest(t) // create a dummy server. @@ -136,16 +139,19 @@ func TestLedgerFetcherHeadLedger(t *testing.T) { listener, err := net.Listen("tcp", "localhost:") var httpServerResponse = 0 - var contentTypes = make([]string, 0) require.NoError(t, err) go s.Serve(listener) defer s.Close() defer listener.Close() mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - for _, contentType := range contentTypes { - w.Header().Add("Content-Type", contentType) + if req.Method == http.MethodHead { + w.WriteHeader(httpServerResponse) + } else { + w.Header().Add("Content-Type", rpcs.LedgerResponseContentType) + w.WriteHeader(httpServerResponse) + wtar := tar.NewWriter(w) + wtar.Close() } - w.WriteHeader(httpServerResponse) }) successPeer := testHTTPPeer(listener.Addr().String()) lf := makeLedgerFetcher(&mocks.MockNetwork{}, &mocks.MockCatchpointCatchupAccessor{}, logging.TestingLog(t), &dummyLedgerFetcherReporter{}, config.GetDefaultLocal()) @@ -169,8 +175,46 @@ func TestLedgerFetcherHeadLedger(t *testing.T) { err = lf.headLedger(context.Background(), &successPeer, basics.Round(0)) require.NoError(t, err) + httpServerResponse = http.StatusOK + err = lf.downloadLedger(context.Background(), &successPeer, basics.Round(0)) + require.NoError(t, err) + // headLedger 500 response httpServerResponse = http.StatusInternalServerError err = lf.headLedger(context.Background(), &successPeer, basics.Round(0)) require.Equal(t, fmt.Errorf("headLedger error response status code %d", http.StatusInternalServerError), err) } + +func TestLedgerFetcherP2P(t *testing.T) { + partitiontest.PartitionTest(t) + + mux := http.NewServeMux() + nodeA := p2ptesting.MakeHTTPNode(t) + nodeA.RegisterHTTPHandler("/v1/ledger/0", mux) + var httpServerResponse = 0 + mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodHead { + w.WriteHeader(httpServerResponse) + } else { + w.Header().Add("Content-Type", rpcs.LedgerResponseContentType) + w.WriteHeader(httpServerResponse) + wtar := tar.NewWriter(w) + wtar.Close() + } + }) + + nodeA.Start() + defer nodeA.Stop() + + successPeer := nodeA.GetHTTPPeer() + lf := makeLedgerFetcher(nodeA, &mocks.MockCatchpointCatchupAccessor{}, logging.TestingLog(t), &dummyLedgerFetcherReporter{}, config.GetDefaultLocal()) + + // headLedger 200 response + httpServerResponse = http.StatusOK + err := lf.headLedger(context.Background(), successPeer, basics.Round(0)) + require.NoError(t, err) + + httpServerResponse = http.StatusOK + err = lf.downloadLedger(context.Background(), successPeer, basics.Round(0)) + require.NoError(t, err) +} diff --git a/components/mocks/mockNetwork.go b/components/mocks/mockNetwork.go index a24cf8fbe8..9c8fc97652 100644 --- a/components/mocks/mockNetwork.go +++ b/components/mocks/mockNetwork.go @@ -19,7 +19,6 @@ package mocks import ( "context" "errors" - "net" "net/http" "github.com/algorand/go-algorand/network" @@ -100,7 +99,7 @@ func (network *MockNetwork) RegisterHTTPHandler(path string, handler http.Handle func (network *MockNetwork) OnNetworkAdvance() {} // GetHTTPRequestConnection - empty implementation -func (network *MockNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { +func (network *MockNetwork) GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettable) { return nil } diff --git a/network/gossipNode.go b/network/gossipNode.go index d973ef8a00..0c6e0b06cc 100644 --- a/network/gossipNode.go +++ b/network/gossipNode.go @@ -18,9 +18,9 @@ package network import ( "context" - "net" "net/http" "strings" + "time" "github.com/algorand/go-algorand/config" "github.com/algorand/go-algorand/protocol" @@ -52,6 +52,13 @@ const ( PeersPhonebookArchivers PeerOption = iota ) +// DeadlineSettable abstracts net.Conn and related types as deadline-settable +type DeadlineSettable interface { + SetDeadline(time.Time) error + SetReadDeadline(time.Time) error + SetWriteDeadline(time.Time) error +} + // GossipNode represents a node in the gossip network type GossipNode interface { Address() (string, bool) @@ -95,7 +102,7 @@ type GossipNode interface { // GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same // request that was provided to the http handler ( or provide a fallback Context() to that ) - GetHTTPRequestConnection(request *http.Request) (conn net.Conn) + GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettable) // GetGenesisID returns the network-specific genesisID. GetGenesisID() string diff --git a/network/hybridNetwork.go b/network/hybridNetwork.go index 69c09186aa..d66b794f24 100644 --- a/network/hybridNetwork.go +++ b/network/hybridNetwork.go @@ -19,7 +19,6 @@ package network import ( "context" "fmt" - "net" "net/http" "sync" @@ -206,8 +205,12 @@ func (n *HybridP2PNetwork) OnNetworkAdvance() { // GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same // request that was provided to the http handler ( or provide a fallback Context() to that ) -func (n *HybridP2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { - return nil +func (n *HybridP2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettable) { + conn = n.wsNetwork.GetHTTPRequestConnection(request) + if conn != nil { + return conn + } + return n.p2pNetwork.GetHTTPRequestConnection(request) } // GetGenesisID returns the network-specific genesisID. diff --git a/network/p2p/p2p.go b/network/p2p/p2p.go index 7450f34794..b399c569b4 100644 --- a/network/p2p/p2p.go +++ b/network/p2p/p2p.go @@ -35,6 +35,8 @@ import ( "github.com/libp2p/go-libp2p/core/network" "github.com/libp2p/go-libp2p/core/peer" "github.com/libp2p/go-libp2p/core/peerstore" + "github.com/libp2p/go-libp2p/core/protocol" + libp2phttp "github.com/libp2p/go-libp2p/p2p/http" "github.com/libp2p/go-libp2p/p2p/muxer/yamux" "github.com/libp2p/go-libp2p/p2p/security/noise" "github.com/libp2p/go-libp2p/p2p/transport/tcp" @@ -56,6 +58,8 @@ type Service interface { ListPeersForTopic(topic string) []peer.ID Subscribe(topic string, val pubsub.ValidatorEx) (*pubsub.Subscription, error) Publish(ctx context.Context, topic string, data []byte) error + + GetStream(peer.ID) (network.Stream, bool) } // serviceImpl manages integration with libp2p and implements the Service interface @@ -116,7 +120,47 @@ func MakeHost(cfg config.Local, datadir string, pstore peerstore.Peerstore) (hos noListenAddrs, libp2p.Security(noise.ID, noise.New), ) - return host, listenAddr, err + return &StreamChainingHost{ + Host: host, + handlers: map[protocol.ID][]network.StreamHandler{}, + }, listenAddr, err +} + +// StreamChainingHost is a wrapper around host.Host that overrides SetStreamHandler +// to allow chaining multiple handlers for the same protocol. +// Note, there should be probably only single handler that writes/reads streams. +type StreamChainingHost struct { + host.Host + handlers map[protocol.ID][]network.StreamHandler + mutex deadlock.Mutex +} + +// SetStreamHandler overrides the host.Host.SetStreamHandler method for chaining multiple handlers. +// Function objects are not comparable so theoretically it could have duplicates. +// The main use case is to track HTTP streams for ProtocolIDForMultistreamSelect = "/http/1.1" +// so it could just filter for such protocol if there any issues with other protocols like kad or mesh. +func (h *StreamChainingHost) SetStreamHandler(pid protocol.ID, handler network.StreamHandler) { + h.mutex.Lock() + defer h.mutex.Unlock() + + handlers := h.handlers[pid] + if len(handlers) == 0 { + // no other handlers, do not set a proxy handler + h.Host.SetStreamHandler(pid, handler) + h.handlers[pid] = append(handlers, handler) + return + } + // otherwise chain the handlers with a copy of the existing handlers + handlers = append(handlers, handler) + // copy to save it in the closure and call lock free + currentHandlers := make([]network.StreamHandler, len(handlers)) + copy(currentHandlers, handlers) + h.Host.SetStreamHandler(pid, func(s network.Stream) { + for _, h := range currentHandlers { + h(s) + } + }) + h.handlers[pid] = handlers } // MakeService creates a P2P service instance @@ -125,6 +169,7 @@ func MakeService(ctx context.Context, log logging.Logger, cfg config.Local, h ho sm := makeStreamManager(ctx, log, h, wsStreamHandler) h.Network().Notify(sm) h.SetStreamHandler(AlgorandWsProtocol, sm.streamHandler) + h.SetStreamHandler(libp2phttp.ProtocolIDForMultistreamSelect, sm.streamHandlerHTTP) ps, err := makePubSub(ctx, cfg, h) if err != nil { @@ -218,6 +263,10 @@ func (s *serviceImpl) ClosePeer(peer peer.ID) error { return s.host.Network().ClosePeer(peer) } +func (s *serviceImpl) GetStream(peerID peer.ID) (network.Stream, bool) { + return s.streams.getStream(peerID) +} + // netAddressToListenAddress converts a netAddress in "ip:port" format to a listen address // that can be passed in to libp2p.ListenAddrStrings func netAddressToListenAddress(netAddress string) (string, error) { diff --git a/network/p2p/p2p_test.go b/network/p2p/p2p_test.go index 558131fe48..005dbc8330 100644 --- a/network/p2p/p2p_test.go +++ b/network/p2p/p2p_test.go @@ -17,11 +17,20 @@ package p2p import ( + "context" "fmt" + "sync/atomic" "testing" + "time" - "github.com/algorand/go-algorand/test/partitiontest" + "github.com/libp2p/go-libp2p/core/network" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/config" + "github.com/algorand/go-algorand/network/p2p/peerstore" + "github.com/algorand/go-algorand/test/partitiontest" ) // Tests the helper function netAddressToListenAddress which converts @@ -74,3 +83,64 @@ func TestNetAddressToListenAddress(t *testing.T) { }) } } + +func TestP2PStreamingHost(t *testing.T) { + partitiontest.PartitionTest(t) + + cfg := config.GetDefaultLocal() + dir := t.TempDir() + pstore, err := peerstore.NewPeerStore(nil) + require.NoError(t, err) + h, la, err := MakeHost(cfg, dir, pstore) + require.NoError(t, err) + + var h1calls atomic.Int64 + h1 := func(network.Stream) { + h1calls.Add(1) + } + var h2calls atomic.Int64 + h2 := func(network.Stream) { + h2calls.Add(1) + } + + ma, err := multiaddr.NewMultiaddr(la) + require.NoError(t, err) + h.Network().Listen(ma) + defer h.Close() + + h.SetStreamHandler(AlgorandWsProtocol, h1) + h.SetStreamHandler(AlgorandWsProtocol, h2) + + addrInfo := peer.AddrInfo{ + ID: h.ID(), + Addrs: h.Addrs(), + } + cpstore, err := peerstore.NewPeerStore([]*peer.AddrInfo{&addrInfo}) + require.NoError(t, err) + c, _, err := MakeHost(cfg, dir, cpstore) + require.NoError(t, err) + defer c.Close() + + s1, err := c.NewStream(context.Background(), h.ID(), AlgorandWsProtocol) + require.NoError(t, err) + s1.Write([]byte("hello")) + defer s1.Close() + + require.Eventually(t, func() bool { + return h1calls.Load() == 1 && h2calls.Load() == 1 + }, 5*time.Second, 100*time.Millisecond) + + // ensure a single handler also works as expected + h1calls.Store(0) + h.SetStreamHandler(algorandP2pHTTPProtocol, h1) + + s2, err := c.NewStream(context.Background(), h.ID(), algorandP2pHTTPProtocol) + require.NoError(t, err) + s2.Write([]byte("hello")) + defer s2.Close() + + require.Eventually(t, func() bool { + return h1calls.Load() == 1 + }, 5*time.Second, 100*time.Millisecond) + +} diff --git a/network/p2p/streams.go b/network/p2p/streams.go index 160f273e17..0961141a0c 100644 --- a/network/p2p/streams.go +++ b/network/p2p/streams.go @@ -104,6 +104,20 @@ func (n *streamManager) streamHandler(stream network.Stream) { n.handler(n.ctx, remotePeer, stream, incoming) } +// streamHandlerHTTP tracks the ProtocolIDForMultistreamSelect = "/http/1.1" streams +func (n *streamManager) streamHandlerHTTP(stream network.Stream) { + n.streamsLock.Lock() + defer n.streamsLock.Unlock() + n.streams[stream.Conn().LocalPeer()] = stream +} + +func (n *streamManager) getStream(peerID peer.ID) (network.Stream, bool) { + n.streamsLock.Lock() + defer n.streamsLock.Unlock() + stream, ok := n.streams[peerID] + return stream, ok +} + // Connected is called when a connection is opened func (n *streamManager) Connected(net network.Network, conn network.Conn) { remotePeer := conn.RemotePeer() @@ -160,6 +174,12 @@ func (n *streamManager) Disconnected(net network.Network, conn network.Conn) { stream.Close() delete(n.streams, conn.RemotePeer()) } + + stream, ok = n.streams[conn.LocalPeer()] + if ok { + stream.Close() + delete(n.streams, conn.LocalPeer()) + } } // Listen is called when network starts listening on an addr diff --git a/network/p2p/testing/httpNode.go b/network/p2p/testing/httpNode.go index f188abcb1a..523cdc5d4c 100644 --- a/network/p2p/testing/httpNode.go +++ b/network/p2p/testing/httpNode.go @@ -77,6 +77,12 @@ func (p *HTTPNode) Stop() { p.Host.Close() } +// GetHTTPPeer returns the http peer for connecting to this node +func (p *HTTPNode) GetHTTPPeer() network.Peer { + addrInfo := peer.AddrInfo{ID: p.ID(), Addrs: p.Addrs()} + return httpPeer{addrInfo, p.tb} +} + // GetGenesisID returns genesisID func (p *HTTPNode) GetGenesisID() string { return p.genesisID } diff --git a/network/p2pNetwork.go b/network/p2pNetwork.go index d546f2fe05..36b9e74eb0 100644 --- a/network/p2pNetwork.go +++ b/network/p2pNetwork.go @@ -560,7 +560,20 @@ func (n *P2PNetwork) OnNetworkAdvance() {} // GetHTTPRequestConnection returns the underlying connection for the given request. Note that the request must be the same // request that was provided to the http handler ( or provide a fallback Context() to that ) -func (n *P2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { return nil } +func (n *P2PNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettable) { + addr := request.Context().Value(http.LocalAddrContextKey).(net.Addr) + peerID, err := peer.Decode(addr.String()) + if err != nil { + n.log.Infof("GetHTTPRequestConnection failed to decode %s", addr.String()) + return nil + } + conn, ok := n.service.GetStream(peerID) + if !ok { + n.log.Warnf("GetHTTPRequestConnection no such stream for peer %s", peerID.String()) + return nil + } + return conn +} // wsStreamHandler is a callback that the p2p package calls when a new peer connects and establishes a // stream for the websocket protocol. diff --git a/network/p2pNetwork_test.go b/network/p2pNetwork_test.go index 49a717de92..e64f4df85e 100644 --- a/network/p2pNetwork_test.go +++ b/network/p2pNetwork_test.go @@ -246,6 +246,10 @@ func (s *mockService) Publish(ctx context.Context, topic string, data []byte) er return nil } +func (s *mockService) GetStream(peer.ID) (network.Stream, bool) { + return nil, false +} + func makeMockService(id peer.ID, addrs []ma.Multiaddr) *mockService { return &mockService{ id: id, @@ -568,11 +572,17 @@ func TestMultiaddrConversionToFrom(t *testing.T) { } type p2phttpHandler struct { + tb testing.TB retData string + net GossipNode } func (h *p2phttpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte(h.retData)) + if r.URL.Path == "/check-conn" { + c := h.net.GetHTTPRequestConnection(r) + require.NotNil(h.tb, c) + } } func TestP2PHTTPHandler(t *testing.T) { @@ -587,11 +597,11 @@ func TestP2PHTTPHandler(t *testing.T) { netA, err := NewP2PNetwork(log, cfg, "", nil, genesisID, config.Devtestnet, &nopeNodeInfo{}) require.NoError(t, err) - h := &p2phttpHandler{"hello"} + h := &p2phttpHandler{t, "hello", nil} netA.RegisterHTTPHandler("/test", h) - h2 := &p2phttpHandler{"world"} - netA.RegisterHTTPHandler("/bar", h2) + h2 := &p2phttpHandler{t, "world", netA} + netA.RegisterHTTPHandler("/check-conn", h2) netA.Start() defer netA.Stop() @@ -613,7 +623,7 @@ func TestP2PHTTPHandler(t *testing.T) { httpClient, err = p2p.MakeHTTPClient(&peerInfoA) require.NoError(t, err) - resp, err = httpClient.Get("/bar") + resp, err = httpClient.Get("/check-conn") require.NoError(t, err) defer resp.Body.Close() diff --git a/network/wsNetwork.go b/network/wsNetwork.go index a05790d644..cdb3b4c635 100644 --- a/network/wsNetwork.go +++ b/network/wsNetwork.go @@ -1019,7 +1019,7 @@ func (wn *WebsocketNetwork) checkIncomingConnectionVariables(response http.Respo // request that was provided to the http handler ( or provide a fallback Context() to that ) // if the provided request has no associated connection, it returns nil. ( this should not happen for any http request that was registered // by WebsocketNetwork ) -func (wn *WebsocketNetwork) GetHTTPRequestConnection(request *http.Request) (conn net.Conn) { +func (wn *WebsocketNetwork) GetHTTPRequestConnection(request *http.Request) (conn DeadlineSettable) { if wn.requestsTracker != nil { conn = wn.requestsTracker.GetRequestConnection(request) } diff --git a/rpcs/ledgerService.go b/rpcs/ledgerService.go index b3742bb985..e020a9bd6a 100644 --- a/rpcs/ledgerService.go +++ b/rpcs/ledgerService.go @@ -60,19 +60,25 @@ type LedgerForService interface { GetCatchpointStream(round basics.Round) (ledger.ReadCloseSizer, error) } +// httpGossipNode is a reduced interface for the gossipNode that only includes the methods needed by the LedgerService +type httpGossipNode interface { + RegisterHTTPHandler(path string, handler http.Handler) + GetHTTPRequestConnection(request *http.Request) (conn network.DeadlineSettable) +} + // LedgerService represents the Ledger RPC API type LedgerService struct { // running is non-zero once the service is running, and zero when it's not running. it needs to be at a 32-bit aligned address for RasPI support. running atomic.Int32 ledger LedgerForService genesisID string - net network.GossipNode + net httpGossipNode enableService bool stopping sync.WaitGroup } // MakeLedgerService creates a LedgerService around the provider Ledger and registers it with the HTTP router -func MakeLedgerService(config config.Local, ledger LedgerForService, net network.GossipNode, genesisID string) *LedgerService { +func MakeLedgerService(config config.Local, ledger LedgerForService, net httpGossipNode, genesisID string) *LedgerService { service := &LedgerService{ ledger: ledger, genesisID: genesisID, diff --git a/rpcs/ledgerService_test.go b/rpcs/ledgerService_test.go index 1cc52fc9c0..a100f03c2b 100644 --- a/rpcs/ledgerService_test.go +++ b/rpcs/ledgerService_test.go @@ -17,6 +17,9 @@ package rpcs import ( + "archive/tar" + "bytes" + "compress/gzip" "fmt" "io" "net/http" @@ -172,3 +175,59 @@ func TestLedgerService(t *testing.T) { ledgerService.Stop() require.Equal(t, int32(0), ledgerService.running.Load()) } + +type mockSizedStream struct { + *bytes.Buffer +} + +func (mss mockSizedStream) Size() (int64, error) { + return int64(mss.Len()), nil +} + +func (mss mockSizedStream) Close() error { + return nil +} + +type mockLedgerForService struct { +} + +func (l *mockLedgerForService) GetCatchpointStream(round basics.Round) (ledger.ReadCloseSizer, error) { + buf := bytes.NewBuffer(nil) + gz := gzip.NewWriter(buf) + wtar := tar.NewWriter(gz) + wtar.Close() + gz.Close() + + buf2 := bytes.NewBuffer(buf.Bytes()) + return mockSizedStream{buf2}, nil +} + +// TestLedgerServiceP2P creates a ledger service on a node, and a p2p client tries to download +// an empty catchpoint file from the ledger service. +func TestLedgerServiceP2P(t *testing.T) { + partitiontest.PartitionTest(t) + + nodeA, nodeB := nodePairP2p(t) + defer nodeA.Stop() + defer nodeB.Stop() + + genesisID := "test GenesisID" + cfg := config.GetDefaultLocal() + cfg.EnableLedgerService = true + l := mockLedgerForService{} + ledgerService := MakeLedgerService(cfg, &l, nodeA, genesisID) + ledgerService.Start() + defer ledgerService.Stop() + + nodeA.RegisterHTTPHandler(LedgerServiceLedgerPath, ledgerService) + + httpPeer := nodeA.GetHTTPPeer().(network.HTTPPeer) + + req, err := http.NewRequest("GET", fmt.Sprintf("/v1/%s/ledger/0", genesisID), nil) + require.NoError(t, err) + resp, err := httpPeer.GetHTTPClient().Do(req) + require.NoError(t, err) + defer func() { _ = resp.Body.Close() }() + + require.Equal(t, http.StatusOK, resp.StatusCode) +}