Files
ark/pkg/client-sdk/client/grpc/client.go
Pietralberto Mazza 89df461623 Update client sdk (#207)
* Add bitcoin networks

* Refactor client

* Refactor explorer

* Refactor store

* Refactor wallet

* Refactor sdk client

* Refactor wasm & Update examples

* Move common util funcs to internal/utils

* Move to constants for service types

* Add unit tests

* Parallelize tests

* Lint

* Add job to gh action

* go mod tidy

* Fixes

* Fixes

* Fix compose file

* Fixes

* Fixes after review:
* Drop factory pattern
* Drop password from ark client methods
* Make singlekey wallet manage store and wallet store instead of defining WalletStore as extension of Store
* Move constants to arksdk module
* Drop config and expect directory store and wallet as ark client factory args

* Fix

* Add constants for bitcoin/liquid explorer

* Fix test

* Fix wasm

* Rename client.Client to client.ASPClient

* Rename store.Store to store.ConfigStore

* Rename wallet.Wallet to wallet.WalletService

* Renamings

* Lint

* Fixes

* Move everything to internal/utils & move ComputeVtxoTaprootScript to common

* Go mod tidy
2024-07-30 16:08:23 +02:00

278 lines
6.4 KiB
Go

package grpcclient
import (
"context"
"fmt"
"strings"
"time"
"github.com/ark-network/ark-sdk/client"
"github.com/ark-network/ark-sdk/explorer"
arkv1 "github.com/ark-network/ark/api-spec/protobuf/gen/ark/v1"
"github.com/ark-network/ark/common/tree"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
type grpcClient struct {
conn *grpc.ClientConn
svc arkv1.ArkServiceClient
eventsCh chan client.RoundEventChannel
}
func NewClient(aspUrl string) (client.ASPClient, error) {
if len(aspUrl) <= 0 {
return nil, fmt.Errorf("missing asp url")
}
creds := insecure.NewCredentials()
port := 80
if strings.HasPrefix(aspUrl, "https://") {
aspUrl = strings.TrimPrefix(aspUrl, "https://")
creds = credentials.NewTLS(nil)
port = 443
}
if !strings.Contains(aspUrl, ":") {
aspUrl = fmt.Sprintf("%s:%d", aspUrl, port)
}
conn, err := grpc.NewClient(aspUrl, grpc.WithTransportCredentials(creds))
if err != nil {
return nil, err
}
svc := arkv1.NewArkServiceClient(conn)
eventsCh := make(chan client.RoundEventChannel)
return &grpcClient{conn, svc, eventsCh}, nil
}
func (c *grpcClient) Close() {
//nolint:all
c.conn.Close()
}
func (a *grpcClient) GetEventStream(
ctx context.Context, paymentID string, req *arkv1.GetEventStreamRequest,
) (<-chan client.RoundEventChannel, error) {
stream, err := a.svc.GetEventStream(ctx, req)
if err != nil {
return nil, err
}
go func() {
defer close(a.eventsCh)
for {
resp, err := stream.Recv()
if err != nil {
a.eventsCh <- client.RoundEventChannel{Err: err}
return
}
a.eventsCh <- client.RoundEventChannel{Event: resp}
}
}()
return a.eventsCh, nil
}
func (a *grpcClient) GetInfo(ctx context.Context) (*arkv1.GetInfoResponse, error) {
return a.svc.GetInfo(ctx, &arkv1.GetInfoRequest{})
}
func (a *grpcClient) ListVtxos(
ctx context.Context,
addr string,
) (*arkv1.ListVtxosResponse, error) {
return a.svc.ListVtxos(ctx, &arkv1.ListVtxosRequest{Address: addr})
}
func (a *grpcClient) GetRound(
ctx context.Context, txID string,
) (*arkv1.GetRoundResponse, error) {
return a.svc.GetRound(ctx, &arkv1.GetRoundRequest{Txid: txID})
}
func (a *grpcClient) GetSpendableVtxos(
ctx context.Context, addr string, explorerSvc explorer.Explorer,
) ([]*client.Vtxo, error) {
allVtxos, err := a.ListVtxos(ctx, addr)
if err != nil {
return nil, err
}
vtxos := make([]*client.Vtxo, 0, len(allVtxos.GetSpendableVtxos()))
for _, v := range allVtxos.GetSpendableVtxos() {
var expireAt *time.Time
if v.ExpireAt > 0 {
t := time.Unix(v.ExpireAt, 0)
expireAt = &t
}
if v.Swept {
continue
}
vtxos = append(vtxos, &client.Vtxo{
Amount: v.Receiver.Amount,
Txid: v.Outpoint.Txid,
VOut: v.Outpoint.Vout,
RoundTxid: v.PoolTxid,
ExpiresAt: expireAt,
})
}
if explorerSvc == nil {
return vtxos, nil
}
redeemBranches, err := a.GetRedeemBranches(ctx, vtxos, explorerSvc)
if err != nil {
return nil, err
}
for vtxoTxid, branch := range redeemBranches {
expiration, err := branch.ExpiresAt()
if err != nil {
return nil, err
}
for i, vtxo := range vtxos {
if vtxo.Txid == vtxoTxid {
vtxos[i].ExpiresAt = expiration
break
}
}
}
return vtxos, nil
}
func (a *grpcClient) GetRedeemBranches(
ctx context.Context, vtxos []*client.Vtxo, explorerSvc explorer.Explorer,
) (map[string]*client.RedeemBranch, error) {
congestionTrees := make(map[string]tree.CongestionTree, 0)
redeemBranches := make(map[string]*client.RedeemBranch, 0)
for _, vtxo := range vtxos {
if _, ok := congestionTrees[vtxo.RoundTxid]; !ok {
round, err := a.GetRound(ctx, vtxo.RoundTxid)
if err != nil {
return nil, err
}
treeFromRound := round.GetRound().GetCongestionTree()
congestionTree, err := toCongestionTree(treeFromRound)
if err != nil {
return nil, err
}
congestionTrees[vtxo.RoundTxid] = congestionTree
}
redeemBranch, err := client.NewRedeemBranch(
explorerSvc, congestionTrees[vtxo.RoundTxid], vtxo,
)
if err != nil {
return nil, err
}
redeemBranches[vtxo.Txid] = redeemBranch
}
return redeemBranches, nil
}
func (a *grpcClient) GetOffchainBalance(
ctx context.Context, addr string, explorerSvc explorer.Explorer,
) (uint64, map[int64]uint64, error) {
amountByExpiration := make(map[int64]uint64, 0)
vtxos, err := a.GetSpendableVtxos(ctx, addr, explorerSvc)
if err != nil {
return 0, nil, err
}
var balance uint64
for _, vtxo := range vtxos {
balance += vtxo.Amount
if vtxo.ExpiresAt != nil {
expiration := vtxo.ExpiresAt.Unix()
if _, ok := amountByExpiration[expiration]; !ok {
amountByExpiration[expiration] = 0
}
amountByExpiration[expiration] += vtxo.Amount
}
}
return balance, amountByExpiration, nil
}
func (a *grpcClient) Onboard(
ctx context.Context, req *arkv1.OnboardRequest,
) (*arkv1.OnboardResponse, error) {
return a.svc.Onboard(ctx, req)
}
func (a *grpcClient) RegisterPayment(
ctx context.Context, req *arkv1.RegisterPaymentRequest,
) (*arkv1.RegisterPaymentResponse, error) {
return a.svc.RegisterPayment(ctx, req)
}
func (a *grpcClient) ClaimPayment(
ctx context.Context, req *arkv1.ClaimPaymentRequest,
) (*arkv1.ClaimPaymentResponse, error) {
return a.svc.ClaimPayment(ctx, req)
}
func (a *grpcClient) Ping(
ctx context.Context, req *arkv1.PingRequest,
) (*arkv1.PingResponse, error) {
return a.svc.Ping(ctx, req)
}
func (a *grpcClient) FinalizePayment(
ctx context.Context, req *arkv1.FinalizePaymentRequest,
) (*arkv1.FinalizePaymentResponse, error) {
return a.svc.FinalizePayment(ctx, req)
}
func (a *grpcClient) GetRoundByID(
ctx context.Context, roundID string,
) (*arkv1.GetRoundByIdResponse, error) {
return a.svc.GetRoundById(ctx, &arkv1.GetRoundByIdRequest{
Id: roundID,
})
}
func toCongestionTree(treeFromProto *arkv1.Tree) (tree.CongestionTree, error) {
levels := make(tree.CongestionTree, 0, len(treeFromProto.Levels))
for _, level := range treeFromProto.Levels {
nodes := make([]tree.Node, 0, len(level.Nodes))
for _, node := range level.Nodes {
nodes = append(nodes, tree.Node{
Txid: node.Txid,
Tx: node.Tx,
ParentTxid: node.ParentTxid,
Leaf: false,
})
}
levels = append(levels, nodes)
}
for j, treeLvl := range levels {
for i, node := range treeLvl {
if len(levels.Children(node.Txid)) == 0 {
levels[j][i].Leaf = true
}
}
}
return levels, nil
}