From 5e4afd8cb7a36e630a058bebe978e2462b8483b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Lewandowski?= <35259896+pawellewandowski98@users.noreply.github.com> Date: Fri, 12 Jan 2024 14:20:37 +0100 Subject: [PATCH 01/10] Feat(BUX-417): remove monitor, itc flag (#402) * fix(BUX-461): use_fee_quotes for both arc and mApi * fix(BUX-461): configurable fee_unit * fix(BUX-461): validate miners * refactor(BUX-461): small change after self-review * chore(BUX-461): validation error when neither fee_unit nor use_fee_quotes is configured * chore(BUX-461): assume next bux version 0.12.0 * chore(BUX-461): assume next version 0.13.0 * chore(BUX-461): go mod tidy with broadcast-client 0.16.0 * chore(BUX-417): remove monitor * chore(BUX-417): remove ITC flag * chore(BUX-417): replace bux version with rc branch * chore(BUX-417): update go sum --------- Co-authored-by: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> --- actions/destinations/create.go | 1 - config/services.go | 4 - graph/schema.resolvers.go | 571 +++++++++++++++++++++++++++++++++ mappings/destination.go | 1 - 4 files changed, 571 insertions(+), 6 deletions(-) create mode 100644 graph/schema.resolvers.go diff --git a/actions/destinations/create.go b/actions/destinations/create.go index c6eafc98d..89d01ac24 100644 --- a/actions/destinations/create.go +++ b/actions/destinations/create.go @@ -66,7 +66,6 @@ func (a *Action) create(w http.ResponseWriter, req *http.Request, _ httprouter.P xPub.RawXpub(), uint32(0), // todo: use a constant? protect this? scriptType, - true, // monitor this address as it was created by request of a user to share opts..., ); err != nil { apirouter.ReturnResponse(w, req, http.StatusUnprocessableEntity, err.Error()) diff --git a/config/services.go b/config/services.go index e28a8ee2e..fd9b15b2b 100644 --- a/config/services.go +++ b/config/services.go @@ -141,10 +141,6 @@ func (s *AppServices) loadBux(ctx context.Context, appConfig *AppConfig, testMod options = append(options, bux.WithUserAgent(appConfig.GetUserAgent())) - if appConfig.DisableITC { - options = append(options, bux.WithITCDisabled()) - } - if appConfig.ImportBlockHeaders != "" { options = append(options, bux.WithImportBlockHeaders(appConfig.ImportBlockHeaders)) } diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go new file mode 100644 index 000000000..4057bc1bd --- /dev/null +++ b/graph/schema.resolvers.go @@ -0,0 +1,571 @@ +package graph + +// This file will be automatically regenerated based on the schema, any resolver implementations +// will be copied through when generating and any unknown code will be moved to the end. +// Code generated by github.com/99designs/gqlgen version v0.17.34 + +import ( + "context" + "errors" + "time" + + "github.com/BuxOrg/bux" + "github.com/BuxOrg/bux-server/graph/generated" + "github.com/BuxOrg/bux/utils" + "github.com/mrz1836/go-datastore" +) + +// Xpub is the resolver for the xpub field. +func (r *mutationResolver) Xpub(ctx context.Context, xpub string, metadata bux.Metadata) (*bux.Xpub, error) { + // including admin check + c, err := GetConfigFromContextAdmin(ctx) + if err != nil { + return nil, err + } + + var existingXpub *bux.Xpub + existingXpub, err = c.Services.Bux.GetXpub(ctx, xpub) + if err != nil && !errors.Is(err, bux.ErrMissingXpub) { + return nil, err + } + if existingXpub != nil { + return nil, errors.New("xpub already exists") + } + + opts := c.Services.Bux.DefaultModelOptions() + for key, value := range metadata { + opts = append(opts, bux.WithMetadata(key, value)) + } + + // Create a new xPub + var xPub *bux.Xpub + if xPub, err = c.Services.Bux.NewXpub( + ctx, xpub, opts..., + ); err != nil { + return nil, err + } + + return bux.DisplayModels(xPub).(*bux.Xpub), nil +} + +// XpubMetadata is the resolver for the xpub_metadata field. +func (r *mutationResolver) XpubMetadata(ctx context.Context, metadata bux.Metadata) (*bux.Xpub, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var xPub *bux.Xpub + xPub, err = c.Services.Bux.UpdateXpubMetadata(ctx, c.XPubID, metadata) + if err != nil { + return nil, err + } + + if !c.Signed || c.XPub == "" { + xPub.RemovePrivateData() + } + + return bux.DisplayModels(xPub).(*bux.Xpub), nil +} + +// AccessKey is the resolver for the access_key field. +func (r *mutationResolver) AccessKey(ctx context.Context, metadata bux.Metadata) (*bux.AccessKey, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + // Create a new accessKey + var accessKey *bux.AccessKey + if accessKey, err = c.Services.Bux.NewAccessKey( + ctx, + c.XPub, + bux.WithMetadatas(metadata), + ); err != nil { + return nil, err + } + + return bux.DisplayModels(accessKey).(*bux.AccessKey), nil +} + +// AccessKeyRevoke is the resolver for the access_key_revoke field. +func (r *mutationResolver) AccessKeyRevoke(ctx context.Context, id *string) (*bux.AccessKey, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + // Revoke an accessKey + var accessKey *bux.AccessKey + if accessKey, err = c.Services.Bux.RevokeAccessKey( + ctx, + c.XPub, + *id, + ); err != nil { + return nil, err + } + + return bux.DisplayModels(accessKey).(*bux.AccessKey), nil +} + +// Transaction is the resolver for the transaction field. +func (r *mutationResolver) Transaction(ctx context.Context, hex string, draftID *string, metadata bux.Metadata) (*bux.Transaction, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + opts := c.Services.Bux.DefaultModelOptions() + for key, value := range metadata { + opts = append(opts, bux.WithMetadata(key, value)) + } + + ref := "" + if draftID != nil { + ref = *draftID + } + + var transaction *bux.Transaction + transaction, err = c.Services.Bux.RecordTransaction( + ctx, c.XPub, hex, ref, opts..., + ) + if err != nil { + if errors.Is(err, datastore.ErrDuplicateKey) { + var txID string + txID, err = utils.GetTransactionIDFromHex(hex) + if err != nil { + return nil, err + } + + transaction, err = c.Services.Bux.GetTransaction(ctx, c.XPub, txID) + if err != nil { + return nil, err + } + + // record the metadata is being added to the transaction + if len(metadata) > 0 { + xPubID := utils.Hash(c.XPub) + if transaction.XpubMetadata == nil { + transaction.XpubMetadata = make(bux.XpubMetadata) + } + if transaction.XpubMetadata[xPubID] == nil { + transaction.XpubMetadata[xPubID] = make(bux.Metadata) + } + for key, value := range metadata { + transaction.XpubMetadata[xPubID][key] = value + } + err = transaction.Save(ctx) + if err != nil { + return nil, err + } + // set metadata to the xpub metadata - is removed after Save + transaction.Metadata = transaction.XpubMetadata[xPubID] + } + + return transaction, nil + } + return nil, err + } + + return bux.DisplayModels(transaction).(*bux.Transaction), nil +} + +// TransactionMetadata is the resolver for the transaction_metadata field. +func (r *mutationResolver) TransactionMetadata(ctx context.Context, id string, metadata bux.Metadata) (*bux.Transaction, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var tx *bux.Transaction + tx, err = c.Services.Bux.UpdateTransactionMetadata(ctx, c.XPubID, id, metadata) + if err != nil { + return nil, err + } + if tx == nil { + return nil, nil + } + + return bux.DisplayModels(tx).(*bux.Transaction), nil +} + +// NewTransaction is the resolver for the new_transaction field. +func (r *mutationResolver) NewTransaction(ctx context.Context, transactionConfig bux.TransactionConfig, metadata bux.Metadata) (*bux.DraftTransaction, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + opts := c.Services.Bux.DefaultModelOptions() + if metadata != nil { + opts = append(opts, bux.WithMetadatas(metadata)) + } + + var draftTransaction *bux.DraftTransaction + draftTransaction, err = c.Services.Bux.NewTransaction(ctx, c.XPub, &transactionConfig, opts...) + if err != nil { + return nil, err + } + + return bux.DisplayModels(draftTransaction).(*bux.DraftTransaction), nil +} + +// Destination is the resolver for the destination field. +func (r *mutationResolver) Destination(ctx context.Context, destinationType *string, metadata bux.Metadata) (*bux.Destination, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + var useDestinationType string + if destinationType != nil { + useDestinationType = *destinationType + } else { + useDestinationType = utils.ScriptTypePubKeyHash + } + + opts := c.Services.Bux.DefaultModelOptions() + if metadata != nil { + opts = append(opts, bux.WithMetadatas(metadata)) + } + + var destination *bux.Destination + destination, err = c.Services.Bux.NewDestination( + ctx, + c.XPub, + utils.ChainExternal, + useDestinationType, + opts..., + ) + if err != nil { + return nil, err + } + + return bux.DisplayModels(destination).(*bux.Destination), nil +} + +// DestinationMetadata is the resolver for the destination_metadata field. +func (r *mutationResolver) DestinationMetadata(ctx context.Context, id *string, address *string, lockingScript *string, metadata bux.Metadata) (*bux.Destination, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + var destination *bux.Destination + if id != nil { + destination, err = c.Services.Bux.UpdateDestinationMetadataByID( + ctx, + c.XPubID, + *id, + metadata, + ) + } else if address != nil { + destination, err = c.Services.Bux.UpdateDestinationMetadataByAddress( + ctx, + c.XPubID, + *address, + metadata, + ) + } else if lockingScript != nil { + destination, err = c.Services.Bux.UpdateDestinationMetadataByLockingScript( + ctx, + c.XPubID, + *lockingScript, + metadata, + ) + } + if err != nil { + return nil, err + } + + return bux.DisplayModels(destination).(*bux.Destination), nil +} + +// UtxosUnreserve is the resolver for the utxos_unreserve field. +func (r *mutationResolver) UtxosUnreserve(ctx context.Context, draftID string) (*bool, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + err = c.Services.Bux.UnReserveUtxos( + ctx, + c.XPubID, + draftID, + ) + + var success bool + success = err != nil + return &success, err +} + +// Xpub is the resolver for the xpub field. +func (r *queryResolver) Xpub(ctx context.Context) (*bux.Xpub, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var xPub *bux.Xpub + xPub, err = c.Services.Bux.GetXpubByID(ctx, c.XPubID) + if err != nil { + return nil, err + } + + if !c.Signed || c.XPub == "" { + xPub.RemovePrivateData() + } + + return bux.DisplayModels(xPub).(*bux.Xpub), nil +} + +// AccessKey is the resolver for the access_key field. +func (r *queryResolver) AccessKey(ctx context.Context, key string) (*bux.AccessKey, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + var accessKey *bux.AccessKey + accessKey, err = c.Services.Bux.GetAccessKey(ctx, c.XPubID, key) + if err != nil { + return nil, err + } + + return bux.DisplayModels(accessKey).(*bux.AccessKey), nil +} + +// AccessKeys is the resolver for the access_keys field. +func (r *queryResolver) AccessKeys(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.AccessKey, error) { + c, err := GetConfigFromContextSigned(ctx) + if err != nil { + return nil, err + } + + var accessKeys []*bux.AccessKey + accessKeys, err = c.Services.Bux.GetAccessKeysByXPubID(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions), params) + if err != nil { + return nil, err + } + + return bux.DisplayModels(accessKeys).([]*bux.AccessKey), nil +} + +// AccessKeysCount is the resolver for the access_keys_count field. +func (r *queryResolver) AccessKeysCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var count int64 + count, err = c.Services.Bux.GetAccessKeysByXPubIDCount(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions)) + if err != nil { + return nil, err + } + + return &count, nil +} + +// Transaction is the resolver for the transaction field. +func (r *queryResolver) Transaction(ctx context.Context, id string) (*bux.Transaction, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var tx *bux.Transaction + tx, err = c.Services.Bux.GetTransaction(ctx, c.XPubID, id) + if err != nil { + return nil, err + } + if tx == nil { + return nil, nil + } + + return bux.DisplayModels(tx).(*bux.Transaction), nil +} + +// Transactions is the resolver for the transactions field. +func (r *queryResolver) Transactions(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.Transaction, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var tx []*bux.Transaction + tx, err = c.Services.Bux.GetTransactionsByXpubID(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions), params) + if err != nil { + return nil, err + } + + return bux.DisplayModels(tx).([]*bux.Transaction), nil +} + +// TransactionsCount is the resolver for the transactions_count field. +func (r *queryResolver) TransactionsCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var count int64 + count, err = c.Services.Bux.GetTransactionsByXpubIDCount(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions)) + if err != nil { + return nil, err + } + + return &count, nil +} + +// Destination is the resolver for the destination field. +func (r *queryResolver) Destination(ctx context.Context, id *string, address *string, lockingScript *string) (*bux.Destination, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var destination *bux.Destination + if id != nil { + destination, err = c.Services.Bux.GetDestinationByID(ctx, c.XPubID, *id) + } else if address != nil { + destination, err = c.Services.Bux.GetDestinationByAddress(ctx, c.XPubID, *address) + } else if lockingScript != nil { + destination, err = c.Services.Bux.GetDestinationByLockingScript(ctx, c.XPubID, *lockingScript) + } else { + return nil, bux.ErrMissingFieldID + } + if err != nil { + return nil, err + } + + return bux.DisplayModels(destination).(*bux.Destination), nil +} + +// Destinations is the resolver for the destinations field. +func (r *queryResolver) Destinations(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.Destination, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var destinations []*bux.Destination + destinations, err = c.Services.Bux.GetDestinationsByXpubID(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions), params) + if err != nil { + return nil, err + } + + return bux.DisplayModels(destinations).([]*bux.Destination), nil +} + +// DestinationsCount is the resolver for the destinations_count field. +func (r *queryResolver) DestinationsCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var count int64 + count, err = c.Services.Bux.GetDestinationsByXpubIDCount(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions)) + if err != nil { + return nil, err + } + + return &count, nil +} + +// Utxo is the resolver for the utxo field. +func (r *queryResolver) Utxo(ctx context.Context, txID string, outputIndex uint32) (*bux.Utxo, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var utxo *bux.Utxo + if utxo, err = c.Services.Bux.GetUtxo( + ctx, + c.XPubID, + txID, + outputIndex, + ); err != nil { + return nil, err + } + + return utxo, nil +} + +// Utxos is the resolver for the utxos field. +func (r *queryResolver) Utxos(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.Utxo, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + var utxos []*bux.Utxo + if utxos, err = c.Services.Bux.GetUtxosByXpubID( + ctx, + c.XPubID, + &metadata, + &conditions, + params, + ); err != nil { + return nil, err + } + + return utxos, nil +} + +// UtxosCount is the resolver for the utxos_count field. +func (r *queryResolver) UtxosCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { + c, err := GetConfigFromContext(ctx) + if err != nil { + return nil, err + } + + dbConditions := map[string]interface{}{} + if conditions != nil { + dbConditions = conditions + } + // force the xpub_id of the current user on query + dbConditions["xpub_id"] = c.XPubID + + var count int64 + if count, err = c.Services.Bux.GetUtxosCount( + ctx, + &metadata, + &dbConditions, + ); err != nil { + return nil, err + } + + return &count, nil +} + +// Inputs is the resolver for the inputs field. +func (r *transactionConfigInputResolver) Inputs(ctx context.Context, obj *bux.TransactionConfig, data []map[string]interface{}) error { + // do nothing with inputs + return nil +} + +// ExpiresIn is the resolver for the expires_in field. +func (r *transactionConfigInputResolver) ExpiresIn(ctx context.Context, obj *bux.TransactionConfig, data *uint64) error { + obj.ExpiresIn = time.Duration(*data) * time.Second + return nil +} + +// Mutation returns generated.MutationResolver implementation. +func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } + +// Query returns generated.QueryResolver implementation. +func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } + +// TransactionConfigInput returns generated.TransactionConfigInputResolver implementation. +func (r *Resolver) TransactionConfigInput() generated.TransactionConfigInputResolver { + return &transactionConfigInputResolver{r} +} + +type ( + mutationResolver struct{ *Resolver } + queryResolver struct{ *Resolver } + transactionConfigInputResolver struct{ *Resolver } +) diff --git a/mappings/destination.go b/mappings/destination.go index e3c255527..98832aff1 100644 --- a/mappings/destination.go +++ b/mappings/destination.go @@ -22,7 +22,6 @@ func MapToDestinationContract(d *bux.Destination) *buxmodels.Destination { Num: d.Num, Address: d.Address, DraftID: d.DraftID, - Monitor: d.Monitor.Time, } } From c8d34744823177d5caf0a5e9fadd4fbd878dc5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Lewandowski?= <35259896+pawellewandowski98@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:40:36 +0100 Subject: [PATCH 02/10] feat(BUX-417): remove unused env (#408) * chore(BUX-417): remove DefaultNote and SkipInitializeWithVersion * chore(BUX-417): update bux version --- .vscode/settings.json | 9 +++++++-- config/config.go | 2 -- config/defaults.go | 1 - config/services.go | 1 - 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index edf3fd5f1..c21ec6f24 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,5 +19,10 @@ "go.lintTool": "golangci-lint", "go.lintFlags": [ "--fast" - ] -} \ No newline at end of file + ], + "[go][go.mod]": { + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + } +} diff --git a/config/config.go b/config/config.go index 961e31105..34c40c2ff 100644 --- a/config/config.go +++ b/config/config.go @@ -189,8 +189,6 @@ type PaymailConfig struct { Beef *BeefConfig `json:"beef" mapstructure:"beef"` // DefaultFromPaymail IE: from@domain.com. DefaultFromPaymail string `json:"default_from_paymail" mapstructure:"default_from_paymail"` - // DefaultNote IE: message needed for address resolution. - DefaultNote string `json:"default_note" mapstructure:"default_note"` // Domains is a list of allowed domains. Domains []string `json:"domains" mapstructure:"domains"` // DomainValidationEnabled should be turned off if hosted domain is not paymail related. diff --git a/config/defaults.go b/config/defaults.go index f064cdd81..cdd011bf8 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -139,7 +139,6 @@ func getPaymailDefaults() *PaymailConfig { PulseAuthToken: "mQZQ6WmxURxWz5ch", // #nosec G101 }, DefaultFromPaymail: "from@domain.com", - DefaultNote: "bux Address Resolution", Domains: []string{"localhost"}, DomainValidationEnabled: true, SenderValidationEnabled: false, diff --git a/config/services.go b/config/services.go index fd9b15b2b..7e5a90e9f 100644 --- a/config/services.go +++ b/config/services.go @@ -262,7 +262,6 @@ func loadPaymail(appConfig *AppConfig, options []bux.ClientOps) []bux.ClientOps options = append(options, bux.WithPaymailSupport( pm.Domains, pm.DefaultFromPaymail, - pm.DefaultNote, pm.DomainValidationEnabled, pm.SenderValidationEnabled, )) From 01b4d780b84dd40a5fbebc5e1727e463140f877b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Lewandowski?= <35259896+pawellewandowski98@users.noreply.github.com> Date: Thu, 18 Jan 2024 13:26:04 +0100 Subject: [PATCH 03/10] feat(BUX-498): remove BlockHeader model (#411) --- actions/admin/block_headers.go | 86 ----- actions/admin/routes.go | 2 - config/services.go | 4 - graph/schema.resolvers.go | 571 --------------------------------- 4 files changed, 663 deletions(-) delete mode 100644 actions/admin/block_headers.go delete mode 100644 graph/schema.resolvers.go diff --git a/actions/admin/block_headers.go b/actions/admin/block_headers.go deleted file mode 100644 index 008659828..000000000 --- a/actions/admin/block_headers.go +++ /dev/null @@ -1,86 +0,0 @@ -package admin - -import ( - "net/http" - - "github.com/BuxOrg/bux" - "github.com/BuxOrg/bux-server/actions" - "github.com/BuxOrg/bux-server/mappings" - "github.com/julienschmidt/httprouter" - apirouter "github.com/mrz1836/go-api-router" -) - -// blockHeadersSearch will fetch a list of block headers filtered by metadata -// Search for block headers godoc -// @Summary Search for block headers -// @Description Search for block headers -// @Tags Admin -// @Param page query int false "page" -// @Param page_size query int false "page_size" -// @Param order_by_field query string false "order_by_field" -// @Param sort_direction query string false "sort_direction" -// @Param metadata query string false "Metadata filter" -// @Param conditions query string false "Conditions filter" -// @Produce json -// @Success 200 -// @Router /v1/admin/block-headers/search [post] -// @Security bux-auth-xpub -func (a *Action) blockHeadersSearch(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { - // Parse the params - params := apirouter.GetParams(req) - queryParams, metadataModel, conditions, err := actions.GetQueryParameters(params) - metadata := mappings.MapToBuxMetadata(metadataModel) - if err != nil { - apirouter.ReturnResponse(w, req, http.StatusExpectationFailed, err.Error()) - return - } - - var blockHeaders []*bux.BlockHeader - if blockHeaders, err = a.Services.Bux.GetBlockHeaders( - req.Context(), - metadata, - conditions, - queryParams, - ); err != nil { - apirouter.ReturnResponse(w, req, http.StatusExpectationFailed, err.Error()) - return - } - - // Return response - apirouter.ReturnResponse(w, req, http.StatusOK, blockHeaders) -} - -// blockHeadersCount will count all block headers filtered by metadata -// Get block headers count headers godoc -// @Summary Get block headers count -// @Description Get block headers count -// @Tags Admin -// @Param metadata query string false "Metadata filter" -// @Param conditions query string false "Conditions filter" -// @Produce json -// @Success 200 -// @Router /v1/admin/block-headers/count [post] -// @Security bux-auth-xpub -func (a *Action) blockHeadersCount(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { - // Parse the params - params := apirouter.GetParams(req) - _, metadataModel, conditions, err := actions.GetQueryParameters(params) - metadata := mappings.MapToBuxMetadata(metadataModel) - if err != nil { - apirouter.ReturnResponse(w, req, http.StatusExpectationFailed, err.Error()) - return - } - - var count int64 - if count, err = a.Services.Bux.GetBlockHeadersCount( - req.Context(), - metadata, - conditions, - ); err != nil { - apirouter.ReturnResponse(w, req, http.StatusExpectationFailed, err.Error()) - return - } - - // Return response - apirouter.ReturnResponse(w, req, http.StatusOK, count) -} diff --git a/actions/admin/routes.go b/actions/admin/routes.go index 3929110a0..53cf8d75f 100644 --- a/actions/admin/routes.go +++ b/actions/admin/routes.go @@ -25,8 +25,6 @@ func RegisterRoutes(router *apirouter.Router, appConfig *config.AppConfig, servi router.HTTPRouter.GET("/"+config.APIVersion+"/admin/status", action.Request(router, require.Wrap(action.status))) router.HTTPRouter.POST("/"+config.APIVersion+"/admin/access-keys/search", action.Request(router, require.Wrap(action.accessKeysSearch))) router.HTTPRouter.POST("/"+config.APIVersion+"/admin/access-keys/count", action.Request(router, require.Wrap(action.accessKeysCount))) - router.HTTPRouter.POST("/"+config.APIVersion+"/admin/block-headers/search", action.Request(router, require.Wrap(action.blockHeadersSearch))) - router.HTTPRouter.POST("/"+config.APIVersion+"/admin/block-headers/count", action.Request(router, require.Wrap(action.blockHeadersCount))) router.HTTPRouter.POST("/"+config.APIVersion+"/admin/destinations/search", action.Request(router, require.Wrap(action.destinationsSearch))) router.HTTPRouter.POST("/"+config.APIVersion+"/admin/destinations/count", action.Request(router, require.Wrap(action.destinationsCount))) router.HTTPRouter.POST("/"+config.APIVersion+"/admin/paymail/get", action.Request(router, require.Wrap(action.paymailGetAddress))) diff --git a/config/services.go b/config/services.go index 7e5a90e9f..d9efc7085 100644 --- a/config/services.go +++ b/config/services.go @@ -141,10 +141,6 @@ func (s *AppServices) loadBux(ctx context.Context, appConfig *AppConfig, testMod options = append(options, bux.WithUserAgent(appConfig.GetUserAgent())) - if appConfig.ImportBlockHeaders != "" { - options = append(options, bux.WithImportBlockHeaders(appConfig.ImportBlockHeaders)) - } - if logger != nil { buxLogger := logger.With().Str("service", "bux").Logger() options = append(options, bux.WithLogger(&buxLogger)) diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go deleted file mode 100644 index 4057bc1bd..000000000 --- a/graph/schema.resolvers.go +++ /dev/null @@ -1,571 +0,0 @@ -package graph - -// This file will be automatically regenerated based on the schema, any resolver implementations -// will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.34 - -import ( - "context" - "errors" - "time" - - "github.com/BuxOrg/bux" - "github.com/BuxOrg/bux-server/graph/generated" - "github.com/BuxOrg/bux/utils" - "github.com/mrz1836/go-datastore" -) - -// Xpub is the resolver for the xpub field. -func (r *mutationResolver) Xpub(ctx context.Context, xpub string, metadata bux.Metadata) (*bux.Xpub, error) { - // including admin check - c, err := GetConfigFromContextAdmin(ctx) - if err != nil { - return nil, err - } - - var existingXpub *bux.Xpub - existingXpub, err = c.Services.Bux.GetXpub(ctx, xpub) - if err != nil && !errors.Is(err, bux.ErrMissingXpub) { - return nil, err - } - if existingXpub != nil { - return nil, errors.New("xpub already exists") - } - - opts := c.Services.Bux.DefaultModelOptions() - for key, value := range metadata { - opts = append(opts, bux.WithMetadata(key, value)) - } - - // Create a new xPub - var xPub *bux.Xpub - if xPub, err = c.Services.Bux.NewXpub( - ctx, xpub, opts..., - ); err != nil { - return nil, err - } - - return bux.DisplayModels(xPub).(*bux.Xpub), nil -} - -// XpubMetadata is the resolver for the xpub_metadata field. -func (r *mutationResolver) XpubMetadata(ctx context.Context, metadata bux.Metadata) (*bux.Xpub, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var xPub *bux.Xpub - xPub, err = c.Services.Bux.UpdateXpubMetadata(ctx, c.XPubID, metadata) - if err != nil { - return nil, err - } - - if !c.Signed || c.XPub == "" { - xPub.RemovePrivateData() - } - - return bux.DisplayModels(xPub).(*bux.Xpub), nil -} - -// AccessKey is the resolver for the access_key field. -func (r *mutationResolver) AccessKey(ctx context.Context, metadata bux.Metadata) (*bux.AccessKey, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - // Create a new accessKey - var accessKey *bux.AccessKey - if accessKey, err = c.Services.Bux.NewAccessKey( - ctx, - c.XPub, - bux.WithMetadatas(metadata), - ); err != nil { - return nil, err - } - - return bux.DisplayModels(accessKey).(*bux.AccessKey), nil -} - -// AccessKeyRevoke is the resolver for the access_key_revoke field. -func (r *mutationResolver) AccessKeyRevoke(ctx context.Context, id *string) (*bux.AccessKey, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - // Revoke an accessKey - var accessKey *bux.AccessKey - if accessKey, err = c.Services.Bux.RevokeAccessKey( - ctx, - c.XPub, - *id, - ); err != nil { - return nil, err - } - - return bux.DisplayModels(accessKey).(*bux.AccessKey), nil -} - -// Transaction is the resolver for the transaction field. -func (r *mutationResolver) Transaction(ctx context.Context, hex string, draftID *string, metadata bux.Metadata) (*bux.Transaction, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - opts := c.Services.Bux.DefaultModelOptions() - for key, value := range metadata { - opts = append(opts, bux.WithMetadata(key, value)) - } - - ref := "" - if draftID != nil { - ref = *draftID - } - - var transaction *bux.Transaction - transaction, err = c.Services.Bux.RecordTransaction( - ctx, c.XPub, hex, ref, opts..., - ) - if err != nil { - if errors.Is(err, datastore.ErrDuplicateKey) { - var txID string - txID, err = utils.GetTransactionIDFromHex(hex) - if err != nil { - return nil, err - } - - transaction, err = c.Services.Bux.GetTransaction(ctx, c.XPub, txID) - if err != nil { - return nil, err - } - - // record the metadata is being added to the transaction - if len(metadata) > 0 { - xPubID := utils.Hash(c.XPub) - if transaction.XpubMetadata == nil { - transaction.XpubMetadata = make(bux.XpubMetadata) - } - if transaction.XpubMetadata[xPubID] == nil { - transaction.XpubMetadata[xPubID] = make(bux.Metadata) - } - for key, value := range metadata { - transaction.XpubMetadata[xPubID][key] = value - } - err = transaction.Save(ctx) - if err != nil { - return nil, err - } - // set metadata to the xpub metadata - is removed after Save - transaction.Metadata = transaction.XpubMetadata[xPubID] - } - - return transaction, nil - } - return nil, err - } - - return bux.DisplayModels(transaction).(*bux.Transaction), nil -} - -// TransactionMetadata is the resolver for the transaction_metadata field. -func (r *mutationResolver) TransactionMetadata(ctx context.Context, id string, metadata bux.Metadata) (*bux.Transaction, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var tx *bux.Transaction - tx, err = c.Services.Bux.UpdateTransactionMetadata(ctx, c.XPubID, id, metadata) - if err != nil { - return nil, err - } - if tx == nil { - return nil, nil - } - - return bux.DisplayModels(tx).(*bux.Transaction), nil -} - -// NewTransaction is the resolver for the new_transaction field. -func (r *mutationResolver) NewTransaction(ctx context.Context, transactionConfig bux.TransactionConfig, metadata bux.Metadata) (*bux.DraftTransaction, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - opts := c.Services.Bux.DefaultModelOptions() - if metadata != nil { - opts = append(opts, bux.WithMetadatas(metadata)) - } - - var draftTransaction *bux.DraftTransaction - draftTransaction, err = c.Services.Bux.NewTransaction(ctx, c.XPub, &transactionConfig, opts...) - if err != nil { - return nil, err - } - - return bux.DisplayModels(draftTransaction).(*bux.DraftTransaction), nil -} - -// Destination is the resolver for the destination field. -func (r *mutationResolver) Destination(ctx context.Context, destinationType *string, metadata bux.Metadata) (*bux.Destination, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - var useDestinationType string - if destinationType != nil { - useDestinationType = *destinationType - } else { - useDestinationType = utils.ScriptTypePubKeyHash - } - - opts := c.Services.Bux.DefaultModelOptions() - if metadata != nil { - opts = append(opts, bux.WithMetadatas(metadata)) - } - - var destination *bux.Destination - destination, err = c.Services.Bux.NewDestination( - ctx, - c.XPub, - utils.ChainExternal, - useDestinationType, - opts..., - ) - if err != nil { - return nil, err - } - - return bux.DisplayModels(destination).(*bux.Destination), nil -} - -// DestinationMetadata is the resolver for the destination_metadata field. -func (r *mutationResolver) DestinationMetadata(ctx context.Context, id *string, address *string, lockingScript *string, metadata bux.Metadata) (*bux.Destination, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - var destination *bux.Destination - if id != nil { - destination, err = c.Services.Bux.UpdateDestinationMetadataByID( - ctx, - c.XPubID, - *id, - metadata, - ) - } else if address != nil { - destination, err = c.Services.Bux.UpdateDestinationMetadataByAddress( - ctx, - c.XPubID, - *address, - metadata, - ) - } else if lockingScript != nil { - destination, err = c.Services.Bux.UpdateDestinationMetadataByLockingScript( - ctx, - c.XPubID, - *lockingScript, - metadata, - ) - } - if err != nil { - return nil, err - } - - return bux.DisplayModels(destination).(*bux.Destination), nil -} - -// UtxosUnreserve is the resolver for the utxos_unreserve field. -func (r *mutationResolver) UtxosUnreserve(ctx context.Context, draftID string) (*bool, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - err = c.Services.Bux.UnReserveUtxos( - ctx, - c.XPubID, - draftID, - ) - - var success bool - success = err != nil - return &success, err -} - -// Xpub is the resolver for the xpub field. -func (r *queryResolver) Xpub(ctx context.Context) (*bux.Xpub, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var xPub *bux.Xpub - xPub, err = c.Services.Bux.GetXpubByID(ctx, c.XPubID) - if err != nil { - return nil, err - } - - if !c.Signed || c.XPub == "" { - xPub.RemovePrivateData() - } - - return bux.DisplayModels(xPub).(*bux.Xpub), nil -} - -// AccessKey is the resolver for the access_key field. -func (r *queryResolver) AccessKey(ctx context.Context, key string) (*bux.AccessKey, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - var accessKey *bux.AccessKey - accessKey, err = c.Services.Bux.GetAccessKey(ctx, c.XPubID, key) - if err != nil { - return nil, err - } - - return bux.DisplayModels(accessKey).(*bux.AccessKey), nil -} - -// AccessKeys is the resolver for the access_keys field. -func (r *queryResolver) AccessKeys(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.AccessKey, error) { - c, err := GetConfigFromContextSigned(ctx) - if err != nil { - return nil, err - } - - var accessKeys []*bux.AccessKey - accessKeys, err = c.Services.Bux.GetAccessKeysByXPubID(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions), params) - if err != nil { - return nil, err - } - - return bux.DisplayModels(accessKeys).([]*bux.AccessKey), nil -} - -// AccessKeysCount is the resolver for the access_keys_count field. -func (r *queryResolver) AccessKeysCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var count int64 - count, err = c.Services.Bux.GetAccessKeysByXPubIDCount(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions)) - if err != nil { - return nil, err - } - - return &count, nil -} - -// Transaction is the resolver for the transaction field. -func (r *queryResolver) Transaction(ctx context.Context, id string) (*bux.Transaction, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var tx *bux.Transaction - tx, err = c.Services.Bux.GetTransaction(ctx, c.XPubID, id) - if err != nil { - return nil, err - } - if tx == nil { - return nil, nil - } - - return bux.DisplayModels(tx).(*bux.Transaction), nil -} - -// Transactions is the resolver for the transactions field. -func (r *queryResolver) Transactions(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.Transaction, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var tx []*bux.Transaction - tx, err = c.Services.Bux.GetTransactionsByXpubID(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions), params) - if err != nil { - return nil, err - } - - return bux.DisplayModels(tx).([]*bux.Transaction), nil -} - -// TransactionsCount is the resolver for the transactions_count field. -func (r *queryResolver) TransactionsCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var count int64 - count, err = c.Services.Bux.GetTransactionsByXpubIDCount(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions)) - if err != nil { - return nil, err - } - - return &count, nil -} - -// Destination is the resolver for the destination field. -func (r *queryResolver) Destination(ctx context.Context, id *string, address *string, lockingScript *string) (*bux.Destination, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var destination *bux.Destination - if id != nil { - destination, err = c.Services.Bux.GetDestinationByID(ctx, c.XPubID, *id) - } else if address != nil { - destination, err = c.Services.Bux.GetDestinationByAddress(ctx, c.XPubID, *address) - } else if lockingScript != nil { - destination, err = c.Services.Bux.GetDestinationByLockingScript(ctx, c.XPubID, *lockingScript) - } else { - return nil, bux.ErrMissingFieldID - } - if err != nil { - return nil, err - } - - return bux.DisplayModels(destination).(*bux.Destination), nil -} - -// Destinations is the resolver for the destinations field. -func (r *queryResolver) Destinations(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.Destination, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var destinations []*bux.Destination - destinations, err = c.Services.Bux.GetDestinationsByXpubID(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions), params) - if err != nil { - return nil, err - } - - return bux.DisplayModels(destinations).([]*bux.Destination), nil -} - -// DestinationsCount is the resolver for the destinations_count field. -func (r *queryResolver) DestinationsCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var count int64 - count, err = c.Services.Bux.GetDestinationsByXpubIDCount(ctx, c.XPubID, &metadata, ConditionsParseGraphQL(conditions)) - if err != nil { - return nil, err - } - - return &count, nil -} - -// Utxo is the resolver for the utxo field. -func (r *queryResolver) Utxo(ctx context.Context, txID string, outputIndex uint32) (*bux.Utxo, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var utxo *bux.Utxo - if utxo, err = c.Services.Bux.GetUtxo( - ctx, - c.XPubID, - txID, - outputIndex, - ); err != nil { - return nil, err - } - - return utxo, nil -} - -// Utxos is the resolver for the utxos field. -func (r *queryResolver) Utxos(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}, params *datastore.QueryParams) ([]*bux.Utxo, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - var utxos []*bux.Utxo - if utxos, err = c.Services.Bux.GetUtxosByXpubID( - ctx, - c.XPubID, - &metadata, - &conditions, - params, - ); err != nil { - return nil, err - } - - return utxos, nil -} - -// UtxosCount is the resolver for the utxos_count field. -func (r *queryResolver) UtxosCount(ctx context.Context, metadata bux.Metadata, conditions map[string]interface{}) (*int64, error) { - c, err := GetConfigFromContext(ctx) - if err != nil { - return nil, err - } - - dbConditions := map[string]interface{}{} - if conditions != nil { - dbConditions = conditions - } - // force the xpub_id of the current user on query - dbConditions["xpub_id"] = c.XPubID - - var count int64 - if count, err = c.Services.Bux.GetUtxosCount( - ctx, - &metadata, - &dbConditions, - ); err != nil { - return nil, err - } - - return &count, nil -} - -// Inputs is the resolver for the inputs field. -func (r *transactionConfigInputResolver) Inputs(ctx context.Context, obj *bux.TransactionConfig, data []map[string]interface{}) error { - // do nothing with inputs - return nil -} - -// ExpiresIn is the resolver for the expires_in field. -func (r *transactionConfigInputResolver) ExpiresIn(ctx context.Context, obj *bux.TransactionConfig, data *uint64) error { - obj.ExpiresIn = time.Duration(*data) * time.Second - return nil -} - -// Mutation returns generated.MutationResolver implementation. -func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } - -// Query returns generated.QueryResolver implementation. -func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } - -// TransactionConfigInput returns generated.TransactionConfigInputResolver implementation. -func (r *Resolver) TransactionConfigInput() generated.TransactionConfigInputResolver { - return &transactionConfigInputResolver{r} -} - -type ( - mutationResolver struct{ *Resolver } - queryResolver struct{ *Resolver } - transactionConfigInputResolver struct{ *Resolver } -) From 514404d3cb971cb6b1dc80acdc5841976ea989e0 Mon Sep 17 00:00:00 2001 From: wregulski Date: Wed, 31 Jan 2024 11:03:23 +0100 Subject: [PATCH 04/10] chore: rebase fixtures --- config/services.go | 1 + go.mod | 6 +++--- go.sum | 12 ++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/config/services.go b/config/services.go index d9efc7085..30bb109a7 100644 --- a/config/services.go +++ b/config/services.go @@ -258,6 +258,7 @@ func loadPaymail(appConfig *AppConfig, options []bux.ClientOps) []bux.ClientOps options = append(options, bux.WithPaymailSupport( pm.Domains, pm.DefaultFromPaymail, + "", pm.DomainValidationEnabled, pm.SenderValidationEnabled, )) diff --git a/go.mod b/go.mod index ca784c13d..78d52b80d 100644 --- a/go.mod +++ b/go.mod @@ -77,14 +77,14 @@ require ( github.com/libsv/go-bk v0.1.6 // indirect github.com/libsv/go-bt v1.0.8 // indirect github.com/libsv/go-bt/v2 v2.2.5 // indirect - github.com/libsv/go-p2p v0.1.5 // indirect + github.com/libsv/go-p2p v0.1.9 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matryer/respond v1.0.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect - github.com/miekg/dns v1.1.57 // indirect + github.com/miekg/dns v1.1.58 // indirect github.com/mitchellh/mapstructure v1.5.0 github.com/montanaflynn/stats v0.7.1 // indirect github.com/mrz1836/go-cache v0.9.4 // indirect @@ -130,7 +130,7 @@ require ( golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/tools v0.17.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect google.golang.org/grpc v1.61.0 // indirect google.golang.org/protobuf v1.32.0 // indirect diff --git a/go.sum b/go.sum index 12230875a..b71b7eabf 100644 --- a/go.sum +++ b/go.sum @@ -210,8 +210,8 @@ github.com/libsv/go-bt v1.0.8 h1:nWLLcnUm0dxNO3exqrL5jvAcTGkl0dsnBuQqB6+M6vQ= github.com/libsv/go-bt v1.0.8/go.mod h1:yO023bNYLh5DwcOYl+ZqLAeTemoy6K+2UbQlIBMv+EQ= github.com/libsv/go-bt/v2 v2.2.5 h1:VoggBLMRW9NYoFujqe5bSYKqnw5y+fYfufgERSoubog= github.com/libsv/go-bt/v2 v2.2.5/go.mod h1:cV45+jDlPOLfhJLfpLmpQoWzrIvVth9Ao2ZO1f6CcqU= -github.com/libsv/go-p2p v0.1.5 h1:zbUE1UQ73J7LN/s3gruQBTh3Sz0DSSmj3cWhC1ZQVM0= -github.com/libsv/go-p2p v0.1.5/go.mod h1:9KhX8e+3oEmGiYQSeF/CrHj22YNHqiof3TH77VqcMCs= +github.com/libsv/go-p2p v0.1.9 h1:cMo8FS66oMOS47lmh8yPVNm0nGIz2Zlxi/zncPD0/o8= +github.com/libsv/go-p2p v0.1.9/go.mod h1:9KhX8e+3oEmGiYQSeF/CrHj22YNHqiof3TH77VqcMCs= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -227,8 +227,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw= -github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= -github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= +github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -501,8 +501,8 @@ golang.org/x/tools v0.0.0-20200530233709-52effbd89c51/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= +golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From 11f199ee525ca602aeccbb057d443528a7bc3584 Mon Sep 17 00:00:00 2001 From: wregulski Date: Fri, 2 Feb 2024 10:49:52 +0100 Subject: [PATCH 05/10] chore: pin rc-v1.1 bux version to bux-server --- config/services.go | 1 - 1 file changed, 1 deletion(-) diff --git a/config/services.go b/config/services.go index 30bb109a7..d9efc7085 100644 --- a/config/services.go +++ b/config/services.go @@ -258,7 +258,6 @@ func loadPaymail(appConfig *AppConfig, options []bux.ClientOps) []bux.ClientOps options = append(options, bux.WithPaymailSupport( pm.Domains, pm.DefaultFromPaymail, - "", pm.DomainValidationEnabled, pm.SenderValidationEnabled, )) From 3948014351fd3d9e179053454cbd974fda2f11cf Mon Sep 17 00:00:00 2001 From: Wojciech Regulski <48433067+wregulski@users.noreply.github.com> Date: Fri, 2 Feb 2024 11:23:04 +0100 Subject: [PATCH 06/10] feat(BUX-368): Arc callbacks (#439) * chore: update gitignore * feat: add broadcast callback action with required components * chore: remove vscode folder * feat: remove config.yaml * feat: clarify the error status * feat: improve checking for bearer schema * fix: compute default calback token in proper place * fix: remove unnecessary logger dependency * feat: update config nesting * fix: remove debug replace * fix: update go.sum * feat: update go.sum * fix: correct linter issues --- .gitignore | 6 + .vscode/extensions.json | 5 - .vscode/launch.json | 14 --- .vscode/settings.json | 28 ----- .vscode/tasks.json | 17 --- actions/middleware.go | 45 ++++++- actions/transactions/broadcast_callback.go | 44 +++++++ actions/transactions/routes.go | 6 +- cmd/server/main.go | 4 + config.example.yaml | 4 + config/config.go | 20 ++- config/defaults.go | 13 +- config/services.go | 11 ++ dictionary/dictionary.go | 3 +- docs/docs.go | 134 ++++++++++++++++++++- docs/swagger.json | 129 ++++++++++++++++++++ docs/swagger.yaml | 101 ++++++++++++++++ 17 files changed, 502 insertions(+), 82 deletions(-) delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/settings.json delete mode 100644 .vscode/tasks.json create mode 100644 actions/transactions/broadcast_callback.go diff --git a/.gitignore b/.gitignore index e8fff3b5c..72d938f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,9 @@ data/ # Configuration files .env.config + +# VSCode +.vscode/ + +# Ignore config file +config.yaml diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index e14366944..000000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "recommendations": [ - "golang.Go" - ] -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 520e551dd..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Launch", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}", - "env": {}, - "args": [] - } - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c21ec6f24..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "go.useLanguageServer": true, - "gopls": { - "gofumpt": true - }, - "[go]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - } - }, - "[go.mod]": { - "editor.formatOnSave": true, - "editor.codeActionsOnSave": { - "source.organizeImports": true - } - }, - - "go.lintTool": "golangci-lint", - "go.lintFlags": [ - "--fast" - ], - "[go][go.mod]": { - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - } -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 8f3da7d51..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "make", - "type": "shell", - "command": "make", - "problemMatcher": [ - "$go" - ], - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} \ No newline at end of file diff --git a/actions/middleware.go b/actions/middleware.go index 5be98c929..3cccd92cd 100644 --- a/actions/middleware.go +++ b/actions/middleware.go @@ -2,6 +2,7 @@ package actions import ( "net/http" + "strings" "time" "github.com/BuxOrg/bux" @@ -21,14 +22,14 @@ type Action struct { // NewStack is used for registering routes func NewStack(appConfig *config.AppConfig, - services *config.AppServices) (Action, *apirouter.InternalStack) { + services *config.AppServices, +) (Action, *apirouter.InternalStack) { return Action{AppConfig: appConfig, Services: services}, apirouter.NewStack() } // RequireAuthentication checks and requires authentication for the related method func (a *Action) RequireAuthentication(fn httprouter.Handle) httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { - // Check the authentication var knownErr dictionary.ErrorMessage if req, knownErr = CheckAuthentication(a.AppConfig, a.Services.Bux, req, false, true); knownErr.Code > 0 { @@ -44,7 +45,6 @@ func (a *Action) RequireAuthentication(fn httprouter.Handle) httprouter.Handle { // RequireBasicAuthentication checks and requires authentication for the related method, but does not require signing func (a *Action) RequireBasicAuthentication(fn httprouter.Handle) httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { - // Check the authentication var knownErr dictionary.ErrorMessage if req, knownErr = CheckAuthentication(a.AppConfig, a.Services.Bux, req, false, false); knownErr.Code > 0 { @@ -60,7 +60,6 @@ func (a *Action) RequireBasicAuthentication(fn httprouter.Handle) httprouter.Han // RequireAdminAuthentication checks and requires ADMIN authentication for the related method func (a *Action) RequireAdminAuthentication(fn httprouter.Handle) httprouter.Handle { return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { - // Check the authentication var knownErr dictionary.ErrorMessage if req, knownErr = CheckAuthentication(a.AppConfig, a.Services.Bux, req, true, true); knownErr.Code > 0 { @@ -73,6 +72,20 @@ func (a *Action) RequireAdminAuthentication(fn httprouter.Handle) httprouter.Han } } +// RequireCallbackAuthentication checks and requires callback Authorize Bearer token for the related method +func (a *Action) RequireCallbackAuthentication(fn httprouter.Handle) httprouter.Handle { + return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { + var knownErr dictionary.ErrorMessage + if req, knownErr = VerifyCallbackToken(a.AppConfig, req); knownErr.Code > 0 { + ReturnErrorResponse(w, req, knownErr, "") + return + } + + // Continue to next method + fn(w, req, p) + } +} + // Request will process the request in the router func (a *Action) Request(_ *apirouter.Router, h httprouter.Handle) httprouter.Handle { return Request(h, a) @@ -80,8 +93,8 @@ func (a *Action) Request(_ *apirouter.Router, h httprouter.Handle) httprouter.Ha // CheckAuthentication will check the authentication func CheckAuthentication(appConfig *config.AppConfig, bux bux.ClientInterface, req *http.Request, - adminRequired bool, requireSigning bool) (*http.Request, dictionary.ErrorMessage) { - + adminRequired bool, requireSigning bool, +) (*http.Request, dictionary.ErrorMessage) { // Bad/Unknown scheme if appConfig.Authentication.Scheme != config.AuthenticationSchemeXpub { return req, dictionary.GetError(dictionary.ErrorAuthenticationScheme, appConfig.Authentication.Scheme) @@ -102,6 +115,26 @@ func CheckAuthentication(appConfig *config.AppConfig, bux bux.ClientInterface, r return req, dictionary.ErrorMessage{} } +// VerifyCallbackToken verifies the callback token - if it's valid and matches the Bearer scheme. +func VerifyCallbackToken(appConfig *config.AppConfig, req *http.Request) (*http.Request, dictionary.ErrorMessage) { + const BearerSchema = "Bearer " + authHeader := req.Header.Get("Authorization") + if authHeader == "" { + return req, dictionary.GetError(dictionary.ErrorAuthenticationCallback, "missing auth header") + } + + if !strings.HasPrefix(authHeader, BearerSchema) || len(authHeader) <= len(BearerSchema) { + return req, dictionary.GetError(dictionary.ErrorAuthenticationCallback, "invalid or missing bearer token") + } + + providedToken := authHeader[len(BearerSchema):] + if providedToken != appConfig.Nodes.Callback.CallbackToken { + return req, dictionary.GetError(dictionary.ErrorAuthenticationCallback, "invalid authorization token") + } + + return req, dictionary.ErrorMessage{} +} + // Request will write the request to the logs before and after calling the handler func Request(h httprouter.Handle, a *Action) httprouter.Handle { return parameters.MakeHTTPRouterParsedReq(func(w http.ResponseWriter, req *http.Request, ps httprouter.Params) { diff --git a/actions/transactions/broadcast_callback.go b/actions/transactions/broadcast_callback.go new file mode 100644 index 000000000..34042b48c --- /dev/null +++ b/actions/transactions/broadcast_callback.go @@ -0,0 +1,44 @@ +package transactions + +import ( + "encoding/json" + "net/http" + + "github.com/bitcoin-sv/go-broadcast-client/broadcast" + "github.com/julienschmidt/httprouter" + apirouter "github.com/mrz1836/go-api-router" +) + +// broadcastCallback will handle a broadcastCallback call from the broadcast api +// Broadcast Callback godoc +// @Summary Broadcast Callback +// @Tags Transactions +// @Param transaction body broadcast.SubmittedTx true "transaction" +// @Success 200 +// @Router /transaction/broadcast/callback [post] +// @Security callback-auth +func (a *Action) broadcastCallback(w http.ResponseWriter, req *http.Request, _ httprouter.Params) { + var resp *broadcast.SubmittedTx + + err := json.NewDecoder(req.Body).Decode(&resp) + if err != nil { + apirouter.ReturnResponse(w, req, http.StatusExpectationFailed, err.Error()) + return + } + + defer func() { + if err = req.Body.Close(); err != nil { + a.Services.Logger.Err(err).Msg("failed to close request body") + } + }() + + err = a.Services.Bux.UpdateTransaction(req.Context(), resp) + if err != nil { + a.Services.Logger.Err(err).Msgf("failed to update transaction - tx: %v", resp) + apirouter.ReturnResponse(w, req, http.StatusInternalServerError, "") + return + } + + // Return response + apirouter.ReturnResponse(w, req, http.StatusOK, "") +} diff --git a/actions/transactions/routes.go b/actions/transactions/routes.go index 5b5f7faff..d8a9123e8 100644 --- a/actions/transactions/routes.go +++ b/actions/transactions/routes.go @@ -13,7 +13,6 @@ type Action struct { // RegisterRoutes register all the package specific routes func RegisterRoutes(router *apirouter.Router, appConfig *config.AppConfig, services *config.AppServices) { - // Use the authentication middleware wrapper a, require := actions.NewStack(appConfig, services) require.Use(a.RequireAuthentication) @@ -22,6 +21,10 @@ func RegisterRoutes(router *apirouter.Router, appConfig *config.AppConfig, servi aBasic, requireBasic := actions.NewStack(appConfig, services) requireBasic.Use(aBasic.RequireBasicAuthentication) + // Use the callback middleware wrapper + aCallback, requireCallback := actions.NewStack(appConfig, services) + requireCallback.Use(aCallback.RequireCallbackAuthentication) + // Load the actions and set the services action := &Action{actions.Action{AppConfig: a.AppConfig, Services: a.Services}} @@ -33,4 +36,5 @@ func RegisterRoutes(router *apirouter.Router, appConfig *config.AppConfig, servi router.HTTPRouter.POST("/"+config.APIVersion+"/transaction", action.Request(router, require.Wrap(action.newTransaction))) router.HTTPRouter.POST("/"+config.APIVersion+"/transaction/record", action.Request(router, require.Wrap(action.record))) router.HTTPRouter.POST("/"+config.APIVersion+"/transaction/search", action.Request(router, requireBasic.Wrap(action.search))) + router.HTTPRouter.POST(config.BroadcastCallbackRoute, action.Request(router, requireCallback.Wrap(action.broadcastCallback))) } diff --git a/cmd/server/main.go b/cmd/server/main.go index caf3b57f6..5d2924299 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -22,6 +22,10 @@ import ( // @securityDefinitions.apikey bux-auth-xpub // @in header // @name bux-auth-xpub + +// @securityDefinitions.apikey callback-auth +// @in header +// @name authorization func main() { defaultLogger := logging.GetDefaultLogger() diff --git a/config.example.yaml b/config.example.yaml index 06e2158ce..f2e32ade2 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -84,6 +84,10 @@ new_relic: nodes: # deployment id used annotating api calls in XDeployment-ID header - this value will be randomly generated if not set _deployment_id: bux-deployment-id + callback: + callback_host: https://xyz.com + # token to authenticate callback calls - default callback token will be generated from the Admin Key + _callback_token: 44a82509 # protocol - arc/mapi (arc is default) protocol: arc # list of apis used for getting and broadcasting transactions diff --git a/config/config.go b/config/config.go index 34c40c2ff..60699733b 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ const ( ConfigFilePathKey = "config_file" DefaultConfigFilePath = "config.yaml" ConfigEnvPrefix = "BUX_" + BroadcastCallbackRoute = "/transaction/broadcast/callback" ) // AppConfig is the configuration values and associated env vars @@ -78,6 +79,14 @@ type CacheConfig struct { Redis *RedisConfig `json:"redis" mapstructure:"redis"` } +// CallbackConfig is the configuration for callbacks +type CallbackConfig struct { + // CallbackHost is the URL for broadcast callback registration. + CallbackHost string `json:"callback_host" mapstructure:"callback_host"` + // CallbackToken is the token for broadcast callback registration. + CallbackToken string `json:"callback_token" mapstructure:"callback_token"` +} + // ClusterConfig is a configuration for the Bux cluster type ClusterConfig struct { // Coordinator is a cluster coordinator (redis or memory). @@ -140,11 +149,12 @@ type NewRelicConfig struct { // NodesConfig consists of blockchain nodes (such as Minercraft and Arc) configuration type NodesConfig struct { - DeploymentID string `json:"deployment_id" mapstructure:"deployment_id"` - Protocol NodesProtocol `json:"protocol" mapstructure:"protocol"` - Apis []*MinerAPI `json:"apis" mapstructure:"apis"` - UseFeeQuotes bool `json:"use_fee_quotes" mapstructure:"use_fee_quotes"` - FeeUnit *FeeUnitConfig `json:"fee_unit" mapstructure:"fee_unit"` + DeploymentID string `json:"deployment_id" mapstructure:"deployment_id"` + Callback *CallbackConfig `json:"callback" mapstructure:"callback"` + Protocol NodesProtocol `json:"protocol" mapstructure:"protocol"` + Apis []*MinerAPI `json:"apis" mapstructure:"apis"` + UseFeeQuotes bool `json:"use_fee_quotes" mapstructure:"use_fee_quotes"` + FeeUnit *FeeUnitConfig `json:"fee_unit" mapstructure:"fee_unit"` } // FeeUnitConfig reflects the utils.FeeUnit struct with proper annotations for json and mapstructure diff --git a/config/defaults.go b/config/defaults.go index cdd011bf8..421d03b8a 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -7,6 +7,9 @@ import ( "github.com/mrz1836/go-datastore" ) +// DefaultAdminXpub is the default admin xpub used for authenticate requests. +const DefaultAdminXpub = "xpub661MyMwAqRbcFgfmdkPgE2m5UjHXu9dj124DbaGLSjaqVESTWfCD4VuNmEbVPkbYLCkykwVZvmA8Pbf8884TQr1FgdG2nPoHR8aB36YdDQh" + func getDefaultAppConfig() *AppConfig { return &AppConfig{ Authentication: getAuthConfigDefaults(), @@ -29,7 +32,7 @@ func getDefaultAppConfig() *AppConfig { func getAuthConfigDefaults() *AuthenticationConfig { return &AuthenticationConfig{ - AdminKey: "xpub661MyMwAqRbcFgfmdkPgE2m5UjHXu9dj124DbaGLSjaqVESTWfCD4VuNmEbVPkbYLCkykwVZvmA8Pbf8884TQr1FgdG2nPoHR8aB36YdDQh", + AdminKey: DefaultAdminXpub, RequireSigning: false, Scheme: "xpub", SigningDisabled: true, @@ -56,6 +59,13 @@ func getCacheDefaults() *CacheConfig { } } +func getCallbackDefaults() *CallbackConfig { + return &CallbackConfig{ + CallbackHost: "http://localhost:3003", + CallbackToken: "", + } +} + func getDbDefaults() *DbConfig { return &DbConfig{ Datastore: &DatastoreConfig{ @@ -113,6 +123,7 @@ func getNodesDefaults() *NodesConfig { return &NodesConfig{ DeploymentID: "bux-" + depIDSufix.String(), Protocol: NodesProtocolArc, + Callback: getCallbackDefaults(), Apis: []*MinerAPI{ { ArcURL: "https://api.taal.com/arc", diff --git a/config/services.go b/config/services.go index d9efc7085..b693f240c 100644 --- a/config/services.go +++ b/config/services.go @@ -175,6 +175,17 @@ func (s *AppServices) loadBux(ctx context.Context, appConfig *AppConfig, testMod options = loadBroadcastClientArc(appConfig, options, logger) } + if appConfig.Nodes.Callback.CallbackToken == "" { + var callbackToken string + callbackToken, err = utils.HashAdler32(DefaultAdminXpub) + if err != nil { + logger.Err(err).Msg("unable to compute default callback token") + } + appConfig.Nodes.Callback.CallbackToken = callbackToken + } + + options = append(options, bux.WithCallback(appConfig.Nodes.Callback.CallbackHost+BroadcastCallbackRoute, appConfig.Nodes.Callback.CallbackToken)) + options = append(options, bux.WithFeeQuotes(appConfig.Nodes.UseFeeQuotes)) if appConfig.Nodes.FeeUnit != nil { diff --git a/dictionary/dictionary.go b/dictionary/dictionary.go index 4979ff92c..92805051d 100644 --- a/dictionary/dictionary.go +++ b/dictionary/dictionary.go @@ -78,6 +78,7 @@ const ( ErrorAuthenticationError = 16 ErrorAuthenticationScheme = 17 ErrorAuthenticationNotAdmin = 18 + ErrorAuthenticationCallback = 19 errorCodeLast = iota ) @@ -89,7 +90,6 @@ func (e ErrorCode) IsValid() bool { // Load all error messages on init func init() { - // Create all the standard error messages errorMessages = make(map[ErrorCode]ErrorMessage, errorCodeLast) @@ -122,4 +122,5 @@ func init() { errorMessages[ErrorAuthenticationError] = ErrorMessage{Code: ErrorAuthenticationError, InternalMessage: "authentication error: %s", PublicMessage: "authentication failed", StatusCode: http.StatusUnauthorized} errorMessages[ErrorAuthenticationScheme] = ErrorMessage{Code: ErrorAuthenticationScheme, InternalMessage: "authentication scheme unknown: %s", PublicMessage: "authentication failed", StatusCode: http.StatusUnauthorized} errorMessages[ErrorAuthenticationNotAdmin] = ErrorMessage{Code: ErrorAuthenticationNotAdmin, InternalMessage: "xpub provided is not an admin key: %s", PublicMessage: "authentication failed", StatusCode: http.StatusUnauthorized} + errorMessages[ErrorAuthenticationCallback] = ErrorMessage{Code: ErrorAuthenticationCallback, InternalMessage: "callback authentication failed: %s", PublicMessage: "authentication failed", StatusCode: http.StatusUnauthorized} } diff --git a/docs/docs.go b/docs/docs.go index 1f137cb2b..85d4dc03e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,5 +1,4 @@ -// Code generated by swaggo/swag. DO NOT EDIT. - +// Code generated by swaggo/swag. DO NOT EDIT package docs import "github.com/swaggo/swag" @@ -16,6 +15,35 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "paths": { + "/transaction/broadcast/callback": { + "post": { + "security": [ + { + "callback-auth": [] + } + ], + "tags": [ + "Transactions" + ], + "summary": "Broadcast Callback", + "parameters": [ + { + "description": "transaction", + "name": "transaction", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/broadcast.SubmittedTx" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/v1/access-key": { "get": { "security": [ @@ -1874,11 +1902,111 @@ const docTemplate = `{ } } }, + "definitions": { + "broadcast.SubmittedTx": { + "type": "object", + "properties": { + "blockHash": { + "description": "BlockHash is the hash of the block where the transaction was included.", + "type": "string" + }, + "blockHeight": { + "description": "BlockHeight is the height of the block where the transaction was included.", + "type": "integer" + }, + "extraInfo": { + "description": "ExtraInfo provides extra information for given transaction.", + "type": "string" + }, + "merklePath": { + "description": "MerklePath is the Merkle path used to calculate Merkle root of the block in which the transaction was included.", + "type": "string" + }, + "status": { + "description": "Status is the status of the response.", + "type": "integer" + }, + "timestamp": { + "description": "Timestamp is the timestamp of the block where the transaction was included.", + "type": "string" + }, + "title": { + "description": "Title is the title of the response.", + "type": "string" + }, + "txStatus": { + "description": "TxStatus is the status of the transaction.", + "allOf": [ + { + "$ref": "#/definitions/broadcast.TxStatus" + } + ] + }, + "txid": { + "description": "TxID is the transaction id.", + "type": "string" + } + } + }, + "broadcast.TxStatus": { + "type": "string", + "enum": [ + "UNKNOWN", + "QUEUED", + "RECEIVED", + "STORED", + "ANNOUNCED_TO_NETWORK", + "REQUESTED_BY_NETWORK", + "SENT_TO_NETWORK", + "ACCEPTED_BY_NETWORK", + "SEEN_ON_NETWORK", + "MINED", + "SEEN_IN_ORPHAN_MEMPOOL", + "CONFIRMED", + "REJECTED" + ], + "x-enum-comments": { + "AcceptedByNetwork": "7", + "AnnouncedToNetwork": "4", + "Confirmed": "108", + "Mined": "9", + "Queued": "1", + "Received": "2", + "Rejected": "109", + "RequestedByNetwork": "5", + "SeenInOrphanMempool": "10", + "SeenOnNetwork": "8", + "SentToNetwork": "6", + "Stored": "3", + "Unknown": "0" + }, + "x-enum-varnames": [ + "Unknown", + "Queued", + "Received", + "Stored", + "AnnouncedToNetwork", + "RequestedByNetwork", + "SentToNetwork", + "AcceptedByNetwork", + "SeenOnNetwork", + "Mined", + "SeenInOrphanMempool", + "Confirmed", + "Rejected" + ] + } + }, "securityDefinitions": { "bux-auth-xpub": { "type": "apiKey", "name": "bux-auth-xpub", "in": "header" + }, + "callback-auth": { + "type": "apiKey", + "name": "authorization", + "in": "header" } } }` @@ -1893,8 +2021,6 @@ var SwaggerInfo = &swag.Spec{ Description: "", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, - LeftDelim: "{{", - RightDelim: "}}", } func init() { diff --git a/docs/swagger.json b/docs/swagger.json index a60581e9f..af4d5ac32 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -6,6 +6,35 @@ "version": "v0.12.0" }, "paths": { + "/transaction/broadcast/callback": { + "post": { + "security": [ + { + "callback-auth": [] + } + ], + "tags": [ + "Transactions" + ], + "summary": "Broadcast Callback", + "parameters": [ + { + "description": "transaction", + "name": "transaction", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/broadcast.SubmittedTx" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/v1/access-key": { "get": { "security": [ @@ -1864,11 +1893,111 @@ } } }, + "definitions": { + "broadcast.SubmittedTx": { + "type": "object", + "properties": { + "blockHash": { + "description": "BlockHash is the hash of the block where the transaction was included.", + "type": "string" + }, + "blockHeight": { + "description": "BlockHeight is the height of the block where the transaction was included.", + "type": "integer" + }, + "extraInfo": { + "description": "ExtraInfo provides extra information for given transaction.", + "type": "string" + }, + "merklePath": { + "description": "MerklePath is the Merkle path used to calculate Merkle root of the block in which the transaction was included.", + "type": "string" + }, + "status": { + "description": "Status is the status of the response.", + "type": "integer" + }, + "timestamp": { + "description": "Timestamp is the timestamp of the block where the transaction was included.", + "type": "string" + }, + "title": { + "description": "Title is the title of the response.", + "type": "string" + }, + "txStatus": { + "description": "TxStatus is the status of the transaction.", + "allOf": [ + { + "$ref": "#/definitions/broadcast.TxStatus" + } + ] + }, + "txid": { + "description": "TxID is the transaction id.", + "type": "string" + } + } + }, + "broadcast.TxStatus": { + "type": "string", + "enum": [ + "UNKNOWN", + "QUEUED", + "RECEIVED", + "STORED", + "ANNOUNCED_TO_NETWORK", + "REQUESTED_BY_NETWORK", + "SENT_TO_NETWORK", + "ACCEPTED_BY_NETWORK", + "SEEN_ON_NETWORK", + "MINED", + "SEEN_IN_ORPHAN_MEMPOOL", + "CONFIRMED", + "REJECTED" + ], + "x-enum-comments": { + "AcceptedByNetwork": "7", + "AnnouncedToNetwork": "4", + "Confirmed": "108", + "Mined": "9", + "Queued": "1", + "Received": "2", + "Rejected": "109", + "RequestedByNetwork": "5", + "SeenInOrphanMempool": "10", + "SeenOnNetwork": "8", + "SentToNetwork": "6", + "Stored": "3", + "Unknown": "0" + }, + "x-enum-varnames": [ + "Unknown", + "Queued", + "Received", + "Stored", + "AnnouncedToNetwork", + "RequestedByNetwork", + "SentToNetwork", + "AcceptedByNetwork", + "SeenOnNetwork", + "Mined", + "SeenInOrphanMempool", + "Confirmed", + "Rejected" + ] + } + }, "securityDefinitions": { "bux-auth-xpub": { "type": "apiKey", "name": "bux-auth-xpub", "in": "header" + }, + "callback-auth": { + "type": "apiKey", + "name": "authorization", + "in": "header" } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9c3a1338c..d50c4f6a0 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,8 +1,105 @@ +definitions: + broadcast.SubmittedTx: + properties: + blockHash: + description: BlockHash is the hash of the block where the transaction was + included. + type: string + blockHeight: + description: BlockHeight is the height of the block where the transaction + was included. + type: integer + extraInfo: + description: ExtraInfo provides extra information for given transaction. + type: string + merklePath: + description: MerklePath is the Merkle path used to calculate Merkle root of + the block in which the transaction was included. + type: string + status: + description: Status is the status of the response. + type: integer + timestamp: + description: Timestamp is the timestamp of the block where the transaction + was included. + type: string + title: + description: Title is the title of the response. + type: string + txStatus: + allOf: + - $ref: '#/definitions/broadcast.TxStatus' + description: TxStatus is the status of the transaction. + txid: + description: TxID is the transaction id. + type: string + type: object + broadcast.TxStatus: + enum: + - UNKNOWN + - QUEUED + - RECEIVED + - STORED + - ANNOUNCED_TO_NETWORK + - REQUESTED_BY_NETWORK + - SENT_TO_NETWORK + - ACCEPTED_BY_NETWORK + - SEEN_ON_NETWORK + - MINED + - SEEN_IN_ORPHAN_MEMPOOL + - CONFIRMED + - REJECTED + type: string + x-enum-comments: + AcceptedByNetwork: "7" + AnnouncedToNetwork: "4" + Confirmed: "108" + Mined: "9" + Queued: "1" + Received: "2" + Rejected: "109" + RequestedByNetwork: "5" + SeenInOrphanMempool: "10" + SeenOnNetwork: "8" + SentToNetwork: "6" + Stored: "3" + Unknown: "0" + x-enum-varnames: + - Unknown + - Queued + - Received + - Stored + - AnnouncedToNetwork + - RequestedByNetwork + - SentToNetwork + - AcceptedByNetwork + - SeenOnNetwork + - Mined + - SeenInOrphanMempool + - Confirmed + - Rejected info: contact: {} title: 'BUX: Server' version: v0.12.0 paths: + /transaction/broadcast/callback: + post: + parameters: + - description: transaction + in: body + name: transaction + required: true + schema: + $ref: '#/definitions/broadcast.SubmittedTx' + responses: + "200": + description: OK + security: + - callback-auth: [] + summary: Broadcast Callback + tags: + - Transactions /v1/access-key: delete: description: Revoke access key @@ -1166,4 +1263,8 @@ securityDefinitions: in: header name: bux-auth-xpub type: apiKey + callback-auth: + in: header + name: authorization + type: apiKey swagger: "2.0" From deb37fed8a4729399172ecd0295e653c7e23b8d9 Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Mon, 12 Feb 2024 07:18:40 +0100 Subject: [PATCH 07/10] feat(BUX-566): introduce prometheus --- config.example.yaml | 7 +++-- config/config.go | 8 +++++ config/defaults.go | 7 +++++ config/services.go | 6 ++++ go.mod | 12 +++++-- go.sum | 18 ++++++++--- metrics/collector.go | 74 ++++++++++++++++++++++++++++++++++++++++++++ metrics/global.go | 18 +++++++++++ metrics/metrics.go | 23 ++++++++++++++ metrics/naming.go | 3 ++ 10 files changed, 168 insertions(+), 8 deletions(-) create mode 100644 metrics/collector.go create mode 100644 metrics/global.go create mode 100644 metrics/metrics.go create mode 100644 metrics/naming.go diff --git a/config.example.yaml b/config.example.yaml index f2e32ade2..b67369a5e 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -93,8 +93,8 @@ nodes: # list of apis used for getting and broadcasting transactions apis: # gorillapool can be used as well -# - arc_url: https://arc.gorillapool.io -# - token: "" + # - arc_url: https://arc.gorillapool.io + # - token: "" - arc_url: https://api.taal.com/arc/v1 token: mainnet_06770f425eb00298839a24a49cbdc02c # use fee quotes for transaction fee calculation @@ -139,3 +139,6 @@ server_config: task_manager: # task manager factory - memory, redis factory: memory +# Prometheus metrics configuration +metrics: + enabled: false diff --git a/config/config.go b/config/config.go index 60699733b..4408bea05 100644 --- a/config/config.go +++ b/config/config.go @@ -55,6 +55,8 @@ type AppConfig struct { Server *ServerConfig `json:"server_config" mapstructure:"server_config"` // TaskManager is a configuration for Task Manager in bux. TaskManager *TaskManagerConfig `json:"task_manager" mapstructure:"task_manager"` + // Metrics is a configuration for metrics in bux. + Metrics *MetricsConfig `json:"metrics" mapstructure:"metrics"` } // AuthenticationConfig is the configuration for Authentication @@ -239,6 +241,12 @@ type ServerConfig struct { Port int `json:"port" mapstructure:"port"` } +// MetricsConfig represents a metrics config. +type MetricsConfig struct { + // Enabled is a flag for enabling metrics. + Enabled bool `json:"enabled" mapstructure:"enabled"` +} + // GetUserAgent will return the outgoing user agent func (a *AppConfig) GetUserAgent() string { return "BUX-Server " + Version diff --git a/config/defaults.go b/config/defaults.go index 421d03b8a..61747783e 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -27,6 +27,7 @@ func getDefaultAppConfig() *AppConfig { RequestLogging: true, Server: getServerDefaults(), TaskManager: getTaskManagerDefault(), + Metrics: getMetricsDefaults(), } } @@ -170,3 +171,9 @@ func getServerDefaults() *ServerConfig { Port: 3003, } } + +func getMetricsDefaults() *MetricsConfig { + return &MetricsConfig{ + Enabled: false, + } +} diff --git a/config/services.go b/config/services.go index b693f240c..6ea8dc643 100644 --- a/config/services.go +++ b/config/services.go @@ -10,6 +10,7 @@ import ( "github.com/BuxOrg/bux" "github.com/BuxOrg/bux-server/logging" + "github.com/BuxOrg/bux-server/metrics" "github.com/BuxOrg/bux/cluster" "github.com/BuxOrg/bux/taskmanager" "github.com/BuxOrg/bux/utils" @@ -139,6 +140,11 @@ func (s *AppServices) loadBux(ctx context.Context, appConfig *AppConfig, testMod options = append(options, bux.WithNewRelic(s.NewRelic)) } + if appConfig.Metrics.Enabled { + collector := metrics.EnableMetrics() + options = append(options, bux.WithMetrics(collector)) + } + options = append(options, bux.WithUserAgent(appConfig.GetUserAgent())) if logger != nil { diff --git a/go.mod b/go.mod index 78d52b80d..6a248c2b6 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/BuxOrg/bux-server go 1.21.5 require ( - github.com/BuxOrg/bux v0.14.3 + github.com/BuxOrg/bux v0.14.4 github.com/BuxOrg/bux-models v0.3.0 github.com/bitcoin-sv/go-broadcast-client v0.17.2 github.com/go-ozzo/ozzo-validation v3.6.0+incompatible @@ -18,12 +18,21 @@ require ( github.com/mrz1836/go-validate v0.2.1 github.com/newrelic/go-agent/v3 v3.29.1 github.com/newrelic/go-agent/v3/integrations/nrhttprouter v1.0.2 + github.com/prometheus/client_golang v1.18.0 github.com/rs/zerolog v1.32.0 github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.8.4 github.com/swaggo/swag v1.16.3 ) +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.45.0 // indirect + github.com/prometheus/procfs v0.12.0 // indirect +) + require ( github.com/99designs/gqlgen v0.17.43 // indirect github.com/KyleBanks/depth v1.2.1 // indirect @@ -72,7 +81,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.17.6 // indirect - github.com/korovkin/limiter v0.0.0-20230307205149-3d4b2b34c99d // indirect github.com/libsv/go-bc v0.1.26 // indirect github.com/libsv/go-bk v0.1.6 // indirect github.com/libsv/go-bt v1.0.8 // indirect diff --git a/go.sum b/go.sum index b71b7eabf..354bf1f03 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/99designs/gqlgen v0.17.43 h1:I4SYg6ahjowErAQcHFVKy5EcWuwJ3+Xw9z2fLpuFCPo= github.com/99designs/gqlgen v0.17.43/go.mod h1:lO0Zjy8MkZgBdv4T1U91x09r0e0WFOdhVUutlQs1Rsc= -github.com/BuxOrg/bux v0.14.3 h1:9ntXErIZF8FJEUxAEEn65oV+Dg40pvmc9xD4zB9OL+I= -github.com/BuxOrg/bux v0.14.3/go.mod h1:4Ku9S/zi4J9Gz1FNy7qJD6zNYcEmbbrx4MgrjSWlvAY= +github.com/BuxOrg/bux v0.14.4 h1:gNvfHnmWUpzftD1qLmtTgKyJZu3vaSCcdvJ58GdKlLA= +github.com/BuxOrg/bux v0.14.4/go.mod h1:sDeOTJshHA0rduuag5NGnMUEe0oC/gVKKPuHlPIMpYI= github.com/BuxOrg/bux-models v0.3.0 h1:+pvpDdYaiIgeOhAO847RK07Vzc+vuUvy2ok/xJevQyg= github.com/BuxOrg/bux-models v0.3.0/go.mod h1:JCpoXxVnKZmCy3rg6nOtLQL5AXh6iEo2tBvcD/qAxEY= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= @@ -22,6 +22,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go v1.43.45 h1:2708Bj4uV+ym62MOtBnErm/CDX61C4mFe9V2gXy1caE= github.com/aws/aws-sdk-go v1.43.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bitcoin-sv/go-broadcast-client v0.17.2 h1:ODeuebBdHTptzdZVzq5cEgDSPQZYkcTy8MruYlD25pI= github.com/bitcoin-sv/go-broadcast-client v0.17.2/go.mod h1:GRAliwumNBjEbLRIEkXqIKJpsgmMfjvlIDqgyw/NoJE= github.com/bitcoin-sv/go-paymail v0.12.1 h1:MDdMFFOZalymT5O5WDUN0EVVWdn3ygo6EhKsWimkM/E= @@ -189,8 +191,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI= github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= -github.com/korovkin/limiter v0.0.0-20230307205149-3d4b2b34c99d h1:7CfsXfFpCG1wrUpuyOzG8+vpL1ZqH2goz23wZ9pboGE= -github.com/korovkin/limiter v0.0.0-20230307205149-3d4b2b34c99d/go.mod h1:3NeYeWwAOTnDChps1fD7YGD/uWzp+tqmShgjhhMIHDM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -227,6 +227,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/goveralls v0.0.6/go.mod h1:h8b4ow6FxSPMQHF6o2ve3qsclnffZjYTNEKmLesRwqw= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= +github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= @@ -279,6 +281,14 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= +github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= +github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= +github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= +github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/rafaeljusto/redigomock v2.4.0+incompatible h1:d7uo5MVINMxnRr20MxbgDkmZ8QRfevjOVgEa4n0OZyY= github.com/rafaeljusto/redigomock v2.4.0+incompatible/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= diff --git a/metrics/collector.go b/metrics/collector.go new file mode 100644 index 000000000..db58af57a --- /dev/null +++ b/metrics/collector.go @@ -0,0 +1,74 @@ +package metrics + +import ( + buxmetrics "github.com/BuxOrg/bux/metrics" + "github.com/prometheus/client_golang/prometheus" +) + +// PrometheusCollector is a collector for Prometheus metrics. It should implement buxmetrics.Collector. +type PrometheusCollector struct { + reg prometheus.Registerer +} + +// NewPrometheusCollector creates a new PrometheusCollector. +func NewPrometheusCollector(reg prometheus.Registerer) buxmetrics.Collector { + return &PrometheusCollector{reg: reg} +} + +// RegisterGauge creates a new Gauge and registers it with the collector. +func (c *PrometheusCollector) RegisterGauge(name string) buxmetrics.GaugeInterface { + g := prometheus.NewGauge( + prometheus.GaugeOpts{ + Name: name, + Help: "Gauge of " + name, + }, + ) + c.reg.MustRegister(g) + return g +} + +// RegisterGaugeVec creates a new GaugeVec and registers it with the collector. +func (c *PrometheusCollector) RegisterGaugeVec(name string, labels ...string) buxmetrics.GaugeVecInterface { + g := prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: name, + Help: "GaugeVec of " + name, + }, + labels, + ) + c.reg.MustRegister(g) + return &GaugeVecWrapper{g} +} + +// RegisterHistogramVec creates a new HistogramVec and registers it with the collector. +func (c *PrometheusCollector) RegisterHistogramVec(name string, labels ...string) buxmetrics.HistogramVecInterface { + h := prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: name, + Help: "HistogramVec of " + name, + }, + labels, + ) + c.reg.MustRegister(h) + return &HistogramVecWrapper{h} +} + +// GaugeVecWrapper is a wrapper for prometheus.GaugeVec +type GaugeVecWrapper struct { + *prometheus.GaugeVec +} + +// WithLabelValues returns a Gauge with the given label values +func (g *GaugeVecWrapper) WithLabelValues(lvs ...string) buxmetrics.GaugeInterface { + return g.GaugeVec.WithLabelValues(lvs...) +} + +// HistogramVecWrapper is a wrapper for prometheus.HistogramVec +type HistogramVecWrapper struct { + *prometheus.HistogramVec +} + +// WithLabelValues returns a Histogram with the given label values +func (h *HistogramVecWrapper) WithLabelValues(lvs ...string) buxmetrics.HistogramInterface { + return h.HistogramVec.WithLabelValues(lvs...) +} diff --git a/metrics/global.go b/metrics/global.go new file mode 100644 index 000000000..a37372d44 --- /dev/null +++ b/metrics/global.go @@ -0,0 +1,18 @@ +package metrics + +import ( + buxmetrics "github.com/BuxOrg/bux/metrics" +) + +var metrics *Metrics + +// EnableMetrics will enable the metrics for the application +func EnableMetrics() buxmetrics.Collector { + metrics = newMetrics() + return NewPrometheusCollector(metrics.registerer) +} + +// Get will return the metrics if enabled +func Get() (m *Metrics, enabled bool) { + return metrics, metrics != nil +} diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 000000000..416df5ce8 --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,23 @@ +package metrics + +import ( + "github.com/prometheus/client_golang/prometheus" +) + +type Metrics struct { + gatherer prometheus.Gatherer + registerer prometheus.Registerer +} + +func newMetrics() *Metrics { + registry := prometheus.NewRegistry() + constLabels := prometheus.Labels{"app": appName} + registererWithLabels := prometheus.WrapRegistererWith(constLabels, registry) + + m := &Metrics{ + gatherer: registry, + registerer: registererWithLabels, + } + + return m +} diff --git a/metrics/naming.go b/metrics/naming.go new file mode 100644 index 000000000..7f33b99af --- /dev/null +++ b/metrics/naming.go @@ -0,0 +1,3 @@ +package metrics + +const appName = "bux-server" From 9d7359a09c6118382b15094aeaba9609d867d87b Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Mon, 12 Feb 2024 07:18:40 +0100 Subject: [PATCH 08/10] feat(BUX-566): adjust to bux metrics --- metrics/collector.go | 30 +++++------------------------- metrics/metrics.go | 7 +++++++ server/server.go | 4 ++++ 3 files changed, 16 insertions(+), 25 deletions(-) diff --git a/metrics/collector.go b/metrics/collector.go index db58af57a..f6bff1128 100644 --- a/metrics/collector.go +++ b/metrics/collector.go @@ -16,7 +16,7 @@ func NewPrometheusCollector(reg prometheus.Registerer) buxmetrics.Collector { } // RegisterGauge creates a new Gauge and registers it with the collector. -func (c *PrometheusCollector) RegisterGauge(name string) buxmetrics.GaugeInterface { +func (c *PrometheusCollector) RegisterGauge(name string) prometheus.Gauge { g := prometheus.NewGauge( prometheus.GaugeOpts{ Name: name, @@ -28,7 +28,7 @@ func (c *PrometheusCollector) RegisterGauge(name string) buxmetrics.GaugeInterfa } // RegisterGaugeVec creates a new GaugeVec and registers it with the collector. -func (c *PrometheusCollector) RegisterGaugeVec(name string, labels ...string) buxmetrics.GaugeVecInterface { +func (c *PrometheusCollector) RegisterGaugeVec(name string, labels ...string) *prometheus.GaugeVec { g := prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: name, @@ -37,11 +37,11 @@ func (c *PrometheusCollector) RegisterGaugeVec(name string, labels ...string) bu labels, ) c.reg.MustRegister(g) - return &GaugeVecWrapper{g} + return g } // RegisterHistogramVec creates a new HistogramVec and registers it with the collector. -func (c *PrometheusCollector) RegisterHistogramVec(name string, labels ...string) buxmetrics.HistogramVecInterface { +func (c *PrometheusCollector) RegisterHistogramVec(name string, labels ...string) *prometheus.HistogramVec { h := prometheus.NewHistogramVec( prometheus.HistogramOpts{ Name: name, @@ -50,25 +50,5 @@ func (c *PrometheusCollector) RegisterHistogramVec(name string, labels ...string labels, ) c.reg.MustRegister(h) - return &HistogramVecWrapper{h} -} - -// GaugeVecWrapper is a wrapper for prometheus.GaugeVec -type GaugeVecWrapper struct { - *prometheus.GaugeVec -} - -// WithLabelValues returns a Gauge with the given label values -func (g *GaugeVecWrapper) WithLabelValues(lvs ...string) buxmetrics.GaugeInterface { - return g.GaugeVec.WithLabelValues(lvs...) -} - -// HistogramVecWrapper is a wrapper for prometheus.HistogramVec -type HistogramVecWrapper struct { - *prometheus.HistogramVec -} - -// WithLabelValues returns a Histogram with the given label values -func (h *HistogramVecWrapper) WithLabelValues(lvs ...string) buxmetrics.HistogramInterface { - return h.HistogramVec.WithLabelValues(lvs...) + return h } diff --git a/metrics/metrics.go b/metrics/metrics.go index 416df5ce8..5d6123bcc 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -1,7 +1,10 @@ package metrics import ( + "net/http" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" ) type Metrics struct { @@ -21,3 +24,7 @@ func newMetrics() *Metrics { return m } + +func (m *Metrics) HttpHandler() http.Handler { + return promhttp.HandlerFor(metrics.gatherer, promhttp.HandlerOpts{Registry: metrics.registerer}) +} diff --git a/server/server.go b/server/server.go index 665aceffe..b23215322 100644 --- a/server/server.go +++ b/server/server.go @@ -16,6 +16,7 @@ import ( "github.com/BuxOrg/bux-server/actions/utxos" "github.com/BuxOrg/bux-server/actions/xpubs" "github.com/BuxOrg/bux-server/config" + "github.com/BuxOrg/bux-server/metrics" apirouter "github.com/mrz1836/go-api-router" "github.com/newrelic/go-agent/v3/integrations/nrhttprouter" httpSwagger "github.com/swaggo/http-swagger" @@ -89,6 +90,9 @@ func (s *Server) Handlers() *nrhttprouter.Router { s.Router = apirouter.NewWithNewRelic(s.Services.NewRelic) s.Router.HTTPRouter.Handler(http.MethodGet, "/swagger", http.RedirectHandler("/swagger/index.html", http.StatusMovedPermanently)) s.Router.HTTPRouter.Handler(http.MethodGet, "/swagger/*any", httpSwagger.WrapHandler) + if metrics, enabled := metrics.Get(); enabled { + s.Router.HTTPRouter.Handler(http.MethodGet, "/metrics", metrics.HttpHandler()) + } segment.End() // Turned on all CORs - should be able to access in a browser From 32853ba164c2d9ed6809ac16fb080159abc4ffb7 Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Mon, 12 Feb 2024 07:18:40 +0100 Subject: [PATCH 09/10] refactor(BUX-566): fix lint errors --- metrics/metrics.go | 5 ++++- server/server.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/metrics/metrics.go b/metrics/metrics.go index 5d6123bcc..8b5e4e7a5 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -7,11 +7,13 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" ) +// Metrics is the metrics collector type Metrics struct { gatherer prometheus.Gatherer registerer prometheus.Registerer } +// newMetrics will create a new Metrics object; this is private to ensure only one, global instance is created func newMetrics() *Metrics { registry := prometheus.NewRegistry() constLabels := prometheus.Labels{"app": appName} @@ -25,6 +27,7 @@ func newMetrics() *Metrics { return m } -func (m *Metrics) HttpHandler() http.Handler { +// HTTPHandler will return the http.Handler for the metrics +func (m *Metrics) HTTPHandler() http.Handler { return promhttp.HandlerFor(metrics.gatherer, promhttp.HandlerOpts{Registry: metrics.registerer}) } diff --git a/server/server.go b/server/server.go index b23215322..6524b284b 100644 --- a/server/server.go +++ b/server/server.go @@ -91,7 +91,7 @@ func (s *Server) Handlers() *nrhttprouter.Router { s.Router.HTTPRouter.Handler(http.MethodGet, "/swagger", http.RedirectHandler("/swagger/index.html", http.StatusMovedPermanently)) s.Router.HTTPRouter.Handler(http.MethodGet, "/swagger/*any", httpSwagger.WrapHandler) if metrics, enabled := metrics.Get(); enabled { - s.Router.HTTPRouter.Handler(http.MethodGet, "/metrics", metrics.HttpHandler()) + s.Router.HTTPRouter.Handler(http.MethodGet, "/metrics", metrics.HTTPHandler()) } segment.End() From a3b02f37fb298004e1bee2b06bc139980e3b61b4 Mon Sep 17 00:00:00 2001 From: Krzysztof Tomecki <152964795+chris-4chain@users.noreply.github.com> Date: Mon, 12 Feb 2024 09:42:12 +0100 Subject: [PATCH 10/10] chore(BUX-566): adjust a comment --- metrics/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metrics/metrics.go b/metrics/metrics.go index 8b5e4e7a5..a134171fb 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -13,7 +13,7 @@ type Metrics struct { registerer prometheus.Registerer } -// newMetrics will create a new Metrics object; this is private to ensure only one, global instance is created +// newMetrics is private to ensure that only one global-instance is created func newMetrics() *Metrics { registry := prometheus.NewRegistry() constLabels := prometheus.Labels{"app": appName}