Skip to content

Commit

Permalink
Log terms acceptance (#88)
Browse files Browse the repository at this point in the history
* add terms acceptance

* resolve comments

* resolve comments
  • Loading branch information
jeremy-babylonlabs authored Oct 26, 2024
1 parent 620f6b0 commit 1710e40
Show file tree
Hide file tree
Showing 17 changed files with 228 additions and 10 deletions.
4 changes: 3 additions & 1 deletion config/config-docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ assets:
ordinals:
host: "http://ord-poc.devnet.babylonchain.io"
port: 8888
timeout: 1000
timeout: 1000
terms_acceptance_logging:
enabled: true
4 changes: 3 additions & 1 deletion config/config-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ assets:
ordinals:
host: "http://ord-poc.devnet.babylonchain.io"
port: 8888
timeout: 5000
timeout: 5000
terms_acceptance_logging:
enabled: true
25 changes: 25 additions & 0 deletions internal/api/handlers/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handlers

import (
"context"
"encoding/json"
"fmt"
"net/http"

Expand Down Expand Up @@ -171,3 +172,27 @@ func parseStateFilterQuery(
}
return stateEnum, nil
}

// parseTermsAcceptanceLoggingRequest parses the terms acceptance request bdoy and returns the address and public key
func parseTermsAcceptanceLoggingRequest(request *http.Request, btcNetParam *chaincfg.Params) (string, string, *types.Error) {
var req TermsAcceptanceLoggingRequest
if err := json.NewDecoder(request.Body).Decode(&req); err != nil {
return "", "", types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "Invalid request payload")
}

// Validate the Bitcoin address
if _, err := utils.CheckBtcAddressType(req.Address, btcNetParam); err != nil {
return "", "", types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "Invalid Bitcoin address")
}

// Validate the public key
if _, err := utils.GetSchnorrPkFromHex(req.PublicKey); err != nil {
return "", "", types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "Invalid public key")
}

if req.Address == "" || req.PublicKey == "" {
return "", "", types.NewErrorWithMsg(http.StatusBadRequest, types.BadRequest, "Address and public key are required")
}

return req.Address, req.PublicKey, nil
}
29 changes: 29 additions & 0 deletions internal/api/handlers/terms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package handlers

import (
"net/http"

"github.com/babylonlabs-io/staking-api-service/internal/types"
)

type TermsAcceptanceLoggingRequest struct {
Address string `json:"address"`
PublicKey string `json:"public_key"`
}

type TermsAcceptancePublic struct {
Status bool `json:"status"`
}

