mirror of
https://github.com/aljazceru/ark.git
synced 2025-12-17 20:24:21 +01:00
Rename folders (#97)
* Rename arkd folder & drop cli * Rename ark cli folder & update docs * Update readme * Fix * scripts: add build-all * Add target to build cli for all platforms * Update build scripts --------- Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
0d8c7bffb2
commit
dc00d60585
46
server/internal/interface/grpc/config.go
Normal file
46
server/internal/interface/grpc/config.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package grpcservice
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port uint32
|
||||
NoTLS bool
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
lis, err := net.Listen("tcp", c.address())
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port: %s", err)
|
||||
}
|
||||
defer lis.Close()
|
||||
|
||||
if !c.NoTLS {
|
||||
return fmt.Errorf("tls termination not supported yet")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Config) insecure() bool {
|
||||
return c.NoTLS
|
||||
}
|
||||
|
||||
func (c Config) address() string {
|
||||
return fmt.Sprintf(":%d", c.Port)
|
||||
}
|
||||
|
||||
func (c Config) listener() net.Listener {
|
||||
lis, _ := net.Listen("tcp", c.address())
|
||||
|
||||
if c.insecure() {
|
||||
return lis
|
||||
}
|
||||
return tls.NewListener(lis, c.tlsConfig())
|
||||
}
|
||||
|
||||
func (c Config) tlsConfig() *tls.Config {
|
||||
return nil
|
||||
}
|
||||
305
server/internal/interface/grpc/handlers/arkservice.go
Normal file
305
server/internal/interface/grpc/handlers/arkservice.go
Normal file
@@ -0,0 +1,305 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/common/tree"
|
||||
"github.com/ark-network/ark/internal/core/application"
|
||||
"github.com/ark-network/ark/internal/core/domain"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/google/uuid"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type listener struct {
|
||||
id string
|
||||
ch chan *arkv1.GetEventStreamResponse
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
svc application.Service
|
||||
|
||||
listenersLock *sync.Mutex
|
||||
listeners []*listener
|
||||
}
|
||||
|
||||
func NewHandler(service application.Service) arkv1.ArkServiceServer {
|
||||
h := &handler{
|
||||
svc: service,
|
||||
listenersLock: &sync.Mutex{},
|
||||
listeners: make([]*listener, 0),
|
||||
}
|
||||
|
||||
go h.listenToEvents()
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *handler) Ping(ctx context.Context, req *arkv1.PingRequest) (*arkv1.PingResponse, error) {
|
||||
if req.GetPaymentId() == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing payment id")
|
||||
}
|
||||
|
||||
if err := h.svc.UpdatePaymentStatus(ctx, req.GetPaymentId()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.PingResponse{}, nil
|
||||
}
|
||||
|
||||
func (h *handler) RegisterPayment(ctx context.Context, req *arkv1.RegisterPaymentRequest) (*arkv1.RegisterPaymentResponse, error) {
|
||||
vtxosKeys := make([]domain.VtxoKey, 0, len(req.GetInputs()))
|
||||
for _, input := range req.GetInputs() {
|
||||
vtxosKeys = append(vtxosKeys, domain.VtxoKey{
|
||||
Txid: input.GetTxid(),
|
||||
VOut: input.GetVout(),
|
||||
})
|
||||
}
|
||||
|
||||
id, err := h.svc.SpendVtxos(ctx, vtxosKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.RegisterPaymentResponse{
|
||||
Id: id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *handler) ClaimPayment(ctx context.Context, req *arkv1.ClaimPaymentRequest) (*arkv1.ClaimPaymentResponse, error) {
|
||||
receivers, err := parseReceivers(req.GetOutputs())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
if err := h.svc.ClaimVtxos(ctx, req.GetId(), receivers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.ClaimPaymentResponse{}, nil
|
||||
}
|
||||
|
||||
func (h *handler) FinalizePayment(ctx context.Context, req *arkv1.FinalizePaymentRequest) (*arkv1.FinalizePaymentResponse, error) {
|
||||
forfeitTxs, err := parseTxs(req.GetSignedForfeitTxs())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
if err := h.svc.SignVtxos(ctx, forfeitTxs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.FinalizePaymentResponse{}, nil
|
||||
}
|
||||
|
||||
func (h *handler) Faucet(ctx context.Context, req *arkv1.FaucetRequest) (*arkv1.FaucetResponse, error) {
|
||||
_, pubkey, _, err := parseAddress(req.GetAddress())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
if err := h.svc.FaucetVtxos(ctx, pubkey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.FaucetResponse{}, nil
|
||||
}
|
||||
|
||||
func (h *handler) GetRound(ctx context.Context, req *arkv1.GetRoundRequest) (*arkv1.GetRoundResponse, error) {
|
||||
if len(req.GetTxid()) <= 0 {
|
||||
return nil, status.Error(codes.InvalidArgument, "missing pool txid")
|
||||
}
|
||||
|
||||
round, err := h.svc.GetRoundByTxid(ctx, req.GetTxid())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.GetRoundResponse{
|
||||
Round: &arkv1.Round{
|
||||
Id: round.Id,
|
||||
Start: round.StartingTimestamp,
|
||||
End: round.EndingTimestamp,
|
||||
Txid: round.Txid,
|
||||
CongestionTree: castCongestionTree(round.CongestionTree),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *handler) GetEventStream(_ *arkv1.GetEventStreamRequest, stream arkv1.ArkService_GetEventStreamServer) error {
|
||||
listener := &listener{
|
||||
id: uuid.NewString(),
|
||||
ch: make(chan *arkv1.GetEventStreamResponse),
|
||||
}
|
||||
|
||||
defer h.removeListener(listener.id)
|
||||
defer close(listener.ch)
|
||||
|
||||
h.pushListener(listener)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-stream.Context().Done():
|
||||
return nil
|
||||
|
||||
case ev := <-listener.ch:
|
||||
if err := stream.Send(ev); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch ev.Event.(type) {
|
||||
case *arkv1.GetEventStreamResponse_RoundFinalized, *arkv1.GetEventStreamResponse_RoundFailed:
|
||||
if err := stream.Send(ev); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) ListVtxos(ctx context.Context, req *arkv1.ListVtxosRequest) (*arkv1.ListVtxosResponse, error) {
|
||||
hrp, userPubkey, aspPubkey, err := parseAddress(req.GetAddress())
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
||||
vtxos, err := h.svc.ListVtxos(ctx, userPubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.ListVtxosResponse{
|
||||
Vtxos: vtxoList(vtxos).toProto(hrp, aspPubkey),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *handler) GetPubkey(ctx context.Context, req *arkv1.GetPubkeyRequest) (*arkv1.GetPubkeyResponse, error) {
|
||||
pubkey, err := h.svc.GetPubkey(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &arkv1.GetPubkeyResponse{
|
||||
Pubkey: pubkey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *handler) pushListener(l *listener) {
|
||||
h.listenersLock.Lock()
|
||||
defer h.listenersLock.Unlock()
|
||||
|
||||
h.listeners = append(h.listeners, l)
|
||||
}
|
||||
|
||||
func (h *handler) removeListener(id string) {
|
||||
h.listenersLock.Lock()
|
||||
defer h.listenersLock.Unlock()
|
||||
|
||||
for i, listener := range h.listeners {
|
||||
if listener.id == id {
|
||||
h.listeners = append(h.listeners[:i], h.listeners[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// listenToEvents forwards events from the application layer to the set of listeners
|
||||
func (h *handler) listenToEvents() {
|
||||
channel := h.svc.GetEventsChannel(context.Background())
|
||||
for event := range channel {
|
||||
var ev *arkv1.GetEventStreamResponse
|
||||
|
||||
switch e := event.(type) {
|
||||
case domain.RoundFinalizationStarted:
|
||||
ev = &arkv1.GetEventStreamResponse{
|
||||
Event: &arkv1.GetEventStreamResponse_RoundFinalization{
|
||||
RoundFinalization: &arkv1.RoundFinalizationEvent{
|
||||
Id: e.Id,
|
||||
PoolPartialTx: e.PoolTx,
|
||||
CongestionTree: castCongestionTree(e.CongestionTree),
|
||||
ForfeitTxs: e.UnsignedForfeitTxs,
|
||||
},
|
||||
},
|
||||
}
|
||||
case domain.RoundFinalized:
|
||||
ev = &arkv1.GetEventStreamResponse{
|
||||
Event: &arkv1.GetEventStreamResponse_RoundFinalized{
|
||||
RoundFinalized: &arkv1.RoundFinalizedEvent{
|
||||
Id: e.Id,
|
||||
PoolTxid: e.Txid,
|
||||
},
|
||||
},
|
||||
}
|
||||
case domain.RoundFailed:
|
||||
ev = &arkv1.GetEventStreamResponse{
|
||||
Event: &arkv1.GetEventStreamResponse_RoundFailed{
|
||||
RoundFailed: &arkv1.RoundFailed{
|
||||
Id: e.Id,
|
||||
Reason: e.Err,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if ev != nil {
|
||||
for _, listener := range h.listeners {
|
||||
listener.ch <- ev
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type vtxoList []domain.Vtxo
|
||||
|
||||
func (v vtxoList) toProto(hrp string, aspKey *secp256k1.PublicKey) []*arkv1.Vtxo {
|
||||
list := make([]*arkv1.Vtxo, 0, len(v))
|
||||
for _, vv := range v {
|
||||
addr := vv.OnchainAddress
|
||||
if vv.Pubkey != "" {
|
||||
buf, _ := hex.DecodeString(vv.Pubkey)
|
||||
key, _ := secp256k1.ParsePubKey(buf)
|
||||
addr, _ = common.EncodeAddress(hrp, key, aspKey)
|
||||
}
|
||||
list = append(list, &arkv1.Vtxo{
|
||||
Outpoint: &arkv1.Input{
|
||||
Txid: vv.Txid,
|
||||
Vout: vv.VOut,
|
||||
},
|
||||
Receiver: &arkv1.Output{
|
||||
Address: addr,
|
||||
Amount: vv.Amount,
|
||||
},
|
||||
PoolTxid: vv.PoolTx,
|
||||
Spent: vv.Spent,
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// castCongestionTree converts a tree.CongestionTree to a repeated arkv1.TreeLevel
|
||||
func castCongestionTree(congestionTree tree.CongestionTree) *arkv1.Tree {
|
||||
levels := make([]*arkv1.TreeLevel, 0, len(congestionTree))
|
||||
for _, level := range congestionTree {
|
||||
levelProto := &arkv1.TreeLevel{
|
||||
Nodes: make([]*arkv1.Node, 0, len(level)),
|
||||
}
|
||||
|
||||
for _, node := range level {
|
||||
levelProto.Nodes = append(levelProto.Nodes, &arkv1.Node{
|
||||
Txid: node.Txid,
|
||||
Tx: node.Tx,
|
||||
ParentTxid: node.ParentTxid,
|
||||
})
|
||||
}
|
||||
|
||||
levels = append(levels, levelProto)
|
||||
}
|
||||
return &arkv1.Tree{
|
||||
Levels: levels,
|
||||
}
|
||||
}
|
||||
64
server/internal/interface/grpc/handlers/utils.go
Normal file
64
server/internal/interface/grpc/handlers/utils.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
"github.com/ark-network/ark/common"
|
||||
"github.com/ark-network/ark/internal/core/domain"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/vulpemventures/go-elements/address"
|
||||
"github.com/vulpemventures/go-elements/psetv2"
|
||||
)
|
||||
|
||||
func parseTxs(txs []string) ([]string, error) {
|
||||
if len(txs) <= 0 {
|
||||
return nil, fmt.Errorf("missing list of forfeit txs")
|
||||
}
|
||||
for _, tx := range txs {
|
||||
if _, err := psetv2.NewPsetFromBase64(tx); err != nil {
|
||||
return nil, fmt.Errorf("invalid tx format")
|
||||
}
|
||||
}
|
||||
return txs, nil
|
||||
}
|
||||
|
||||
func parseAddress(addr string) (string, *secp256k1.PublicKey, *secp256k1.PublicKey, error) {
|
||||
if len(addr) <= 0 {
|
||||
return "", nil, nil, fmt.Errorf("missing address")
|
||||
}
|
||||
return common.DecodeAddress(addr)
|
||||
}
|
||||
|
||||
func parseReceivers(outs []*arkv1.Output) ([]domain.Receiver, error) {
|
||||
receivers := make([]domain.Receiver, 0, len(outs))
|
||||
for _, out := range outs {
|
||||
if out.GetAmount() == 0 {
|
||||
return nil, fmt.Errorf("missing output amount")
|
||||
}
|
||||
if len(out.GetAddress()) <= 0 {
|
||||
return nil, fmt.Errorf("missing output address")
|
||||
}
|
||||
var pubkey, addr string
|
||||
_, pk, _, err := common.DecodeAddress(out.GetAddress())
|
||||
if err != nil {
|
||||
if _, err := address.ToOutputScript(out.GetAddress()); err != nil {
|
||||
return nil, fmt.Errorf("invalid output address: unknown format")
|
||||
}
|
||||
if isConf, _ := address.IsConfidential(out.GetAddress()); isConf {
|
||||
return nil, fmt.Errorf("invalid output address: must be unconfidential")
|
||||
}
|
||||
addr = out.GetAddress()
|
||||
}
|
||||
if pk != nil {
|
||||
pubkey = hex.EncodeToString(pk.SerializeCompressed())
|
||||
}
|
||||
receivers = append(receivers, domain.Receiver{
|
||||
Pubkey: pubkey,
|
||||
Amount: out.GetAmount(),
|
||||
OnchainAddress: addr,
|
||||
})
|
||||
}
|
||||
return receivers, nil
|
||||
}
|
||||
16
server/internal/interface/grpc/interceptors/interceptor.go
Normal file
16
server/internal/interface/grpc/interceptors/interceptor.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package interceptors
|
||||
|
||||
import (
|
||||
middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// UnaryInterceptor returns the unary interceptor
|
||||
func UnaryInterceptor() grpc.ServerOption {
|
||||
return grpc.UnaryInterceptor(middleware.ChainUnaryServer(unaryLogger))
|
||||
}
|
||||
|
||||
// StreamInterceptor returns the stream interceptor with a logrus log
|
||||
func StreamInterceptor() grpc.ServerOption {
|
||||
return grpc.StreamInterceptor(middleware.ChainStreamServer(streamLogger))
|
||||
}
|
||||
28
server/internal/interface/grpc/interceptors/logger.go
Normal file
28
server/internal/interface/grpc/interceptors/logger.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package interceptors
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func unaryLogger(
|
||||
ctx context.Context,
|
||||
req interface{},
|
||||
info *grpc.UnaryServerInfo,
|
||||
handler grpc.UnaryHandler,
|
||||
) (interface{}, error) {
|
||||
log.Debugf("gRPC method: %s", info.FullMethod)
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
func streamLogger(
|
||||
srv interface{},
|
||||
stream grpc.ServerStream,
|
||||
info *grpc.StreamServerInfo,
|
||||
handler grpc.StreamHandler,
|
||||
) error {
|
||||
log.Debugf("gRPC method: %s", info.FullMethod)
|
||||
return handler(srv, stream)
|
||||
}
|
||||
0
server/internal/interface/grpc/permissions/.gitkeep
Executable file
0
server/internal/interface/grpc/permissions/.gitkeep
Executable file
64
server/internal/interface/grpc/service.go
Normal file
64
server/internal/interface/grpc/service.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package grpcservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
|
||||
appconfig "github.com/ark-network/ark/internal/app-config"
|
||||
interfaces "github.com/ark-network/ark/internal/interface"
|
||||
"github.com/ark-network/ark/internal/interface/grpc/handlers"
|
||||
"github.com/ark-network/ark/internal/interface/grpc/interceptors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type service struct {
|
||||
config Config
|
||||
appConfig *appconfig.Config
|
||||
server *grpc.Server
|
||||
}
|
||||
|
||||
func NewService(
|
||||
svcConfig Config, appConfig *appconfig.Config,
|
||||
) (interfaces.Service, error) {
|
||||
if err := svcConfig.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid service config: %s", err)
|
||||
}
|
||||
if err := appConfig.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("invalid app config: %s", err)
|
||||
}
|
||||
|
||||
grpcConfig := []grpc.ServerOption{
|
||||
interceptors.UnaryInterceptor(), interceptors.StreamInterceptor(),
|
||||
}
|
||||
if !svcConfig.NoTLS {
|
||||
return nil, fmt.Errorf("tls termination not supported yet")
|
||||
}
|
||||
creds := insecure.NewCredentials()
|
||||
grpcConfig = append(grpcConfig, grpc.Creds(creds))
|
||||
server := grpc.NewServer(grpcConfig...)
|
||||
handler := handlers.NewHandler(appConfig.AppService())
|
||||
arkv1.RegisterArkServiceServer(server, handler)
|
||||
return &service{svcConfig, appConfig, server}, nil
|
||||
}
|
||||
|
||||
func (s *service) Start() error {
|
||||
// nolint:all
|
||||
go s.server.Serve(s.config.listener())
|
||||
log.Infof("started listening at %s", s.config.address())
|
||||
|
||||
if err := s.appConfig.AppService().Start(); err != nil {
|
||||
return fmt.Errorf("failed to start app service: %s", err)
|
||||
}
|
||||
log.Info("started app service")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *service) Stop() {
|
||||
s.server.Stop()
|
||||
log.Info("stopped grpc server")
|
||||
s.appConfig.AppService().Stop()
|
||||
log.Info("stopped app service")
|
||||
}
|
||||
6
server/internal/interface/service.go
Executable file
6
server/internal/interface/service.go
Executable file
@@ -0,0 +1,6 @@
|
||||
package interfaces
|
||||
|
||||
type Service interface {
|
||||
Start() error
|
||||
Stop()
|
||||
}
|
||||
Reference in New Issue
Block a user