Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

accounts: address pendingPayments issues & add tracer #766

Merged
merged 11 commits into from
Jun 6, 2024
270 changes: 234 additions & 36 deletions accounts/checkers.go

Large diffs are not rendered by default.

631 changes: 623 additions & 8 deletions accounts/checkers_test.go

Large diffs are not rendered by default.

57 changes: 54 additions & 3 deletions accounts/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package accounts
import (
"context"
"fmt"

"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)

// ContextKey is the type that we use to identify account specific values in the
Expand All @@ -18,18 +21,23 @@ var (
// KeyAccount is the key under which we store the account in the request
// context.
KeyAccount = ContextKey{"account"}

// KeyRequestID is the key under which we store the middleware request
// ID.
KeyRequestID = ContextKey{"request_id"}
)

// FromContext tries to extract a value from the given context.
func FromContext(ctx context.Context, key ContextKey) interface{} {
return ctx.Value(key)
}

// AddToContext adds the given value to the context for easy retrieval later on.
func AddToContext(ctx context.Context, key ContextKey,
// AddAccountToContext adds the given value to the context for easy retrieval
// later on.
func AddAccountToContext(ctx context.Context,
value *OffChainBalanceAccount) context.Context {

return context.WithValue(ctx, key, value)
return context.WithValue(ctx, KeyAccount, value)
}

// AccountFromContext attempts to extract an account from the given context.
Expand All @@ -46,3 +54,46 @@ func AccountFromContext(ctx context.Context) (*OffChainBalanceAccount, error) {

return acct, nil
}

// AddRequestIDToContext adds the given request ID to the context for easy
// retrieval later on.
func AddRequestIDToContext(ctx context.Context, value uint64) context.Context {
return context.WithValue(ctx, KeyRequestID, value)
}

// RequestIDFromContext attempts to extract a request ID from the given context.
func RequestIDFromContext(ctx context.Context) (uint64, error) {
val := FromContext(ctx, KeyRequestID)
if val == nil {
return 0, fmt.Errorf("no request ID found in context")
}

reqID, ok := val.(uint64)
if !ok {
return 0, fmt.Errorf("invalid request ID value in context")
}

return reqID, nil
}

// requestScopedValuesFromCtx is a helper function that can be used to extract
// an account and requestID from the given context. It also creates a new
// prefixed logger that can be used by account request and response handlers.
// Each log line will be prefixed by the account ID and the request ID.
func requestScopedValuesFromCtx(ctx context.Context) (btclog.Logger,
*OffChainBalanceAccount, uint64, error) {

acc, err := AccountFromContext(ctx)
if err != nil {
return nil, nil, 0, err
}

reqID, err := RequestIDFromContext(ctx)
if err != nil {
return nil, nil, 0, err
}

prefix := fmt.Sprintf("[account: %s, request: %d]", acc.ID, reqID)

return build.NewPrefixLog(prefix, log), acc, reqID, nil
}
35 changes: 30 additions & 5 deletions accounts/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ func (s *InterceptorService) Intercept(ctx context.Context,
)
}

// We now add the account to the incoming context to give each checker
// access to it if required.
ctxAccount := AddToContext(ctx, KeyAccount, acct)
// We now add the account and request ID to the incoming context to give
// each checker access to them if required.
ctx = AddAccountToContext(ctx, acct)
ctx = AddRequestIDToContext(ctx, req.RequestId)

switch r := req.InterceptType.(type) {
// In the authentication phase we just check that the account hasn't
Expand All @@ -120,18 +121,42 @@ func (s *InterceptorService) Intercept(ctx context.Context,
}

return mid.RPCErr(req, s.checkers.checkIncomingRequest(
ctxAccount, r.Request.MethodFullUri, msg,
ctx, r.Request.MethodFullUri, msg,
))

// Parse and possibly manipulate outgoing responses.
case *lnrpc.RPCMiddlewareRequest_Response:
if r.Response.IsError {
parsedErr := mid.ParseResponseErr(r.Response.Serialized)

replacementErr, err := s.checkers.handleErrorResponse(
ctx, r.Response.MethodFullUri, parsedErr,
)
if err != nil {
return mid.RPCErr(req, err)
}

// No error occurred but the response error should be
// replaced with the given custom error. Wrap it in the
// correct RPC response of the interceptor now.
if replacementErr != nil {
return mid.RPCErrReplacement(
req, replacementErr,
)
}

// No error and no replacement, just return an empty
// response of the correct type.
return mid.RPCOk(req)
}

msg, err := parseRPCMessage(r.Response)
if err != nil {
return mid.RPCErr(req, err)
}

replacement, err := s.checkers.replaceOutgoingResponse(
ctxAccount, r.Response.MethodFullUri, msg,
ctx, r.Response.MethodFullUri, msg,
)
if err != nil {
return mid.RPCErr(req, err)
Expand Down
39 changes: 39 additions & 0 deletions accounts/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ func ParseAccountID(idStr string) (*AccountID, error) {
return &id, nil
}

// String returns the string representation of the AccountID.
func (a AccountID) String() string {
return hex.EncodeToString(a[:])
}

// PaymentEntry is the data we track per payment that is associated with an
// account. This basically includes all information required to make sure
// in-flight payments don't exceed the total available account balance.
Expand Down Expand Up @@ -252,4 +257,38 @@ type Service interface {
// restarted.
AssociatePayment(id AccountID, paymentHash lntypes.Hash,
fullAmt lnwire.MilliSatoshi) error

// PaymentErrored removes a pending payment from the accounts
// registered payment list. This should only ever be called if we are
// sure that the payment request errored out.
PaymentErrored(id AccountID, hash lntypes.Hash) error

RequestValuesStore
}

// RequestValues holds various values associated with a specific request that
// we may want access to when handling the response. At the moment this only
// stores payment related data.
type RequestValues struct {
// PaymentHash is the hash of the payment that this request is
// associated with.
PaymentHash lntypes.Hash

// PaymentAmount is the value of the payment being made.
PaymentAmount lnwire.MilliSatoshi
}

// RequestValuesStore is a store that can be used to keep track of the mapping
// between a request ID and various values associated with that request which
// we may want access to when handling the request response.
type RequestValuesStore interface {
// RegisterValues stores values for the given request ID.
RegisterValues(reqID uint64, values *RequestValues) error

// GetValues returns the corresponding request values for the given
// request ID if they exist.
GetValues(reqID uint64) (*RequestValues, bool)

// DeleteValues deletes any values stored for the given request ID.
DeleteValues(reqID uint64)
}
2 changes: 1 addition & 1 deletion accounts/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"github.com/lightningnetwork/lnd/build"
)

const Subsystem = "ACCT"
const Subsystem = "ACNT"

// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
Expand Down
Loading
Loading