Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

Commit

Permalink
Define rough architecture for middleware
Browse files Browse the repository at this point in the history
The functionality of the middleware is now split into three packages. The
middleware package takes care of communicating with bitcoind and
lightningd and emits an event if new data is received.

The handlers package takes care of the communication with
the bitbox-wallet-app. It upgrades connections to websocket, if
requested, and starts the main middleware event loop. Events from the
middleware are caught and passed into a websocket handler.

The main package is what is compiled into a binary. It parses command
line arguments and creates new middleware and handler instances.
This commit also implements logging, where each package creates its own
logrus logging instance.
  • Loading branch information
TheCharlatan committed Jun 3, 2019
1 parent 6111778 commit d45f761
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 151 deletions.
4 changes: 2 additions & 2 deletions middleware/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ envinit:
./scripts/go-get.sh v1.16.0 github.com/golangci/golangci-lint/cmd/golangci-lint
go get -u github.com/golang/dep/cmd/dep
native: check-env
go build -o $(REPO_ROOT)/build/base-middleware ./src/
go build -o $(REPO_ROOT)/build/base-middleware ./cmd/middleware/
aarch64: check-env
GOARCH=arm64 go build -o $(REPO_ROOT)/build/base-middleware ./src/
GOARCH=arm64 go build -o $(REPO_ROOT)/build/base-middleware ./cmd/middleware/
39 changes: 39 additions & 0 deletions middleware/cmd/middleware/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Package main provides the entry point into the middleware and accepts command line arguments.
// Once compiled, the application pipes information from bitbox-base backend services to the bitbox-wallet-app and serves as an authenticator to the bitbox-base.
package main

import (
"flag"
"log"
"net/http"

middleware "github.com/digitalbitbox/bitbox-base/middleware/src"
"github.com/digitalbitbox/bitbox-base/middleware/src/handlers"
)

func main() {
bitcoinRPCUser := flag.String("rpcuser", "rpcuser", "Bitcoin rpc user name")
bitcoinRPCPassword := flag.String("rpcpassword", "rpcpassword", "Bitcoin rpc password")
bitcoinRPCPort := flag.String("rpcport", "8332", "Bitcoin rpc port, localhost is assumed as an address")
lightningRPCPath := flag.String("lightning-rpc-path", "/home/bitcoin/.lightning/lightning-rpc", "Path to the lightning rpc unix socket")
flag.Parse()

logBeforeExit := func() {
// Recover from all panics and log error before panicking again.
if r := recover(); r != nil {
// r is of type interface{}, just print its value
log.Printf("%v, error detected, shutting down.", r)
panic(r)
}
}
defer logBeforeExit()
middleware := middleware.NewMiddleware(*bitcoinRPCUser, *bitcoinRPCPassword, *bitcoinRPCPort, *lightningRPCPath)
log.Println("--------------- Started middleware --------------")

handlers := handlers.NewHandlers(middleware)
log.Println("Binding middleware api to port 8845")

if err := http.ListenAndServe(":8845", handlers.Router); err != nil {
log.Println(err.Error() + " Failed to listen for HTTP")
}
}
89 changes: 89 additions & 0 deletions middleware/src/handlers/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Package handlers implements an api for the bitbox-wallet-app to talk to.
package handlers

import (
"fmt"
"log"
"net/http"

middleware "github.com/digitalbitbox/bitbox-base/middleware/src"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
)

// Middleware provides an interface to the middleware package.
type Middleware interface {
Start() <-chan *middleware.SampleInfo
}

// Handlers provides a web api
type Handlers struct {
Router *mux.Router
clients map[*websocket.Conn]bool
//upgrader takes an http request and upgrades the connection with its origin to websocket
upgrader websocket.Upgrader
middleware Middleware
//TODO(TheCharlatan): In future this event should have a generic interface (thus only containing raw json)
middlewareEvents <-chan *middleware.SampleInfo
}

// NewHandlers returns a handler instance.
func NewHandlers(middlewareInstance Middleware) *Handlers {
router := mux.NewRouter()

handlers := &Handlers{
middleware: middlewareInstance,
Router: router,
clients: make(map[*websocket.Conn]bool),
upgrader: websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
},
}
handlers.Router.HandleFunc("/", handlers.rootHandler).Methods("GET")
handlers.Router.HandleFunc("/ws", handlers.wsHandler)

handlers.middlewareEvents = handlers.middleware.Start()
go handlers.echo()
return handlers
}

// TODO(TheCharlatan): Define a better error-response system. In future, this should be the first step in an authentication procedure.
// rootHandler provides an endpoint to indicate that the middleware is online and able to handle requests.
func (handlers *Handlers) rootHandler(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte("OK!!\n"))
if err != nil {
log.Println(err.Error() + " Failed to write response bytes in root handler")
}
}