func (h *Handler) LogTermsAcceptance(request *http.Request) (*Result, *types.Error) {
address, publicKey, err := parseTermsAcceptanceLoggingRequest(request, h.config.Server.BTCNetParam)
if err != nil {
return nil, err
}

if err := h.services.AcceptTerms(request.Context(), address, publicKey); err != nil {
return nil, err
}

return NewResult(TermsAcceptancePublic{Status: true}), nil
}
4 changes: 4 additions & 0 deletions internal/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ func (a *Server) SetupRoutes(r *chi.Mux) {
handlers := a.handlers
r.Get("/healthcheck", registerHandler(handlers.HealthCheck))

if a.cfg.TermsAcceptanceLogging.Enabled {
r.Post("/log_terms_acceptance", registerHandler(handlers.LogTermsAcceptance))
}

r.Get("/v1/staker/delegations", registerHandler(handlers.GetStakerDelegations))
r.Post("/v1/unbonding", registerHandler(handlers.UnbondDelegation))
r.Get("/v1/unbonding/eligibility", registerHandler(handlers.GetUnbondingEligibility))
Expand Down
11 changes: 6 additions & 5 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import (
)

type Config struct {
Server *ServerConfig `mapstructure:"server"`
Db *DbConfig `mapstructure:"db"`
Queue *queue.QueueConfig `mapstructure:"queue"`
Metrics *MetricsConfig `mapstructure:"metrics"`
Assets *AssetsConfig `mapstructure:"assets"`
Server *ServerConfig `mapstructure:"server"`
Db *DbConfig `mapstructure:"db"`
Queue *queue.QueueConfig `mapstructure:"queue"`
Metrics *MetricsConfig `mapstructure:"metrics"`
Assets *AssetsConfig `mapstructure:"assets"`
TermsAcceptanceLogging *TermsAcceptanceConfig `mapstructure:"terms_acceptance_logging"`
}

func (cfg *Config) Validate() error {
Expand Down
5 changes: 5 additions & 0 deletions internal/config/terms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package config

type TermsAcceptanceConfig struct {
Enabled bool `mapstructure:"enabled"`
}
2 changes: 2 additions & 0 deletions internal/db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ type DBClient interface {
ctx context.Context,
paginationToken string,
) (*DbResultMap[model.DelegationDocument], error)
// SaveTermsAcceptance saves the acceptance of the terms of service of the public key
SaveTermsAcceptance(ctx context.Context, termsAcceptance *model.TermsAcceptance) error
}

type DelegationFilter struct {
Expand Down
1 change: 1 addition & 0 deletions internal/db/model/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
BtcInfoCollection = "btc_info"
UnprocessableMsgCollection = "unprocessable_messages"
PkAddressMappingsCollection = "pk_address_mappings"
TermsAcceptanceCollection = "terms_acceptance"
)

type index struct {
Expand Down
11 changes: 11 additions & 0 deletions internal/db/model/terms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package model

import (
"go.mongodb.org/mongo-driver/bson/primitive"
)

type TermsAcceptance struct {
Id primitive.ObjectID `bson:"_id,omitempty"`
Address string `bson:"address"`
PublicKey string `bson:"public_key"`
}
14 changes: 14 additions & 0 deletions internal/db/terms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package db

import (
"context"

"github.com/babylonlabs-io/staking-api-service/internal/db/model"
)

func (db *Database) SaveTermsAcceptance(ctx context.Context, termsAcceptance *model.TermsAcceptance) error {
collection := db.Client.Database(db.DbName).Collection(model.TermsAcceptanceCollection)

_, err := collection.InsertOne(ctx, termsAcceptance)
return err
}
21 changes: 21 additions & 0 deletions internal/services/terms.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package services

import (
"context"

"github.com/babylonlabs-io/staking-api-service/internal/db/model"
"github.com/babylonlabs-io/staking-api-service/internal/types"
)

func (s *Services) AcceptTerms(ctx context.Context, address, publicKey string) *types.Error {
termsAcceptance := &model.TermsAcceptance{
Address: address,
PublicKey: publicKey,
}

if err := s.DbClient.SaveTermsAcceptance(ctx, termsAcceptance); err != nil {
return types.NewInternalServiceError(err)
}

return nil
}
4 changes: 3 additions & 1 deletion tests/config/config-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ assets:
ordinals:
host: "http://ord-poc.devnet.babylonchain.io"
port: 8888
timeout: 100
timeout: 100
terms_acceptance_logging:
enabled: true
66 changes: 66 additions & 0 deletions tests/integration_test/terms_acceptance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package tests

import (
"bytes"
"encoding/json"
"math/rand"
"net/http"
"testing"
"time"

"github.com/babylonlabs-io/staking-api-service/internal/api/handlers"
"github.com/babylonlabs-io/staking-api-service/tests/testutils"
"github.com/stretchr/testify/assert"
)

const (
termsAcceptancePath = "/log_terms_acceptance"
)

func TestTermsAcceptance(t *testing.T) {
testServer := setupTestServer(t, nil)
defer testServer.Close()

r := rand.New(rand.NewSource(time.Now().UnixNano()))
address, _ := testutils.RandomBtcAddress(r, testServer.Config.Server.BTCNetParam)
publicKey, _ := testutils.RandomPk()

// Prepare request body
requestBody := handlers.TermsAcceptanceLoggingRequest{
Address: address,
PublicKey: publicKey,
}
bodyBytes, _ := json.Marshal(requestBody)

url := testServer.Server.URL + termsAcceptancePath
resp, err := http.Post(url, "application/json", bytes.NewReader(bodyBytes))
assert.NoError(t, err, "making POST request to terms acceptance endpoint should not fail")
defer resp.Body.Close()

assert.Equal(t, http.StatusOK, resp.StatusCode, "expected HTTP 200 OK status")

var response handlers.PublicResponse[handlers.TermsAcceptancePublic]
err = json.NewDecoder(resp.Body).Decode(&response)
assert.NoError(t, err, "decoding response body should not fail")
assert.Equal(t, true, response.Data.Status)
}

func TestTermsAcceptanceInvalidAddress(t *testing.T) {
testServer := setupTestServer(t, nil)
defer testServer.Close()

// Use invalid address
invalidAddress := "invalidaddress"
publicKey, _ := testutils.RandomPk()

requestBody := handlers.TermsAcceptanceLoggingRequest{}
bodyBytes, _ := json.Marshal(requestBody)

url := testServer.Server.URL + termsAcceptancePath + "?address=" + invalidAddress + "&public_key=" + publicKey
resp, err := http.Post(url, "application/json", bytes.NewReader(bodyBytes))
assert.NoError(t, err, "making POST request to terms acceptance endpoint should not fail")
defer resp.Body.Close()

// Check response
assert.Equal(t, http.StatusBadRequest, resp.StatusCode, "expected HTTP 400 Bad Request status")
}
20 changes: 19 additions & 1 deletion tests/mocks/mock_db_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tests/mocks/mock_ordinal_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions tests/testutils/datagen.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/babylonlabs-io/staking-queue-client/client"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
Expand Down Expand Up @@ -140,6 +142,19 @@ func RandomBytes(r *rand.Rand, n uint64) ([]byte, string) {
return randomBytes, hex.EncodeToString(randomBytes)
}

func RandomBtcAddress(r *rand.Rand, params *chaincfg.Params) (string, error) {
privKey, err := btcec.NewPrivateKey()
if err != nil {
return "", err
}
pubKey := privKey.PubKey()
addr, err := btcutil.NewAddressTaproot(schnorr.SerializePubKey(pubKey), params)
if err != nil {
return "", err
}
return addr.EncodeAddress(), nil
}

// GenerateRandomTimestamp generates a random timestamp before the specified timestamp.
// If beforeTimestamp is 0, then the current time is used.
func GenerateRandomTimestamp(afterTimestamp, beforeTimestamp int64) int64 {
Expand Down

0 comments on commit 1710e40

Please sign in to comment.