GO SDK Microservices

This guide provides a complete walkthrough for setting up and running a Go-based microservice that interacts with the Züs decentralized storage network.

1. Prerequisites Setup

Before you begin:

Install Go

install Go (version 1.20+ recommended). Verify installation:

go version

Get a Züs Wallet

You’ll need a .json wallet file.

It is typically located at:

~/.zcn/wallet.json

Alternatively, you can store the wallet JSON file in any location of your choice. To retrieve your wallet information, you can use the recover wallet command from zwalletcli.

Or you can use any location to create the json file. You can retrieve your wallet info by using recover wallet from the zwalletcli.

Below is sample wallet structure:

{
  "client_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "client_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "keys": [
    {
      "public_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      "private_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
    }
  ],
  "mnemonics": "xxxx xxxx xxxx xxxxx",
  "version": "1.0",
  "date_created": "2023-05-03T12:44:46+05:30",
  "nonce": 0
}

Create config.json

Create a file named config.json in your project root with the following structure:

{
  "wallet_json": "/Users/yourusername/.zcn/wallet.json",
  "block_worker": "https://mainnet.zus.network/dns",
  "chain_id": "0af33bc5402b5f13e349347db8b88b6543157baadb7c9265ef0a8f87429e4a44",
  "signature_scheme": "bls0chain",
  "min_submit": 50,
  "min_confirmation": 50,
  "confirmation_chain_length": 3,
  "sharder_consensous": 3
}

2. Project File Structure

Your project should be structured as follows:

/project-root
├── config.json
├── main.go
└── handlers/
    ├── download_handler.go
    ├── memfile_handler.go
    ├── sdk_handler.go
    └── upload_handler.go

3. Code Your main.go

package main

import (
	"log"
	"net/http"

	"go_zus_blog/handlers" // Replace with your module name if different
	"github.com/gorilla/mux"
)

func main() {
	router := mux.NewRouter()

	router.HandleFunc("/init-sdk", handlers.InitSDKHandler).Methods("POST")
	router.HandleFunc("/upload-file", handlers.UploadFileHandler).Methods("POST")
	router.HandleFunc("/download-file", handlers.DownloadFileHandler).Methods("POST")

	log.Println("Go microservice running on port 8080")
	log.Fatal(http.ListenAndServe(":8080", router))
}

Install Gorilla Mux:

go get -u github.com/gorilla/mux

In case you see an error while importing go_zus_handlers, run this command:

go mod init go_zus_blog

4. Build Your Handlers

sdk_handler.go

package handlers

import (
	"encoding/json"
	"log"
	"net/http"
	"os"

	"github.com/0chain/gosdk/core/client"
)

type Config struct {
	WalletJSON              string `json:"wallet_json"`
	BlockWorker             string `json:"block_worker"`
	ChainID                 string `json:"chain_id"`
	SignatureScheme         string `json:"signature_scheme"`
	MinSubmit               int    `json:"min_submit"`
	MinConfirmation         int    `json:"min_confirmation"`
	ConfirmationChainLength int    `json:"confirmation_chain_length"`
	SharderConsensous       int    `json:"sharder_consensous"`
}

func InitSDKHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	file, err := os.ReadFile("./config.json")
	if err != nil {
		http.Error(w, "Failed to read config.json: "+err.Error(), http.StatusInternalServerError)
		return
	}

	var config Config
	err = json.Unmarshal(file, &config)
	if err != nil {
		http.Error(w, "Invalid config.json: "+err.Error(), http.StatusInternalServerError)
		return
	}

	if config.WalletJSON == "" {
		http.Error(w, "Missing wallet JSON path", http.StatusBadRequest)
		return
	}

	if _, err := os.Stat(config.WalletJSON); os.IsNotExist(err) {
		http.Error(w, "Wallet JSON file not found: "+config.WalletJSON, http.StatusBadRequest)
		return
	}

	walletContent, err := os.ReadFile(config.WalletJSON)
	if err != nil {
		http.Error(w, "Failed to read wallet JSON: "+err.Error(), http.StatusInternalServerError)
		return
	}

	err = client.InitSDK(
		string(walletContent),
		config.BlockWorker,
		config.ChainID,
		config.SignatureScheme,
		0,
		true,
		0,
		config.MinSubmit,
		config.MinConfirmation,
		config.ConfirmationChainLength,
		config.SharderConsensous,
	)
	if err != nil {
		http.Error(w, "SDK initialization failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte(`{"message": "SDK initialized successfully"}`))
}

memfile_handler.go

package handlers

import (
	"bytes"
	"errors"
	"io"
	"os"
	"sync"
	"time"
)

type MemFile struct {
	buffer  *bytes.Buffer
	mu      sync.Mutex
	name    string
	modTime time.Time
	mode    os.FileMode
}

func NewMemFile(name string) *MemFile {
	return &MemFile{
		buffer:  new(bytes.Buffer),
		name:    name,
		modTime: time.Now(),
		mode:    0644,
	}
}

func (m *MemFile) Read(p []byte) (int, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.buffer.Read(p)
}

func (m *MemFile) Write(p []byte) (int, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	return m.buffer.Write(p)
}

func (m *MemFile) Seek(offset int64, whence int) (int64, error) {
	m.mu.Lock()
	defer m.mu.Unlock()
	switch whence {
	case io.SeekStart:
		m.buffer = bytes.NewBuffer(m.buffer.Bytes()[offset:])
		return offset, nil
	case io.SeekEnd:
		m.buffer = bytes.NewBuffer(m.buffer.Bytes()[len(m.buffer.Bytes())+int(offset):])
		return int64(len(m.buffer.Bytes())), nil
	case io.SeekCurrent:
		return 0, errors.New("unsupported seek method")
	}
	return 0, errors.New("invalid seek whence")
}

func (m *MemFile) Close() error {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.buffer.Reset()
	return nil
}

func (m *MemFile) Stat() (os.FileInfo, error) {
	return &memFileInfo{
		name:    m.name,
		size:    int64(m.buffer.Len()),
		modTime: m.modTime,
		mode:    m.mode,
	}, nil
}

func (m *MemFile) Sync() error {
	return nil
}

func (m *MemFile) ToBytes() []byte {
	return m.buffer.Bytes()
}

type memFileInfo struct {
	name    string
	size    int64
	modTime time.Time
	mode    os.FileMode
}

func (fi *memFileInfo) Name() string       { return fi.name }
func (fi *memFileInfo) Size() int64        { return fi.size }
func (fi *memFileInfo) Mode() os.FileMode  { return fi.mode }
func (fi *memFileInfo) ModTime() time.Time { return fi.modTime }
func (fi *memFileInfo) IsDir() bool        { return false }
func (fi *memFileInfo) Sys() interface{}   { return nil }

upload_handler.go

package handlers

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"path/filepath"

	"github.com/0chain/gosdk/constants"
	"github.com/0chain/gosdk/zboxcore/fileref"
	"github.com/0chain/gosdk/zboxcore/sdk"
)

type ShortURLResponse struct {
	AuthTicket string `json:"auth_ticket"`
}

type UploadRequest struct {
	AllocationID     string `json:"allocation_id"`
	LocalPath        string `json:"local_path"`
	Encrypt          bool   `json:"encrypt"`
	WebStreaming     bool   `json:"web_streaming"`
	IsMemoryDownload bool   `json:"download_file"`
}

