80 lines
3.4 KiB
Go
80 lines
3.4 KiB
Go
package ledger
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
domain "gl/domain/ledger"
|
|
"go.opentelemetry.io/otel"
|
|
"go.opentelemetry.io/otel/attribute"
|
|
"go.opentelemetry.io/otel/codes"
|
|
)
|
|
|
|
type AuthorityRepository interface {
|
|
OperatingMode(context.Context) (domain.OperatingState, error)
|
|
SetOperatingMode(context.Context, domain.OperatingMode, int64, string, string, bool) (domain.OperatingState, error)
|
|
UserBalances(context.Context, string, string, *int64) ([]domain.UserAssetBalance, error)
|
|
}
|
|
|
|
func (s *Service) OperatingMode(ctx context.Context) (domain.OperatingState, error) {
|
|
repository, ok := s.repository.(AuthorityRepository)
|
|
if !ok {
|
|
return domain.OperatingState{}, domain.ErrModeRequired
|
|
}
|
|
return repository.OperatingMode(ctx)
|
|
}
|
|
|
|
func (s *Service) SetOperatingMode(ctx context.Context, mode domain.OperatingMode, revision int64, actor, reason string, reconciled bool) (domain.OperatingState, error) {
|
|
if (mode != domain.ModeNormal && mode != domain.ModeGLOnly) || revision < 0 || strings.TrimSpace(actor) == "" || strings.TrimSpace(reason) == "" {
|
|
return domain.OperatingState{}, invalid("mode, nonnegative revision, actor, and reason are required")
|
|
}
|
|
repository, ok := s.repository.(AuthorityRepository)
|
|
if !ok {
|
|
return domain.OperatingState{}, domain.ErrModeRequired
|
|
}
|
|
ctx, span := otel.Tracer("gl/ledger").Start(ctx, "ledger.set_operating_mode")
|
|
defer span.End()
|
|
span.SetAttributes(attribute.String("ledger.mode", string(mode)), attribute.Int64("ledger.expected_revision", revision), attribute.String("ledger.actor_id", actor))
|
|
state, err := repository.SetOperatingMode(ctx, mode, revision, actor, reason, reconciled)
|
|
if err != nil {
|
|
span.RecordError(err)
|
|
span.SetStatus(codes.Error, "mode change failed")
|
|
return domain.OperatingState{}, err
|
|
}
|
|
if state.Revision != revision {
|
|
span.AddEvent("ledger.operating_mode_changed")
|
|
}
|
|
span.SetAttributes(attribute.Int64("ledger.revision", state.Revision))
|
|
return state, nil
|
|
}
|
|
|
|
func (s *Service) UserBalances(ctx context.Context, ownerType, ownerID string, assetID *int64) ([]domain.UserAssetBalance, error) {
|
|
if strings.TrimSpace(ownerType) == "" || strings.TrimSpace(ownerID) == "" || (assetID != nil && *assetID <= 0) {
|
|
return nil, invalid("owner type, owner id, and a positive optional asset id are required")
|
|
}
|
|
repository, ok := s.repository.(AuthorityRepository)
|
|
if !ok {
|
|
return nil, domain.ErrNotFound
|
|
}
|
|
return repository.UserBalances(ctx, ownerType, ownerID, assetID)
|
|
}
|
|
|
|
// PostInternalJournal executes exact balanced entries without network settlement.
|
|
// PostgreSQL rechecks mode and funds under locks in the same journal transaction.
|
|
func (s *Service) PostInternalJournal(ctx context.Context, command AppendJournalCommand) (AppendJournalResult, error) {
|
|
if strings.TrimSpace(command.ActorID) == "" {
|
|
return AppendJournalResult{}, invalid("actor id is required for internal execution")
|
|
}
|
|
if command.Blockchain != (domain.BlockchainReference{}) {
|
|
return AppendJournalResult{}, invalid("internal execution cannot claim blockchain settlement")
|
|
}
|
|
for _, entry := range command.Entries {
|
|
if entry.Account.Class == domain.AccountClassExternalBlockchain {
|
|
return AppendJournalResult{}, invalid("external blockchain movements cannot execute internally")
|
|
}
|
|
}
|
|
command.Metadata = cloneMetadata(command.Metadata)
|
|
command.Metadata[domain.InternalExecutionMetadata] = string(domain.ModeGLOnly)
|
|
return s.appendJournal(ctx, command)
|
|
}
|