Facebook Linkedin Twitter
Posted Sat Dec 24, 2022 •  Reading time: 4 minutes

Peernet: An Easy Way to Add p2p Capabilities to Your Go Program

Today we are going to explore Peernet a p2p protocol designed for file sharing. With a strong focus being simple to use and powerful at the same time.

Out of my personal experience, extending a regular application with p2p capabilities has always been a nightmare. The standard problems would be

  • “How do I talk to nodes behind NAT?”
  • “How do I discover nodes in a p2p network?” and so on.

If you ask the current community of Go developers, IPFS would be considered as the standard solution. When building my initial project p2prc, I did consider trying to extend it with IPFS. The end result was that it was easier writing your own p2p network than extending IPFS with Libp2p (This was a huge nightmare in my case). Peernet ticks most of the checkboxes for a good protocol to extend Go applications with p2p capabilities. It is an amazingly well abstracted p2p network which utilizes UDT to transfer files with the support of UPNP and UDP hole punching to support talking to nodes behind NAT. And there are many more nice features such as a clean decentralized search etc…

Steps to get stated

The aim of today’s blog post is to build a sample p2p application using the Peernet abstraction library. The Peernet abstraction library helps us to extend a Go program with p2p capabilities (i.e. simple set of function calls.)

The boilerplate

This is the go main function with the Peernet boilerplate code needed to get started:

package main

import (
    "encoding/json"
    "fmt"
    Abstrations "github.com/PeernetOfficial/Abstraction"
    "github.com/PeernetOfficial/Abstraction/webapi"
    "github.com/PeernetOfficial/core"
    "github.com/google/uuid"
    "os"
    "runtime"
    "time"
)

func main() {
    backend, status, err := core.Init("<application name>/<version no>", "Config.yaml", nil, nil)
    if status != core.ExitSuccess {
        fmt.Printf("Error %d initializing backend: %s\n", status, err.Error())
    }

    backend.Connect()

    api := webapi.Start(backend, []string{"<ip address>:<port no>"}, false, "", "", 0, 0, uuid.New())

    // The abstracted functions will be called below here 
}

Set a warm up time

We need a delay before we can call the abstracted functions,
so the Go routines in the connect function can learn about nodes in the network. In this case, we add a 6 seconds delay:

// 6 seconds
time.Sleep(time.Millisecond * 6000)

Searching the p2p Network

Searching the network is as simple as calling a function called Abstrations.Search(&<web api object>, "<term>") to search about files metadata in the p2p network. This function returns a search result object.

search, err := Abstrations.Search(api, "space")
    if err != nil {
        // handle error 
    }

Downloading a File From the p2p Network

To download a file we can simply call Abstrations.Download(&<web api object>,<file hash>,<node id>,<download path>). This will ensure that the user can download a file from the p2p network based on the hash and node id which is derived from the file’s metadata of a file.

// Get download dir of the following machine 
homeDir, _ := os.UserHomeDir()

var downloadDir string
if runtime.GOOS == "darwin" {
    downloadDir = homeDir + "/Downloads/"
} else {
    downloadDir = homeDir + "\\Downloads\\"
}

Metadata can be derived from the search results object as an example.

downloadID, err := Abstrations.Download(api, search.Files[0].Hash, search.Files[0].NodeID, downloadDir+search.Files[0].Name)
if err != nil {
    fmt.Println(err)
    return
}

In the code snippet above the hash and node id is derived from the first object of the search result array for the search term “space”.

Calling the download function will return a download ID, using this ID it’s possible to track the status of the download. The DownloadStatus function gets the current status of the current download.

 downloadStatus, err := Abstrations.DownloadStatus(api, downloadID)
    if err != nil {
        fmt.Println(err)
    }

    fmt.Println("========= Downloading file " + downloadStatus.File.Name + " ==============")
    // The status 5 means that the file has completed downloading 
    for downloadStatus.DownloadStatus != 5 {
        downloadStatus, err = Abstrations.DownloadStatus(api, downloadID)
        if err != nil {
            fmt.Println(err)
        }
    }

The code snippet above implements checks if the download is complete and then terminates the loop.

Sharing a File From Peernet

Sharing a file is as simple as calling the function touch. The following function has the shape of Abstrations.Touch(&<web api object>,<file path>). Calling this function allows the user to share all their file to the Peernet Warehouse and Peernet blockchain.

touch, file, err := Abstrations.Touch(api, "example.go")
    if err != nil {
        // handle error
    }
 
fmt.Printf("blockchain verison: %v, blockchain height: %v, file hash: %v \n", touch.BlockchainVersion, touch.BlockchainHeight, file.Hash)

Removing a File From Peernet

To remove a file from the Peernet Warehouse and Peernet blockchain we simply use Abstrations.Rm(&<web api object>,<file id>).

err = Abstrations.Rm(api, file.ID)
    if err != nil {
        // handle error 
    }

More information