func UploadFileHandler(w http.ResponseWriter, r *http.Request) {
	var req UploadRequest

	// Decode the incoming JSON payload
	err := json.NewDecoder(r.Body).Decode(&req)
	if err != nil {
		log.Printf("Invalid request payload: %v", err)
		http.Error(w, "Invalid request payload", http.StatusBadRequest)
		return
	}

	log.Printf("Received upload request: %+v", req)

	// Validate required fields
	if req.AllocationID == "" || req.LocalPath == "" {
		log.Printf("Missing required fields: %+v", req)
		http.Error(w, "AllocationID and LocalPath are required", http.StatusBadRequest)
		return
	}

	// Check if the file exists locally
	if _, err := os.Stat(req.LocalPath); os.IsNotExist(err) {
		log.Printf("File not found at: %s", req.LocalPath)
		http.Error(w, "File not found at "+req.LocalPath, http.StatusBadRequest)
		return
	}

	// Generate the remote path using the file name
	fileName := filepath.Base(req.LocalPath)
	remotePath := "/" + fileName
	log.Printf("Generated remote path: %s", remotePath)

	// Get the allocation object using the provided AllocationID
	allocation, err := sdk.GetAllocation(req.AllocationID)
	if err != nil {
		log.Printf("Failed to get allocation: %v", err)
		http.Error(w, "Failed to get allocation: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Open the local file
	fileReader, err := os.Open(req.LocalPath)
	if err != nil {
		log.Printf("Failed to open file: %v", err)
		http.Error(w, "Failed to open file: "+err.Error(), http.StatusInternalServerError)
		return
	}
	defer fileReader.Close()

	// Get file size
	fileInfo, err := fileReader.Stat()
	if err != nil {
		log.Printf("Failed to get file info: %v", err)
		http.Error(w, "Failed to get file info: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Set up the operation for the upload
	operations := []sdk.OperationRequest{
		{
			OperationType: constants.FileOperationInsert,
			FileMeta: sdk.FileMeta{
				Path:       req.LocalPath,
				RemotePath: remotePath,
				RemoteName: fileName,
				ActualSize: fileInfo.Size(),
			},
			FileReader:     fileReader,
			IsWebstreaming: req.WebStreaming,
			IsRepair:       false,
			Opts: func() []sdk.ChunkedUploadOption {
				// Include encryption option only if Encrypt is true
				if req.Encrypt {
					return []sdk.ChunkedUploadOption{
						sdk.WithEncrypt(req.Encrypt),
					}
				}
				return nil
			}(),
		},
	}

	// Perform the multi-operation
	log.Println("Starting DoMultiOperation...")
	err = allocation.DoMultiOperation(operations)
	if err != nil {
		log.Printf("Multi-operation failed: %v", err)
		http.Error(w, "Upload failed: "+err.Error(), http.StatusInternalServerError)
		return
	}

	log.Println("File uploaded successfully to Züs network")

	// Generate Auth Ticket
	//authTicket, err := allocation.GetAuthTicketForShare(remotePath, req.FileName, fileref.FILE, "")
	authTicket, err := allocation.GetAuthTicketForShare(remotePath, fileName, fileref.FILE, "")
	if err != nil {
		log.Printf("Failed to generate auth ticket: %v", err)
		http.Error(w, "Failed to generate auth ticket", http.StatusInternalServerError)
		return
	}
	log.Printf("Auth Ticket: %+v", authTicket)

	// Return short URL
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(ShortURLResponse{
		AuthTicket: authTicket,
	})
}

download_handler.go

package handlers

import (
"encoding/json"
"log"
"net/http"

//"path/filepath"
//"strings"

"github.com/0chain/gosdk/zboxcore/fileref"
"github.com/0chain/gosdk/zboxcore/sdk"
)

type DownloadRequest struct {
AllocationID string `json:"allocation_id"`
FileName     string `json:"filename"`
AuthTicket   string `json:"auth_ticket"`
LocalPath    string `json:"local_path"`
}

func DownloadFileHandler(w http.ResponseWriter, r *http.Request) {
var req DownloadRequest

// Decode the incoming JSON payload
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
log.Printf("Invalid request payload: %v", err)
http.Error(w, "Invalid request payload", http.StatusBadRequest)
return
}
log.Printf("Received download request: %+v", req)

allocation, err := sdk.GetAllocation(req.AllocationID)
if err != nil {
http.Error(w, "Failed to get allocation", http.StatusInternalServerError)
return
}

// Generate the remote path using the file name
remotePath := "/" + req.FileName
log.Printf("Generated remote path: %s", remotePath)

remoteLookupHash := fileref.GetReferenceLookup(req.AllocationID, remotePath)
log.Printf("Generated remoteLookupHash: %s", remoteLookupHash)

// Create in-memory file
//memFile := NewMemFile(remotePath)

// Download file
err = allocation.DownloadFromAuthTicket(
req.LocalPath, req.AuthTicket, remoteLookupHash, remotePath, false, nil, true,
)
if err != nil {
http.Error(w, "File download failed", http.StatusInternalServerError)
return
}

5. Run Your Server

go run main.go

Expected output:

Go microservice running on port 8080

6. Test With curl

Initialize SDK

curl -X POST http://localhost:8080/init-sdk

Upload a File

curl -X POST http://localhost:8080/upload-file \
  -H "Content-Type: application/json" \
  -d '{
    "allocation_id": "your_allocation_id",
    "local_path": "/Users/you/Downloads/file.txt",
    "encrypt": false,
    "web_streaming": false,
    "download_file": false
  }'

Download a File

curl -X POST http://localhost:8080/download-file \
  -H "Content-Type: application/json" \
  -d '{
    "allocation_id": "allocation-id",
    "filename": "filename of file you would like to download from allocation",
    "auth_ticket": "auth-ticket-string",
  "local_path": "/path/to/local/download"
  }' 

Last updated