feat: add durable Kuknos settlement coordination

This commit is contained in:
Navid
2026-09-15 13:59:13 +03:30
parent d5c9b338a4
commit 12c478cf3a
34 changed files with 1846 additions and 122 deletions
+21 -6
View File
@@ -5,18 +5,27 @@ import (
"gl/application/health"
applicationledger "gl/application/ledger"
applicationsettlement "gl/application/settlement"
basev1 "gl/gen/base/v1"
ledgerv1 "gl/gen/ledger/v1"
)
type Handler struct {
ledgerv1.UnimplementedGeneralLedgerServiceServer
health *health.Service
ledger *applicationledger.Service
health *health.Service
ledger *applicationledger.Service
settlement *applicationsettlement.Service
adminToken string
}
func NewHandler(healthService *health.Service, ledgerService *applicationledger.Service) *Handler {
return &Handler{health: healthService, ledger: ledgerService}
func (h *Handler) WithSettlementAdminToken(token string) *Handler { h.adminToken = token; return h }
func NewHandler(healthService *health.Service, ledgerService *applicationledger.Service, settlementService ...*applicationsettlement.Service) *Handler {
var settlement *applicationsettlement.Service
if len(settlementService) > 0 {
settlement = settlementService[0]
}
return &Handler{health: healthService, ledger: ledgerService, settlement: settlement}
}
func NewHealthHandler(service *health.Service) *Handler {
@@ -26,7 +35,13 @@ func NewHealthHandler(service *health.Service) *Handler {
func (h *Handler) Health(ctx context.Context, _ *basev1.Empty) (*ledgerv1.HealthResponse, error) {
result := h.health.Check(ctx)
return &ledgerv1.HealthResponse{
Serving: result.Serving,
DatabaseReady: result.DatabaseReady,
Serving: result.Serving,
DatabaseReady: result.DatabaseReady,
SettlementEnabled: result.SettlementEnabled,
SettlementReady: result.SettlementReady,
SettlementPending: result.SettlementStats.Pending,
SettlementRetryable: result.SettlementStats.Retryable,
SettlementManualReview: result.SettlementStats.ManualReview,
OldestPendingSeconds: result.SettlementStats.OldestPendingSeconds,
}, nil
}
+60
View File
@@ -0,0 +1,60 @@
package grpcadapter
import (
"context"
"crypto/subtle"
"gl/domain/ledger"
ledgerv1 "gl/gen/ledger/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func (h *Handler) EnqueueSettlement(ctx context.Context, request *ledgerv1.EnqueueSettlementRequest) (*ledgerv1.EnqueueSettlementResponse, error) {
if h.settlement == nil {
return nil, status.Error(codes.Unimplemented, "settlement is not configured")
}
record, already, err := h.settlement.Enqueue(ctx, request.GetSourceService(), request.GetSourceTransactionId(), request.GetIdempotencyKey(), request.GetNetwork(), request.GetSignedTransactionXdr())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return &ledgerv1.EnqueueSettlementResponse{SettlementId: record.ID, Status: string(record.Status), AlreadyExisted: already}, nil
}
func (h *Handler) RetrySettlement(ctx context.Context, request *ledgerv1.RetrySettlementRequest) (*ledgerv1.RetrySettlementResponse, error) {
if h.settlement == nil {
return nil, status.Error(codes.Unimplemented, "settlement is not configured")
}
values := metadata.ValueFromIncomingContext(ctx, "x-admin-token")
if h.adminToken == "" || len(values) != 1 || subtle.ConstantTimeCompare([]byte(values[0]), []byte(h.adminToken)) != 1 {
return nil, status.Error(codes.PermissionDenied, "invalid administrative token")
}
record, err := h.settlement.RetryByOperator(ctx, request.GetSettlementId(), request.GetActorId(), request.GetReason())
if err != nil {
return nil, status.Error(codes.FailedPrecondition, err.Error())
}
return &ledgerv1.RetrySettlementResponse{Settlement: settlementMessage(record)}, nil
}
func settlementMessage(record ledger.Settlement) *ledgerv1.Settlement {
return &ledgerv1.Settlement{SettlementId: record.ID, SourceService: record.SourceService, SourceTransactionId: record.SourceTxID, IdempotencyKey: record.IdempotencyKey, Status: string(record.Status), Network: record.Network, TransactionHash: record.TransactionHash, Attempts: uint32(record.Attempts), LastError: record.LastError}
}
func (h *Handler) GetSettlement(ctx context.Context, request *ledgerv1.GetSettlementRequest) (*ledgerv1.Settlement, error) {
if h.settlement == nil {
return nil, status.Error(codes.Unimplemented, "settlement is not configured")
}
var id, key string
switch lookup := request.GetLookup().(type) {
case *ledgerv1.GetSettlementRequest_SettlementId:
id = lookup.SettlementId
case *ledgerv1.GetSettlementRequest_IdempotencyKey:
key = lookup.IdempotencyKey
}
record, err := h.settlement.Get(ctx, id, key)
if err != nil {
return nil, status.Error(codes.NotFound, err.Error())
}
return settlementMessage(record), nil
}