Skip to content

Commit

Permalink
Merge pull request #1092 from iand/test-examples
Browse files Browse the repository at this point in the history
chore: bring examples back into repository and add tests
  • Loading branch information
marten-seemann authored May 12, 2021
2 parents 035edd3 + d18fa0f commit 52d3e89
Show file tree
Hide file tree
Showing 90 changed files with 8,262 additions and 9 deletions.
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
*.swp
examples/echo/echo
examples/multicodecs/multicodecs
.idea
.idea
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ As new releases of go-libp2p are made available, you can upgrade your applicatio

### Examples

Examples can be found in the [examples repo](https://github.com/libp2p/go-libp2p-examples).
Examples can be found in the [examples folder](examples).

## Development

Expand Down Expand Up @@ -180,7 +180,6 @@ List of packages currently in existence for libp2p:
| [`go-libp2p-http`](//github.com/libp2p/go-libp2p-http) | [![Travis CI](https://travis-ci.com/libp2p/go-libp2p-http.svg?branch=master)](https://travis-ci.com/libp2p/go-libp2p-http) | [![codecov](https://codecov.io/gh/libp2p/go-libp2p-http/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/go-libp2p-http) | HTTP on top of libp2p streams |
| **Testing and examples** |
| [`go-libp2p-testing`](//github.com/libp2p/go-libp2p-testing) | [![Travis CI](https://travis-ci.com/libp2p/go-libp2p-testing.svg?branch=master)](https://travis-ci.com/libp2p/go-libp2p-testing) | [![codecov](https://codecov.io/gh/libp2p/go-libp2p-testing/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/go-libp2p-testing) | a collection of testing utilities for libp2p |
| [`go-libp2p-examples`](//github.com/libp2p/go-libp2p-examples) | [![Travis CI](https://travis-ci.com/libp2p/go-libp2p-examples.svg?branch=master)](https://travis-ci.com/libp2p/go-libp2p-examples) | [![codecov](https://codecov.io/gh/libp2p/go-libp2p-examples/branch/master/graph/badge.svg)](https://codecov.io/gh/libp2p/go-libp2p-examples) | go-libp2p examples and tutorials |

# Contribute

Expand Down
42 changes: 40 additions & 2 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,41 @@
# go-libp2p Examples
# `go-libp2p` examples and tutorials

The go-libp2p examples have moved to [go-libp2p-examples](https://github.com/libp2p/go-libp2p-examples).
[![](https://img.shields.io/badge/made%20by-Protocol%20Labs-blue.svg?style=flat-square)](https://protocol.ai)
[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io/)
[![](https://img.shields.io/badge/freenode-%23libp2p-yellow.svg?style=flat-square)](http://webchat.freenode.net/?channels=%23libp2p)
[![Discourse posts](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg)](https://discuss.libp2p.io)

In this folder, you can find a variety of examples to help you get started in using go-libp2p. Every example as a specific purpose and some of each incorporate a full tutorial that you can follow through, helping you expand your knowledge about libp2p and p2p networks in general.

Let us know if you find any issue or if you want to contribute and add a new tutorial, feel welcome to submit a pr, thank you!

## Examples and Tutorials

- [The libp2p 'host'](./libp2p-host)
- [Building an http proxy with libp2p](./http-proxy)
- [An echo host](./echo)
- [Multicodecs with protobufs](./multipro)
- [P2P chat application](./chat)
- [P2P chat application w/ rendezvous peer discovery](./chat-with-rendezvous)
- [P2P chat application with peer discovery using mdns](./chat-with-mdns)
- [A chapter based approach to building a libp2p application](./ipfs-camp-2019/) _Created for [IPFS Camp 2019](https://github.com/ipfs/camp/tree/master/CORE_AND_ELECTIVE_COURSES/CORE_COURSE_B)_

For js-libp2p examples, check https://github.com/libp2p/js-libp2p/tree/master/examples

## Troubleshooting

When building the examples ensure you have a clean `$GOPATH`. If you have checked out and built other `libp2p` repos then you may get errors similar to the one below when building the examples. Note that the use of the `gx` package manager **is not required** to run the examples or to use `libp2p`.
```
$:~/go/src/github.com/libp2p/go-libp2p-examples/libp2p-host$ go build host.go
# command-line-arguments
./host.go:36:18: cannot use priv (type "github.com/libp2p/go-libp2p-crypto".PrivKey) as type "gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto".PrivKey in argument to libp2p.Identity:
"github.com/libp2p/go-libp2p-crypto".PrivKey does not implement "gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto".PrivKey (wrong type for Equals method)
have Equals("github.com/libp2p/go-libp2p-crypto".Key) bool
want Equals("gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto".Key) bool
```

To obtain a clean `$GOPATH` execute the following:
```
> mkdir /tmp/libp2p-examples
> export GOPATH=/tmp/libp2p/examples
```
1 change: 1 addition & 0 deletions examples/chat-with-mdns/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chat-with-mdns
100 changes: 100 additions & 0 deletions examples/chat-with-mdns/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# p2p chat app with libp2p [support peer discovery using mdns]

This program demonstrates a simple p2p chat application. You will learn how to discover a peer in the network (using mdns), connect to it and open a chat stream. This example is heavily influenced by (and shamelessly copied from) `chat-with-rendezvous` example

## How to build this example?

```
go get -v -d ./...
go build
```

## Usage

Use two different terminal windows to run

```
./chat-with-mdns -port 6666
./chat-with-mdns -port 6668
```


## So how does it work?

1. **Configure a p2p host**
```go
ctx := context.Background()

// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
host, err := libp2p.New(ctx)
```
[libp2p.New](https://godoc.org/github.com/libp2p/go-libp2p#New) is the constructor for libp2p node. It creates a host with given configuration.

2. **Set a default handler function for incoming connections.**

This function is called on the local peer when a remote peer initiate a connection and starts a stream with the local peer.
```go
// Set a function as stream handler.
host.SetStreamHandler("/chat/1.1.0", handleStream)
```

```handleStream``` is executed for each new stream incoming to the local peer. ```stream``` is used to exchange data between local and remote peer. This example uses non blocking functions for reading and writing from this stream.

```go
func handleStream(stream net.Stream) {

// Create a buffer stream for non blocking read and write.
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go readData(rw)
go writeData(rw)

// 'stream' will stay open until you close it (or the other side closes it).
}
```

3. **Find peers nearby using mdns**

Start [mdns discovery](https://godoc.org/github.com/libp2p/go-libp2p/p2p/discovery#NewMdnsService) service in host.

```go
ser, err := discovery.NewMdnsService(ctx, peerhost, time.Hour, rendezvous)
```
register [Notifee interface](https://godoc.org/github.com/libp2p/go-libp2p/p2p/discovery#Notifee) with service so that we get notified about peer discovery

```go
n := &discoveryNotifee{}
ser.RegisterNotifee(n)
```


4. **Open streams to peers found.**

Finally we open stream to the peers we found, as we find them

```go
peer := <-peerChan // will block untill we discover a peer
fmt.Println("Found peer:", peer, ", connecting")

if err := host.Connect(ctx, peer); err != nil {
fmt.Println("Connection failed:", err)
}

// open a stream, this stream will be handled by handleStream other end
stream, err := host.NewStream(ctx, peer.ID, protocol.ID(cfg.ProtocolID))

if err != nil {
fmt.Println("Stream open failed", err)
} else {
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go writeData(rw)
go readData(rw)
fmt.Println("Connected to:", peer)
}
```

## Authors
1. Bineesh Lazar
24 changes: 24 additions & 0 deletions examples/chat-with-mdns/flags.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"flag"
)

type config struct {
RendezvousString string
ProtocolID string
listenHost string
listenPort int
}

func parseFlags() *config {
c := &config{}

flag.StringVar(&c.RendezvousString, "rendezvous", "meetme", "Unique string to identify group of nodes. Share this with your friends to let them connect with you")
flag.StringVar(&c.listenHost, "host", "0.0.0.0", "The bootstrap node host listen address\n")
flag.StringVar(&c.ProtocolID, "pid", "/chat/1.1.0", "Sets a protocol id for stream headers")
flag.IntVar(&c.listenPort, "port", 4001, "node listen port")

flag.Parse()
return c
}
141 changes: 141 additions & 0 deletions examples/chat-with-mdns/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package main

import (
"bufio"
"context"
"crypto/rand"
"flag"
"fmt"
"os"

"github.com/libp2p/go-libp2p"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/libp2p/go-libp2p-core/network"
"github.com/libp2p/go-libp2p-core/protocol"

"github.com/multiformats/go-multiaddr"
)

func handleStream(stream network.Stream) {
fmt.Println("Got a new stream!")

// Create a buffer stream for non blocking read and write.
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go readData(rw)
go writeData(rw)

// 'stream' will stay open until you close it (or the other side closes it).
}

func readData(rw *bufio.ReadWriter) {
for {
str, err := rw.ReadString('\n')
if err != nil {
fmt.Println("Error reading from buffer")
panic(err)
}

if str == "" {
return
}
if str != "\n" {
// Green console colour: \x1b[32m
// Reset console colour: \x1b[0m
fmt.Printf("\x1b[32m%s\x1b[0m> ", str)
}

}
}

func writeData(rw *bufio.ReadWriter) {
stdReader := bufio.NewReader(os.Stdin)

for {
fmt.Print("> ")
sendData, err := stdReader.ReadString('\n')
if err != nil {
fmt.Println("Error reading from stdin")
panic(err)
}

_, err = rw.WriteString(fmt.Sprintf("%s\n", sendData))
if err != nil {
fmt.Println("Error writing to buffer")
panic(err)
}
err = rw.Flush()
if err != nil {
fmt.Println("Error flushing buffer")
panic(err)
}
}
}

func main() {
help := flag.Bool("help", false, "Display Help")
cfg := parseFlags()

if *help {
fmt.Printf("Simple example for peer discovery using mDNS. mDNS is great when you have multiple peers in local LAN.")
fmt.Printf("Usage: \n Run './chat-with-mdns'\nor Run './chat-with-mdns -host [host] -port [port] -rendezvous [string] -pid [proto ID]'\n")

os.Exit(0)
}

fmt.Printf("[*] Listening on: %s with port: %d\n", cfg.listenHost, cfg.listenPort)

ctx := context.Background()
r := rand.Reader

// Creates a new RSA key pair for this host.
prvKey, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r)
if err != nil {
panic(err)
}

// 0.0.0.0 will listen on any interface device.
sourceMultiAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/%s/tcp/%d", cfg.listenHost, cfg.listenPort))

// libp2p.New constructs a new libp2p Host.
// Other options can be added here.
host, err := libp2p.New(
ctx,
libp2p.ListenAddrs(sourceMultiAddr),
libp2p.Identity(prvKey),
)

if err != nil {
panic(err)
}

// Set a function as stream handler.
// This function is called when a peer initiates a connection and starts a stream with this peer.
host.SetStreamHandler(protocol.ID(cfg.ProtocolID), handleStream)

fmt.Printf("\n[*] Your Multiaddress Is: /ip4/%s/tcp/%v/p2p/%s\n", cfg.listenHost, cfg.listenPort, host.ID().Pretty())

peerChan := initMDNS(ctx, host, cfg.RendezvousString)

peer := <-peerChan // will block untill we discover a peer
fmt.Println("Found peer:", peer, ", connecting")

if err := host.Connect(ctx, peer); err != nil {
fmt.Println("Connection failed:", err)
}

// open a stream, this stream will be handled by handleStream other end
stream, err := host.NewStream(ctx, peer.ID, protocol.ID(cfg.ProtocolID))

if err != nil {
fmt.Println("Stream open failed", err)
} else {
rw := bufio.NewReadWriter(bufio.NewReader(stream), bufio.NewWriter(stream))

go writeData(rw)
go readData(rw)
fmt.Println("Connected to:", peer)
}

select {} //wait here
}
35 changes: 35 additions & 0 deletions examples/chat-with-mdns/mdns.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"context"
"time"

"github.com/libp2p/go-libp2p-core/host"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/libp2p/go-libp2p/p2p/discovery"
)

type discoveryNotifee struct {
PeerChan chan peer.AddrInfo
}

//interface to be called when new peer is found
func (n *discoveryNotifee) HandlePeerFound(pi peer.AddrInfo) {
n.PeerChan <- pi
}

//Initialize the MDNS service
func initMDNS(ctx context.Context, peerhost host.Host, rendezvous string) chan peer.AddrInfo {
// An hour might be a long long period in practical applications. But this is fine for us
ser, err := discovery.NewMdnsService(ctx, peerhost, time.Hour, rendezvous)
if err != nil {
panic(err)
}

//register with service so that we get notified about peer discovery
n := &discoveryNotifee{}
n.PeerChan = make(chan peer.AddrInfo)

ser.RegisterNotifee(n)
return n.PeerChan
}
1 change: 1 addition & 0 deletions examples/chat-with-rendezvous/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
chat-with-rendezvous
Loading

0 comments on commit 52d3e89

Please sign in to comment.