Skip to content

Commit

Permalink
refactor handleInfo and handleClaim to work with an address and rollu…
Browse files Browse the repository at this point in the history
…pName. fix routing.
  • Loading branch information
steezeburger committed Jan 19, 2024
1 parent fa495fb commit 7da432a
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 171 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ password.txt

# binary
eth-faucet

# envs
.env
.env.local
24 changes: 10 additions & 14 deletions internal/server/dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,19 @@ type claimRequest struct {
}

type claimResponse struct {
Message string `json:"msg"`
Message string `json:"message"`
}

type infoResponse struct {
Payout string `json:"payout"`
FundingAddress string `json:"fundingAddress"`
NetworkID uint32 `json:"networkId"`
Payout string `json:"payout"`
RollupName string `json:"rollupName"`
}

type errorResponse struct {
Message string `json:"message"`
Status int `json:"status"`
}

type malformedRequest struct {
Expand Down Expand Up @@ -77,18 +85,6 @@ func decodeJSONBody(r *http.Request, dst interface{}) error {
return nil
}

func readAddress(r *http.Request) (string, error) {
var claimReq claimRequest
if err := decodeJSONBody(r, &claimReq); err != nil {
return "", err
}
if !chain.IsValidAddress(claimReq.Address, true) {
return "", &malformedRequest{status: http.StatusBadRequest, message: "invalid address"}
}

return claimReq.Address, nil
}

func readClaimRequest(r *http.Request) (claimRequest, error) {
var claimReq claimRequest
if err := decodeJSONBody(r, &claimReq); err != nil {
Expand Down
10 changes: 5 additions & 5 deletions internal/server/limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func NewLimiter(proxyCount int, ttl time.Duration) *Limiter {
}

func (l *Limiter) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
address, err := readAddress(r)
claimRequest, err := readClaimRequest(r)
if err != nil {
var mr *malformedRequest
if errors.As(err, &mr) {
Expand All @@ -50,22 +50,22 @@ func (l *Limiter) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.Ha

clintIP := getClientIPFromRequest(l.proxyCount, r)
l.mutex.Lock()
if l.limitByKey(w, address) || l.limitByKey(w, clintIP) {
if l.limitByKey(w, claimRequest.Address) || l.limitByKey(w, clintIP) {
l.mutex.Unlock()
return
}
l.cache.SetWithTTL(address, true, l.ttl)
l.cache.SetWithTTL(claimRequest.Address, true, l.ttl)
l.cache.SetWithTTL(clintIP, true, l.ttl)
l.mutex.Unlock()

next.ServeHTTP(w, r)
if w.(negroni.ResponseWriter).Status() != http.StatusOK {
l.cache.Remove(address)
l.cache.Remove(claimRequest.Address)
l.cache.Remove(clintIP)
return
}
log.WithFields(log.Fields{
"address": address,
"address": claimRequest.Address,
"clientIP": clintIP,
}).Info("Maximum request limit has been reached")
}
Expand Down
75 changes: 50 additions & 25 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,25 @@ import (
type Server struct {
mutex trylock.Mutex
cfg *Config
queue chan string
queue chan claimRequest
sm store.RollupStoreManager
}

func NewServer(sm store.RollupStoreManager, cfg *Config) *Server {
return &Server{
cfg: cfg,
queue: make(chan string, cfg.queueCap),
queue: make(chan claimRequest, cfg.queueCap),
sm: sm,
}
}

func (s *Server) setupRouter() *mux.Router {
r := mux.NewRouter()
r := mux.NewRouter().StrictSlash(true)

api := r.PathPrefix("/api").Subrouter()
limiter := NewLimiter(s.cfg.proxyCount, time.Duration(s.cfg.interval)*time.Minute)
api.Handle("/claim", negroni.New(limiter, negroni.Wrap(s.handleClaim())))
api.Handle("/info", s.handleInfo())
api.Handle("/claim", negroni.New(limiter, negroni.Wrap(s.handleClaim()))).Methods("POST")
api.Handle("/info/{rollupName}", s.handleInfo()).Methods("GET")

fs := http.FileServer(web.Dist())

Expand Down Expand Up @@ -81,45 +81,39 @@ func (s *Server) consumeQueue() {
s.mutex.Lock()
defer s.mutex.Unlock()
for len(s.queue) != 0 {
address := <-s.queue
claimRequest := <-s.queue

// TODO - need to store the rollup name with the address
name := "bugbug-fake-rollup-name"
txBuilder, err := s.txBuilderFromRollupName(name)
txBuilder, err := s.txBuilderFromRollupName(claimRequest.RollupName)
if err != nil {
log.WithError(err).Error("Failed to create transaction builder while processing claim in queue")
}

txHash, err := txBuilder.Transfer(context.Background(), address, chain.EtherToWei(int64(s.cfg.payout)))
txHash, err := txBuilder.Transfer(context.Background(), claimRequest.Address, chain.EtherToWei(int64(s.cfg.payout)))
if err != nil {
log.WithError(err).Error("Failed to handle transaction in the queue")
} else {
log.WithFields(log.Fields{
"txHash": txHash,
"address": address,
"address": claimRequest.Address,
}).Info("Consume from queue successfully")
}
}
}

func (s *Server) handleClaim() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.NotFound(w, r)
return
}

// The error always be nil since it has already been handled in limiter
address, _ := readAddress(r)
claimRequest, _ := readClaimRequest(r)
// Try to lock mutex if the work queue is empty
if len(s.queue) != 0 || !s.mutex.TryLock() {
// TODO - need to store the rollup name with the address
select {
case s.queue <- address:
case s.queue <- claimRequest:
log.WithFields(log.Fields{
"address": address,
"address": claimRequest.Address,
"rollupName": claimRequest.RollupName,
}).Info("Added to queue successfully")
resp := claimResponse{Message: fmt.Sprintf("Added %s to the queue", address)}
resp := claimResponse{Message: fmt.Sprintf("Added %s to the queue", claimRequest.Address)}
_ = renderJSON(w, resp, http.StatusOK)
default:
log.Warn("Max queue capacity reached")
Expand All @@ -138,7 +132,7 @@ func (s *Server) handleClaim() http.HandlerFunc {
return
}

txHash, err := txBuilder.Transfer(ctx, address, chain.EtherToWei(int64(s.cfg.payout)))
txHash, err := txBuilder.Transfer(ctx, claimRequest.Address, chain.EtherToWei(int64(s.cfg.payout)))
s.mutex.Unlock()
if err != nil {
log.WithError(err).Error("Failed to send transaction")
Expand All @@ -148,22 +142,47 @@ func (s *Server) handleClaim() http.HandlerFunc {

log.WithFields(log.Fields{
"txHash": txHash,
"address": address,
"address": claimRequest.Address,
}).Info("Funded directly successfully")
resp := claimResponse{Message: fmt.Sprintf("Txhash: %s", txHash)}
_ = renderJSON(w, resp, http.StatusOK)
}
}

// handleInfo returns some details of the rollup.
// It fetches data from Firestore and returns it as JSON.
func (s *Server) handleInfo() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.NotFound(w, r)
vars := mux.Vars(r)
rollupName := vars["rollupName"]

isValid := store.IsRollupNameValid(rollupName)
if !isValid {
msg := fmt.Sprintf("Invalid rollup name: %v", rollupName)
log.Warn(msg)
_ = renderJSON(w, errorResponse{Message: msg, Status: http.StatusBadRequest}, http.StatusBadRequest)
return
}

rDoc, err := s.sm.FindRollupByName(rollupName)
if err != nil {
log.WithError(err).Warn("Failed to find rollup by name")
_ = renderJSON(w, errorResponse{Message: err.Error(), Status: http.StatusInternalServerError}, http.StatusInternalServerError)
return
}

if rDoc.Status != store.StatusDeployed {
msg := "Rollup is not deployed"
log.Warnf("%v: %v", msg, rDoc)
_ = renderJSON(w, errorResponse{Message: "msg", Status: http.StatusInternalServerError}, http.StatusInternalServerError)
return
}

_ = renderJSON(w, infoResponse{
Payout: strconv.Itoa(s.cfg.payout),
Payout: strconv.Itoa(s.cfg.payout),
FundingAddress: rDoc.RollupAccountAddress,
RollupName: rDoc.Name,
NetworkID: rDoc.NetworkID,
}, http.StatusOK)
}
}
Expand All @@ -175,13 +194,18 @@ func (s *Server) txBuilderFromRequest(r *http.Request) (chain.TxBuilder, error)
return nil, err
}

log.WithFields(log.Fields{
"address": claimRequest.Address,
"rollupName": claimRequest.RollupName,
}).Info("Received claim request")
return s.txBuilderFromRollupName(claimRequest.RollupName)
}

// txBuilderFromRollupName creates and returns a TxBuilder from the given name
func (s *Server) txBuilderFromRollupName(name string) (chain.TxBuilder, error) {
rollup, err := s.sm.FindRollupByName(name)
if err != nil {
err = fmt.Errorf("failed to find rollup by name: %w", err)
return nil, err
}

Expand All @@ -192,6 +216,7 @@ func (s *Server) txBuilderFromRollupName(name string) (chain.TxBuilder, error) {

privKey, err := crypto.HexToECDSA(hexkey)
if err != nil {
err = fmt.Errorf("failed to parse private key: %w", err)
return nil, err
}

Expand Down
38 changes: 30 additions & 8 deletions internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package store
import (
"context"
"errors"
"regexp"

"cloud.google.com/go/firestore"
"google.golang.org/api/iterator"
Expand Down Expand Up @@ -55,10 +56,14 @@ const (
)

type RollupDoc struct {
Name string `firestore:"name"`
NetworkID uint32 `firestore:"networkId"`
Status RollupDocumentStatus `firestore:"status"`
PrivateDetails RollupPrivateDoc `firestore:"private"`
ID string `firestore:"id"`
Name string `firestore:"name"`
NetworkID uint32 `firestore:"networkId"`
Status RollupDocumentStatus `firestore:"status"`

RollupPublicDetails
// FIXME - private isn't a field on the doc but is another collection, so this won't work
PrivateDetails RollupPrivateDoc `firestore:"private"`
}

type RollupPrivateDoc struct {
Expand All @@ -77,21 +82,38 @@ type RollupPublicDetails struct {
func (m *Manager) FindRollupByName(name string) (RollupDoc, error) {
ctx := context.Background()

iter := m.client.Collection(m.rollupsCollection).Where("name", "==", name).Documents(ctx)
iter := m.client.CollectionGroup(m.rollupsCollection).Where("name", "==", name).Documents(ctx)
defer iter.Stop()
var rollup RollupDoc
for {
doc, err := iter.Next()
if errors.Is(err, iterator.Done) {
break
return RollupDoc{}, errors.New("rollup not found")
}
if err != nil {
return RollupDoc{}, err
}

var rollup RollupDoc
err = doc.DataTo(&rollup)
if err != nil {
return RollupDoc{}, err
}
if rollup.Name != "" {
rollup.ID = doc.Ref.ID
return rollup, nil
}
}
}

// IsRollupNameValid checks against a regex to ensure the rollup name is valid
func IsRollupNameValid(name string) bool {
pattern := "^[a-z]+[a-z0-9]*(?:-[a-z0-9]+)*$"
matched, err := regexp.MatchString(pattern, name)
if err != nil {
return false
}
if !matched {
return false
}
return rollup, nil
return true
}
4 changes: 3 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
default:
@just --list

set dotenv-filename := ".env.local"

# installs developer dependencies for MacOS users with `brew`
brew-install-dev-deps:
brew install just
Expand All @@ -15,7 +17,7 @@ web-install-deps:
# runs the full web app. generates the front end app before starting the server.
run:
go generate -x
go run -v ./... -httpport 8080
go run -v ./... -httpport 8080 -firestoreprojectid $FIRESTORE_PROJECT_ID

# run cli and restart when code changes
run-watch:
Expand Down
Loading

0 comments on commit 7da432a

Please sign in to comment.