feat(cmd)_: status-backend

This commit is contained in:
Igor Sirotin 2024-09-18 23:41:13 +01:00
parent 946ee4e496
commit 6696e6fb9e
No known key found for this signature in database
GPG Key ID: 425E227CAAB81F95
7 changed files with 386 additions and 12 deletions

View File

@ -0,0 +1,29 @@
package main
import (
"flag"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/cmd/statusd/server"
)
var (
address = flag.String("address", "", "host:port to listen")
logger = log.New("package", "status-go/cmd/status-backend")
)
func main() {
srv := server.NewServer()
srv.Setup()
err := srv.Listen(*address)
if err != nil {
logger.Error("failed to start server", "error", err)
return
}
log.Info("server started", "address", srv.Address())
srv.RegisterMobileAPI()
srv.Serve()
}

View File

@ -178,6 +178,7 @@ func main() {
logger.Error("failed to start server", "error", err) logger.Error("failed to start server", "error", err)
return return
} }
go srv.Serve()
log.Info("server started", "address", srv.Address()) log.Info("server started", "address", srv.Address())
defer func() { defer func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)

View File

@ -0,0 +1,101 @@
package server
import "github.com/status-im/status-go/mobile"
var EndpointsWithResponse = []func(string) string {
statusgo.InitializeApplication,
statusgo.ExtractGroupMembershipSignatures,
statusgo.SignGroupMembership,
statusgo.ValidateNodeConfig,
statusgo.CallRPC,
statusgo.CallPrivateRPC,
statusgo.CreateAccountAndLogin,
statusgo.LoginAccount,
statusgo.RestoreAccountAndLogin,
statusgo.InitKeystore,
statusgo.SignMessage,
statusgo.HashTypedData,
statusgo.HashTypedDataV4,
statusgo.Recover,
statusgo.HashTransaction,
statusgo.HashMessage,
statusgo.StartCPUProfile,
statusgo.WriteHeapProfile,
statusgo.AddPeer,
statusgo.SignHash,
statusgo.GenerateAlias,
statusgo.IsAlias,
statusgo.Identicon,
statusgo.EmojiHash,
statusgo.ColorHash,
statusgo.ColorID,
statusgo.ValidateMnemonic,
statusgo.DecompressPublicKey,
statusgo.CompressPublicKey,
statusgo.SerializeLegacyKey,
statusgo.GetPasswordStrength,
statusgo.GetPasswordStrengthScore,
statusgo.GetConnectionStringForBeingBootstrapped,
statusgo.GetConnectionStringForBootstrappingAnotherDevice,
statusgo.GetConnectionStringForExportingKeypairsKeystores,
statusgo.ValidateConnectionString,
statusgo.DecodeParameters,
statusgo.HexToNumber,
statusgo.NumberToHex,
statusgo.Sha3,
statusgo.Utf8ToHex,
statusgo.HexToUtf8,
statusgo.CheckAddressChecksum,
statusgo.IsAddress,
statusgo.ToChecksumAddress,
statusgo.DeserializeAndCompressKey,
statusgo.InitLogging,
statusgo.ToggleCentralizedMetrics,
statusgo.AddCentralizedMetric,
}
var EndpointsNoRequest = []func() string {
statusgo.GetNodeConfig,
statusgo.ResetChainData,
statusgo.Logout,
statusgo.StopCPUProfiling,
statusgo.StartLocalNotifications,
statusgo.StopLocalNotifications,
statusgo.ExportNodeLogs,
statusgo.ImageServerTLSCert,
statusgo.Fleets,
statusgo.LocalPairingPreflightOutboundCheck,
statusgo.StartSearchForLocalPairingPeers,
statusgo.GetRandomMnemonic,
statusgo.CentralizedMetricsInfo,
}
var EndpointsUnsupported = []string {
"VerifyAccountPassword",
"VerifyDatabasePassword",
"MigrateKeyStoreDir",
"DeleteMultiaccount",
"DeleteImportedKey",
"SignTypedData",
"SignTypedDataV4",
"SendTransactionWithChainID",
"SendTransaction",
"SendTransactionWithSignature",
"ConnectionChange",
"AppStateChange",
"SetMobileSignalHandler",
"SetSignalEventCallback",
"MultiformatSerializePublicKey",
"MultiformatDeserializePublicKey",
"ExportUnencryptedDatabase",
"ImportUnencryptedDatabase",
"ChangeDatabasePassword",
"ConvertToKeycardAccount",
"ConvertToRegularAccount",
"SwitchFleet",
"InputConnectionStringForBootstrapping",
"InputConnectionStringForBootstrappingAnotherDevice",
"InputConnectionStringForImportingKeypairsKeystores",
"EncodeTransfer",
"EncodeFunctionCall",
}