func (handlers *Handlers) wsHandler(w http.ResponseWriter, r *http.Request) {
ws, err := handlers.upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err.Error() + " Failed to upgrade connection")
}

// register client
handlers.clients[ws] = true
}

func (handlers *Handlers) echo() {
var i = 0
for {
i++
val := <-handlers.middlewareEvents
blockinfo := fmt.Sprintf("%d %f %d %s", val.Blocks, val.Difficulty, i, val.LightningAlias)
// send to every client that is currently connected
//fmt.Println(blockinfo)
for client := range handlers.clients {
log.Println(blockinfo)
err := client.WriteMessage(websocket.TextMessage, []byte(blockinfo))
if err != nil {
log.Println(err.Error() + " Unexpected websocket error")
client.Close()
delete(handlers.clients, client)
}
}
}
}
149 changes: 0 additions & 149 deletions middleware/src/main.go

This file was deleted.

105 changes: 105 additions & 0 deletions middleware/src/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Package middleware emits events with data from services running on the base.
package middleware

import (
"log"
"time"

"github.com/btcsuite/btcd/rpcclient"
lightning "github.com/fiatjaf/lightningd-gjson-rpc"
)

// SampleInfo holds sample information from c-lightning and bitcoind. It is temporary for testing purposes.
type SampleInfo struct {
Blocks int64 `json:"blocks"`
Difficulty float64 `json:"difficulty"`
LightningAlias string `json:"lightning_alias"`
}

// Middleware connects to services on the base with provided parrameters and emits events for the handler.
type Middleware struct {
info SampleInfo
events chan *SampleInfo
bitcoinRPCUser, bitcoinRPCPassword, bitcoinRPCPort string
lightningRPCPath string
}

// NewMiddleware returns a new instance of the middleware
func NewMiddleware(bitcoinRPCUser, bitcoinRPCPassword, bitcoinRPCPort, lightningRPCPath string) *Middleware {
middleware := &Middleware{
events: make(chan *SampleInfo),
bitcoinRPCUser: bitcoinRPCUser,
bitcoinRPCPassword: bitcoinRPCPassword,
bitcoinRPCPort: bitcoinRPCPort,
lightningRPCPath: lightningRPCPath,
info: SampleInfo{
Blocks: 0,
Difficulty: 0.0,
LightningAlias: "disconnected",
},
}

return middleware
}

// demoBitcoinRPC is a function that demonstrates a connection to bitcoind. Currently it gets the blockcount and difficulty and writes it into the SampleInfo.
func (middleware *Middleware) demoBitcoinRPC(bitcoinRPCUser, bitcoinRPCPassword, bitcoinRPCPort string) {
connCfg := rpcclient.ConnConfig{
HTTPPostMode: true,
DisableTLS: true,
Host: "127.0.0.1:" + bitcoinRPCPort,
User: bitcoinRPCUser,
Pass: bitcoinRPCPassword,
}
client, err := rpcclient.New(&connCfg, nil)
if err != nil {
log.Println(err.Error() + " Failed to create new bitcoind rpc client")
}
//client is shutdown/deconstructed again as soon as this function returns
defer client.Shutdown()

//Get current block count.
var blockCount int64
blockCount, err = client.GetBlockCount()
if err != nil {
log.Println(err.Error() + " No blockcount received")
} else {
middleware.info.Blocks = blockCount
}
blockChainInfo, err := client.GetBlockChainInfo()
if err != nil {
log.Println(err.Error() + " GetBlockChainInfo rpc call failed")
} else {
middleware.info.Difficulty = blockChainInfo.Difficulty
}

}

// demoCLightningRPC demonstrates a connection with lightnind. Currently it gets the lightningd alias and writes it into the SampleInfo.
func (middleware *Middleware) demoCLightningRPC(lightningRPCPath string) {
ln := &lightning.Client{
Path: lightningRPCPath,
}

nodeinfo, err := ln.Call("getinfo")
if err != nil {
log.Println(err.Error() + " Lightningd getinfo called failed.")
} else {
middleware.info.LightningAlias = nodeinfo.Get("alias").String()
}
}

func (middleware *Middleware) rpcLoop() {
for {
middleware.demoBitcoinRPC(middleware.bitcoinRPCUser, middleware.bitcoinRPCPassword, middleware.bitcoinRPCPort)
middleware.demoCLightningRPC(middleware.lightningRPCPath)
middleware.events <- &middleware.info
time.Sleep(5 * time.Second)
}
}

// Start gives a trigger for the handler to start the rpc event loop
func (middleware *Middleware) Start() <-chan *SampleInfo {
go middleware.rpcLoop()
return middleware.events
}

0 comments on commit d45f761

Please sign in to comment.