This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
package grpcadapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
domain "gl/domain/ledger"
|
||||
basev1 "gl/gen/base/v1"
|
||||
ledgerv1 "gl/gen/ledger/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func (h *Handler) requireAdmin(ctx context.Context) error {
|
||||
values := metadata.ValueFromIncomingContext(ctx, "x-admin-token")
|
||||
if h.adminToken == "" || len(values) != 1 || subtle.ConstantTimeCompare([]byte(values[0]), []byte(h.adminToken)) != 1 {
|
||||
return status.Error(codes.PermissionDenied, "invalid administrative token")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func modeMessage(state domain.OperatingState) *ledgerv1.OperatingModeResponse {
|
||||
return &ledgerv1.OperatingModeResponse{Mode: string(state.Mode), Revision: state.Revision, ActorId: state.ActorID, Reason: state.Reason, ChangedAt: timestampMessage(state.ChangedAt)}
|
||||
}
|
||||
|
||||
func (h *Handler) GetOperatingMode(ctx context.Context, _ *basev1.Empty) (*ledgerv1.OperatingModeResponse, error) {
|
||||
state, err := h.ledger.OperatingMode(ctx)
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
return modeMessage(state), nil
|
||||
}
|
||||
|
||||
func (h *Handler) SetOperatingMode(ctx context.Context, request *ledgerv1.SetOperatingModeRequest) (*ledgerv1.OperatingModeResponse, error) {
|
||||
if err := h.requireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state, err := h.ledger.SetOperatingMode(ctx, domain.OperatingMode(request.GetMode()), request.GetExpectedRevision(), request.GetActorId(), request.GetReason(), request.GetReconciliationConfirmed())
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
return modeMessage(state), nil
|
||||
}
|
||||
|
||||
func (h *Handler) PostInternalJournal(ctx context.Context, request *ledgerv1.AppendJournalRequest) (*ledgerv1.AppendJournalResponse, error) {
|
||||
if err := h.requireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
command, err := appendJournalCommand(request)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
result, err := h.ledger.PostInternalJournal(ctx, command)
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
return &ledgerv1.AppendJournalResponse{Journal: journalMessage(result.Journal), AlreadyExisted: result.AlreadyExisted}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) GetUserBalances(ctx context.Context, request *ledgerv1.GetUserBalancesRequest) (*ledgerv1.GetUserBalancesResponse, error) {
|
||||
if request == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "request is required")
|
||||
}
|
||||
balances, err := h.ledger.UserBalances(ctx, request.GetOwnerType(), request.GetOwnerId(), request.AssetId)
|
||||
if err != nil {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
response := &ledgerv1.GetUserBalancesResponse{OwnerType: request.GetOwnerType(), OwnerId: request.GetOwnerId()}
|
||||
for _, balance := range balances {
|
||||
response.Balances = append(response.Balances, &ledgerv1.UserAssetBalance{
|
||||
AssetId: balance.AssetID, Available: balance.Available.String(), Frozen: balance.Frozen.String(), Total: balance.Available.Add(balance.Frozen).String(),
|
||||
})
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package grpcadapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
ledgerv1 "gl/gen/ledger/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAuthorityCommandsRequireOneConfiguredAdministrativeToken(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, configured string
|
||||
tokens []string
|
||||
}{
|
||||
{"unconfigured", "", []string{"token"}},
|
||||
{"missing", "token", nil},
|
||||
{"wrong", "token", []string{"wrong"}},
|
||||
{"duplicate", "token", []string{"token", "token"}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
handler := NewHandler(nil, nil).WithSettlementAdminToken(tc.configured)
|
||||
ctx := metadata.NewIncomingContext(context.Background(), metadata.MD{"x-admin-token": tc.tokens})
|
||||
if _, err := handler.SetOperatingMode(ctx, &ledgerv1.SetOperatingModeRequest{}); status.Code(err) != codes.PermissionDenied {
|
||||
t.Fatalf("toggle: %v", err)
|
||||
}
|
||||
if _, err := handler.PostInternalJournal(ctx, &ledgerv1.AppendJournalRequest{}); status.Code(err) != codes.PermissionDenied {
|
||||
t.Fatalf("internal posting: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -398,6 +398,10 @@ func rpcError(err error) error {
|
||||
return status.Error(codes.FailedPrecondition, err.Error())
|
||||
case errors.Is(err, domain.ErrAlreadyReversed):
|
||||
return status.Error(codes.FailedPrecondition, err.Error())
|
||||
case errors.Is(err, domain.ErrModeConflict), errors.Is(err, domain.ErrConcurrentPosting):
|
||||
return status.Error(codes.Aborted, err.Error())
|
||||
case errors.Is(err, domain.ErrModeRequired), errors.Is(err, domain.ErrInsufficientFunds), errors.Is(err, domain.ErrSettlementInFlight):
|
||||
return status.Error(codes.FailedPrecondition, err.Error())
|
||||
case errors.Is(err, context.Canceled):
|
||||
return status.Error(codes.Canceled, err.Error())
|
||||
case errors.Is(err, context.DeadlineExceeded):
|
||||
|
||||
@@ -2,12 +2,11 @@ package grpcadapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
|
||||
"gl/domain/ledger"
|
||||
ledgerv1 "gl/gen/ledger/v1"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
@@ -17,6 +16,9 @@ func (h *Handler) EnqueueSettlement(ctx context.Context, request *ledgerv1.Enque
|
||||
}
|
||||
record, already, err := h.settlement.Enqueue(ctx, request.GetSourceService(), request.GetSourceTransactionId(), request.GetIdempotencyKey(), request.GetNetwork(), request.GetSignedTransactionXdr())
|
||||
if err != nil {
|
||||
if errors.Is(err, ledger.ErrModeRequired) {
|
||||
return nil, rpcError(err)
|
||||
}
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
return &ledgerv1.EnqueueSettlementResponse{SettlementId: record.ID, Status: string(record.Status), AlreadyExisted: already}, nil
|
||||
@@ -26,9 +28,8 @@ func (h *Handler) RetrySettlement(ctx context.Context, request *ledgerv1.RetrySe
|
||||
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")
|
||||
if err := h.requireAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
record, err := h.settlement.RetryByOperator(ctx, request.GetSettlementId(), request.GetActorId(), request.GetReason())
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user