View File

@ -0,0 +1,21 @@
package server
import "github.com/status-im/status-go/mobile"
var EndpointsWithResponse = []func(string) string {
{{- range .FunctionsWithResp }}
{{ $.PackageName }}.{{ . }},
{{- end }}
}
var EndpointsNoRequest = []func() string {
{{- range .FunctionsNoArgs }}
{{ $.PackageName }}.{{ . }},
{{- end }}
}
var EndpointsUnsupported = []string {
{{- range .UnsupportedEndpoints }}
"{{ . }}",
{{- end }}
}

View File

@ -0,0 +1,148 @@
//go:generate go run main.go
package main
import (
"bufio"
"fmt"
"os"
"regexp"
"strings"
"text/template"
)
const (
inputFilePath = "../../../../mobile/status.go"
templateFilePath = "./endpoints_template.txt"
outputFilePath = "../endpoints.go"
)
var (
// Regular expressions extracted as global variables
publicFunc = regexp.MustCompile(`func\s+([A-Z]\w+)\(.*\).*\{`)
publicFuncWithRespPattern = regexp.MustCompile(`^func\s+([A-Z]\w*)\((\w|\s)+\)\s+string\s+\{$`)
publicFuncNoArgsPattern = regexp.MustCompile(`^func\s+([A-Z]\w*)\(\)\s+string\s+\{$`)
funcNamePattern = regexp.MustCompile(`^func\s+([A-Z]\w*)\(`)
)
type TemplateData struct {
PackageName string
FunctionsWithResp []string
FunctionsNoArgs []string
UnsupportedEndpoints []string
}
func main() {
// Open the Go source file
file, err := os.Open(inputFilePath)
if err != nil {
fmt.Printf("Failed to open file: %s\n", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
var publicFunctionsWithResp []string
var publicFunctionsNoArgs []string
var unsupportedFunctions []string
var isDeprecated bool
var packageName string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// Detect package name
if strings.HasPrefix(line, "package ") {
packageName = strings.TrimPrefix(line, "package ")
}
// Check for deprecation comment
if isDeprecatedComment(line) {
isDeprecated = true
continue
}
if !publicFunc.MatchString(line) {
continue
}
if isDeprecated {
isDeprecated = false
continue
}
functionName := extractFunctionName(line)
switch {
case isPublicFunctionWithResp(line):
publicFunctionsWithResp = append(publicFunctionsWithResp, functionName)
continue
case isPublicFunctionNoArgs(line):
publicFunctionsNoArgs = append(publicFunctionsNoArgs, functionName)
continue
default:
unsupportedFunctions = append(unsupportedFunctions, functionName)
}
}
if err := scanner.Err(); err != nil {
fmt.Printf("Error reading file: %s\n", err)
return
}
// Prepare the template data
data := TemplateData{
PackageName: packageName,
FunctionsWithResp: publicFunctionsWithResp,
FunctionsNoArgs: publicFunctionsNoArgs,
UnsupportedEndpoints: unsupportedFunctions,
}
// Load and parse the template
tmpl, err := template.ParseFiles(templateFilePath)
if err != nil {
fmt.Printf("Failed to parse template file: %s\n", err)
return
}
// Create the output file
outputFile, err := os.Create(outputFilePath)
if err != nil {
fmt.Printf("Failed to create output file: %s\n", err)
return
}
defer outputFile.Close()
// Execute the template and write the result to the output file
err = tmpl.Execute(outputFile, data)
if err != nil {
fmt.Printf("Failed to execute template: %s\n", err)
return
}
fmt.Println("Generated endpoints file:", outputFilePath)
}
// Function to check if a line contains a public function with a response of string
func isPublicFunctionWithResp(line string) bool {
return publicFuncWithRespPattern.MatchString(line)
}
// Function to check if a line contains a public function with not arguments and a response of string
func isPublicFunctionNoArgs(line string) bool {
return publicFuncNoArgsPattern.MatchString(line)
}
// Function to extract the public function name from a line
func extractFunctionName(line string) string {
matches := funcNamePattern.FindStringSubmatch(line)
if len(matches) > 1 {
return matches[1]
}
return ""
}
// Function to check if a comment indicates a deprecated function
func isDeprecatedComment(line string) bool {
return strings.Contains(line, "// Deprecated:")
}

View File

@ -3,8 +3,13 @@ package server
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"io"
"net" "net"
"net/http" "net/http"
"reflect"
"runtime"
"strings"
"sync" "sync"
"time" "time"
@ -17,6 +22,8 @@ import (
type Server struct { type Server struct {
server *http.Server server *http.Server
listener net.Listener
mux *http.ServeMux
lock sync.Mutex lock sync.Mutex
connections map[*websocket.Conn]struct{} connections map[*websocket.Conn]struct{}
address string address string
@ -58,27 +65,28 @@ func (s *Server) Listen(address string) error {
ReadHeaderTimeout: 5 * time.Second, ReadHeaderTimeout: 5 * time.Second,
} }
mux := http.NewServeMux() s.mux = http.NewServeMux()
mux.HandleFunc("/signals", s.signals) s.mux.HandleFunc("/signals", s.signals)
s.server.Handler = mux s.server.Handler = s.mux
listener, err := net.Listen("tcp", address) var err error
s.listener, err = net.Listen("tcp", address)
if err != nil { if err != nil {
return err return err
} }
s.address = listener.Addr().String() s.address = s.listener.Addr().String()
go func() {
err := s.server.Serve(listener)
if !errors.Is(err, http.ErrServerClosed) {
log.Error("signals server closed with error: %w", err)
}
}()
return nil return nil
} }
func (s *Server) Serve() {
err := s.server.Serve(s.listener)
if !errors.Is(err, http.ErrServerClosed) {
log.Error("signals server closed with error: %w", err)
}
}
func (s *Server) Stop(ctx context.Context) { func (s *Server) Stop(ctx context.Context) {
for connection := range s.connections { for connection := range s.connections {
err := connection.Close() err := connection.Close()
@ -115,3 +123,68 @@ func (s *Server) signals(w http.ResponseWriter, r *http.Request) {
s.connections[connection] = struct{}{} s.connections[connection] = struct{}{}
} }
func (s *Server) addEndpointWithResponse(handler func(string) string) {
endpoint := endpointName(functionName(handler))
log.Info("adding endpoint", "name", endpoint)
s.mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
request, err := io.ReadAll(r.Body)
if err != nil {
log.Error("failed to read request: %w", err)
return
}
response := handler(string(request))
_, err = w.Write([]byte(response))
if err != nil {
log.Error("failed to write response: %w", err)
}
})
}
func (s *Server) addEndpointNoRequest(handler func() string) {
endpoint := endpointName(functionName(handler))
log.Info("adding endpoint", "name", endpoint)
s.mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
response := handler()
_, err := w.Write([]byte(response))
if err != nil {
log.Error("failed to write response: %w", err)
}
})
}
func (s *Server) addUnsupportedEndpoint(name string) {
endpoint := endpointName(name)
log.Info("marking unsupported endpoint", "name", endpoint)
s.mux.HandleFunc(endpoint, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotImplemented)
})
}
func (s *Server) RegisterMobileAPI() {
for _, endpoint := range EndpointsWithResponse {
s.addEndpointWithResponse(endpoint)
}
for _, endpoint := range EndpointsNoRequest {
s.addEndpointNoRequest(endpoint)
}
for _, endpoint := range EndpointsUnsupported {
s.addUnsupportedEndpoint(endpoint)
}
}
func functionName(fn any) string {
fullName := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
parts := strings.Split(fullName, "/")
lastPart := parts[len(parts)-1]
nameParts := strings.Split(lastPart, ".")
return nameParts[len(nameParts)-1]
}
func endpointName(functionName string) string {
const base = "statusgo"
return fmt.Sprintf("/%s/%s", base, functionName)
}

View File

@ -99,6 +99,7 @@ func initializeApplication(requestJSON string) string {
return string(data) return string(data)
} }
// Deprecated: Use InitializeApplication instead.
func OpenAccounts(datadir string) string { func OpenAccounts(datadir string) string {
return logAndCallString(openAccounts, datadir) return logAndCallString(openAccounts, datadir)
} }