diff --git a/accounts/checkers_test.go b/accounts/checkers_test.go index 3f1f28d7..0e6ef37a 100644 --- a/accounts/checkers_test.go +++ b/accounts/checkers_test.go @@ -63,6 +63,18 @@ type mockService struct { *requestValuesStore } +func (m *mockService) CreditAccount(_ context.Context, _ AccountID, + _ lnwire.MilliSatoshi) (*OffChainBalanceAccount, error) { + + return nil, nil +} + +func (m *mockService) DebitAccount(_ context.Context, _ AccountID, + _ lnwire.MilliSatoshi) (*OffChainBalanceAccount, error) { + + return nil, nil +} + func newMockService() *mockService { return &mockService{ acctBalanceMsat: 0, diff --git a/accounts/interface.go b/accounts/interface.go index ef0e7d3e..760732f1 100644 --- a/accounts/interface.go +++ b/accounts/interface.go @@ -331,6 +331,16 @@ type Service interface { PaymentErrored(ctx context.Context, id AccountID, hash lntypes.Hash) error + // CreditAccount increases the balance of an existing account in the + // database. + CreditAccount(ctx context.Context, accountID AccountID, + amount lnwire.MilliSatoshi) (*OffChainBalanceAccount, error) + + // DebitAccount decreases the balance of an existing account in the + // database. + DebitAccount(ctx context.Context, accountID AccountID, + amount lnwire.MilliSatoshi) (*OffChainBalanceAccount, error) + RequestValuesStore } diff --git a/accounts/rpcserver.go b/accounts/rpcserver.go index 5795696c..ce1291c1 100644 --- a/accounts/rpcserver.go +++ b/accounts/rpcserver.go @@ -130,6 +130,83 @@ func (s *RPCServer) UpdateAccount(ctx context.Context, return marshalAccount(account), nil } +// CreditAccount increases the balance of an existing account in the account +// database, by the given amount. +func (s *RPCServer) CreditAccount(ctx context.Context, + req *litrpc.CreditAccountRequest) (*litrpc.CreditAccountResponse, + error) { + + if req.GetAccount() == nil { + return nil, fmt.Errorf("account param must be specified") + } + + var id, label string + + switch idType := req.Account.Identifier.(type) { + case *litrpc.AccountIdentifier_Id: + id = idType.Id + case *litrpc.AccountIdentifier_Label: + label = idType.Label + } + + log.Infof("[creditaccount] id=%s, label=%v, amount=%d", id, label, + req.Amount) + + amount := lnwire.MilliSatoshi(req.Amount * 1000) + + accountID, err := s.findAccount(ctx, id, label) + if err != nil { + return nil, err + } + + account, err := s.service.CreditAccount(ctx, accountID, amount) + if err != nil { + return nil, err + } + + return &litrpc.CreditAccountResponse{ + Account: marshalAccount(account), + }, nil +} + +// DebitAccount decreases the balance of an existing account in the account +// database, by the given amount. +func (s *RPCServer) DebitAccount(ctx context.Context, + req *litrpc.DebitAccountRequest) (*litrpc.DebitAccountResponse, error) { + + if req.GetAccount() == nil { + return nil, fmt.Errorf("account param must be specified") + } + + var id, label string + + switch idType := req.Account.Identifier.(type) { + case *litrpc.AccountIdentifier_Id: + id = idType.Id + case *litrpc.AccountIdentifier_Label: + label = idType.Label + } + + log.Infof("[debitaccount] id=%s, label=%v, amount=%d", id, label, + req.Amount) + + amount := lnwire.MilliSatoshi(req.Amount * 1000) + + accountID, err := s.findAccount(ctx, id, label) + if err != nil { + return nil, err + } + + account, err := s.service.DebitAccount(ctx, accountID, amount) + if err != nil { + return nil, err + } + + return &litrpc.DebitAccountResponse{ + Account: marshalAccount(account), + }, nil +} + // ListAccounts returns all accounts that are currently stored in the account // database. func (s *RPCServer) ListAccounts(ctx context.Context, diff --git a/accounts/service.go b/accounts/service.go index 098c4a8f..102b9ea8 100644 --- a/accounts/service.go +++ b/accounts/service.go @@ -345,6 +345,56 @@ func (s *InterceptorService) UpdateAccount(ctx context.Context, return s.store.Account(ctx, accountID) } +// CreditAccount increases the balance of an existing account in the database. +func (s *InterceptorService) CreditAccount(ctx context.Context, + accountID AccountID, + amount lnwire.MilliSatoshi) (*OffChainBalanceAccount, error) { + + s.Lock() + defer s.Unlock() + + // As this function updates account balances, we require that the + // service is running before we execute it. + if !s.isRunningUnsafe() { + // This case can only happen if the service is disabled while + // we're processing a request. + return nil, ErrAccountServiceDisabled + } + + // Credit the account in the db. + err := s.store.CreditAccount(ctx, accountID, amount) + if err != nil { + return nil, fmt.Errorf("unable to credit account: %w", err) + } + + return s.store.Account(ctx, accountID) +} + +// DebitAccount decreases the balance of an existing account in the database. +func (s *InterceptorService) DebitAccount(ctx context.Context, + accountID AccountID, + amount lnwire.MilliSatoshi) (*OffChainBalanceAccount, error) { + + s.Lock() + defer s.Unlock() + + // As this function updates account balances, we require that the + // service is running before we execute it. + if !s.isRunningUnsafe() { + // This case can only happen if the service is disabled while + // we're processing a request. + return nil, ErrAccountServiceDisabled + } + + // Debit the account in the db. + err := s.store.DebitAccount(ctx, accountID, amount) + if err != nil { + return nil, fmt.Errorf("unable to debit account: %w", err) + } + + return s.store.Account(ctx, accountID) +} + // Account retrieves an account from the bolt DB and un-marshals it. If the // account cannot be found, then ErrAccNotFound is returned. func (s *InterceptorService) Account(ctx context.Context,