Add AdminService (#176)

* add admin service

* go mod tidy

* fix linter: grpc.Dial

* fix ocean get balance

* fix linter

* add .vscode to gitignore

* rework admin balance API

* fix mockedwallet in covenantless pkg

* make proto
This commit is contained in:
Louis Singer
2024-05-31 15:46:46 +02:00
committed by GitHub
parent 329ba555db
commit 9fc49d9f08
27 changed files with 2510 additions and 73 deletions

View File

@@ -7,11 +7,21 @@ import (
)
type Config struct {
Port uint32
NoTLS bool
Port uint32
NoTLS bool
AuthUser string
AuthPass string
}
func (c Config) Validate() error {
if len(c.AuthUser) == 0 {
return fmt.Errorf("missing auth user")
}
if len(c.AuthPass) == 0 {
return fmt.Errorf("missing auth password")
}
lis, err := net.Listen("tcp", c.address())
if err != nil {
return fmt.Errorf("invalid port: %s", err)

View File

@@ -0,0 +1,121 @@
package handlers
import (
"context"
"fmt"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/internal/core/application"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type adminHandler struct {
adminService application.AdminService
}
func NewAdminHandler(adminService application.AdminService) arkv1.AdminServiceServer {
return &adminHandler{adminService}
}
func (a *adminHandler) GetBalance(ctx context.Context, _ *arkv1.GetBalanceRequest) (*arkv1.GetBalanceResponse, error) {
balance, err := a.adminService.GetBalance(ctx)
if err != nil {
return nil, err
}
return &arkv1.GetBalanceResponse{
MainAccount: &arkv1.Balance{
Locked: convertSatoshis(balance.MainAccountBalance.Locked),
Available: convertSatoshis(balance.MainAccountBalance.Available),
},
ConnectorsAccount: &arkv1.Balance{
Locked: convertSatoshis(balance.ConnectorsAccountBalance.Locked),
Available: convertSatoshis(balance.ConnectorsAccountBalance.Available),
},
}, nil
}
func (a *adminHandler) GetRoundDetails(ctx context.Context, req *arkv1.GetRoundDetailsRequest) (*arkv1.GetRoundDetailsResponse, error) {
id := req.GetRoundId()
if len(id) == 0 {
return nil, status.Error(codes.InvalidArgument, "missing round id")
}
details, err := a.adminService.GetRoundDetails(ctx, id)
if err != nil {
return nil, err
}
return &arkv1.GetRoundDetailsResponse{
RoundId: details.RoundId,
Txid: details.TxId,
ForfeitedAmount: convertSatoshis(details.ForfeitedAmount),
TotalVtxosAmount: convertSatoshis(details.TotalVtxosAmount),
TotalExitAmount: convertSatoshis(details.TotalExitAmount),
FeesAmount: convertSatoshis(details.FeesAmount),
InputsVtxos: details.InputsVtxos,
OutputsVtxos: details.OutputsVtxos,
ExitAddresses: details.ExitAddresses,
}, nil
}
// GetRounds implements arkv1.AdminServiceServer.
func (a *adminHandler) GetRounds(ctx context.Context, req *arkv1.GetRoundsRequest) (*arkv1.GetRoundsResponse, error) {
startAfter := req.GetAfter()
startBefore := req.GetBefore()
if startAfter < 0 {
return nil, status.Error(codes.InvalidArgument, "invalid after (must be >= 0)")
}
if startBefore < 0 {
return nil, status.Error(codes.InvalidArgument, "invalid before (must be >= 0)")
}
if startAfter >= startBefore {
return nil, status.Error(codes.InvalidArgument, "invalid range")
}
rounds, err := a.adminService.GetRounds(ctx, startAfter, startBefore)
if err != nil {
return nil, err
}
return &arkv1.GetRoundsResponse{Rounds: rounds}, nil
}
func (a *adminHandler) GetScheduledSweep(ctx context.Context, _ *arkv1.GetScheduledSweepRequest) (*arkv1.GetScheduledSweepResponse, error) {
scheduledSweeps, err := a.adminService.GetScheduledSweeps(ctx)
if err != nil {
return nil, err
}
sweeps := make([]*arkv1.ScheduledSweep, 0)
for _, sweep := range scheduledSweeps {
outputs := make([]*arkv1.SweepableOutput, 0)
for _, output := range sweep.SweepableOutputs {
outputs = append(outputs, &arkv1.SweepableOutput{
Txid: output.TxId,
Vout: output.Vout,
ScheduledAt: output.ScheduledAt,
Amount: convertSatoshis(output.Amount),
})
}
sweeps = append(sweeps, &arkv1.ScheduledSweep{
RoundId: sweep.RoundId,
Outputs: outputs,
})
}
return &arkv1.GetScheduledSweepResponse{Sweeps: sweeps}, nil
}
// convert sats to string BTC
func convertSatoshis(sats uint64) string {
btc := float64(sats) * 1e-8
return fmt.Sprintf("%.8f", btc)
}

View File

@@ -0,0 +1,42 @@
package interceptors
import (
"context"
"encoding/base64"
"fmt"
"strings"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
func unaryAuthenticator(user, pass string) grpc.UnaryServerInterceptor {
adminToken := fmt.Sprintf("%s:%s", user, pass)
adminTokenEncoded := base64.StdEncoding.EncodeToString([]byte(adminToken))
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
// whitelist the ArkService
if strings.Contains(info.FullMethod, arkv1.ArkService_ServiceDesc.ServiceName) {
return handler(ctx, req)
}
token, err := grpc_auth.AuthFromMD(ctx, "basic")
if err != nil {
return nil, status.Errorf(codes.Unauthenticated, "no basic header found: %v", err)
}
if token != adminTokenEncoded {
return nil, status.Errorf(codes.Unauthenticated, "invalid auth credentials: %v", err)
}
return handler(ctx, req)
}
}

View File

@@ -6,8 +6,11 @@ import (
)
// UnaryInterceptor returns the unary interceptor
func UnaryInterceptor() grpc.ServerOption {
return grpc.UnaryInterceptor(middleware.ChainUnaryServer(unaryLogger))
func UnaryInterceptor(user, pass string) grpc.ServerOption {
return grpc.UnaryInterceptor(middleware.ChainUnaryServer(
unaryAuthenticator(user, pass),
unaryLogger,
))
}
// StreamInterceptor returns the stream interceptor with a logrus log

View File

@@ -40,7 +40,8 @@ func NewService(
}
grpcConfig := []grpc.ServerOption{
interceptors.UnaryInterceptor(), interceptors.StreamInterceptor(),
interceptors.UnaryInterceptor(svcConfig.AuthUser, svcConfig.AuthPass),
interceptors.StreamInterceptor(),
}
if !svcConfig.NoTLS {
return nil, fmt.Errorf("tls termination not supported yet")
@@ -53,8 +54,13 @@ func NewService(
// Server grpc.
grpcServer := grpc.NewServer(grpcConfig...)
appHandler := handlers.NewHandler(appConfig.AppService())
arkv1.RegisterArkServiceServer(grpcServer, appHandler)
adminHandler := handlers.NewAdminHandler(appConfig.AdminService())
arkv1.RegisterAdminServiceServer(grpcServer, adminHandler)
healthHandler := handlers.NewHealthHandler()
grpchealth.RegisterHealthServer(grpcServer, healthHandler)
@@ -91,6 +97,11 @@ func NewService(
); err != nil {
return nil, err
}
if err := arkv1.RegisterAdminServiceHandler(
ctx, gwmux, conn,
); err != nil {
return nil, err
}
grpcGateway := http.Handler(gwmux)
handler := router(grpcServer, grpcGateway)