-
Notifications
You must be signed in to change notification settings - Fork 501
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
services/horizon: Add a rate limit for path finding requests. (#4310)
Add a per second rate limit for path finding requests backed by https://pkg.go.dev/golang.org/x/time/rate
- Loading branch information
Showing
12 changed files
with
260 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package paths | ||
|
||
import ( | ||
"context" | ||
|
||
"golang.org/x/time/rate" | ||
|
||
"github.com/stellar/go/support/errors" | ||
"github.com/stellar/go/xdr" | ||
) | ||
|
||
var ( | ||
// ErrRateLimitExceeded indicates that the Finder is not able to fulfill the request due to rate limits. | ||
ErrRateLimitExceeded = errors.New("Rate limit exceeded") | ||
) | ||
|
||
// RateLimitedFinder is a Finder implementation which limits the number of path finding requests. | ||
type RateLimitedFinder struct { | ||
finder Finder | ||
limiter *rate.Limiter | ||
} | ||
|
||
// NewRateLimitedFinder constructs a new RateLimitedFinder which enforces a per | ||
// second limit on path finding requests. | ||
func NewRateLimitedFinder(finder Finder, limit uint) *RateLimitedFinder { | ||
return &RateLimitedFinder{ | ||
finder: finder, | ||
limiter: rate.NewLimiter(rate.Limit(limit), int(limit)), | ||
} | ||
} | ||
|
||
// Limit returns the per second limit of path finding requests. | ||
func (f *RateLimitedFinder) Limit() int { | ||
return f.limiter.Burst() | ||
} | ||
|
||
// Find implements the Finder interface and returns ErrRateLimitExceeded if the | ||
// RateLimitedFinder is unable to complete the request due to rate limits. | ||
func (f *RateLimitedFinder) Find(ctx context.Context, q Query, maxLength uint) ([]Path, uint32, error) { | ||
if !f.limiter.Allow() { | ||
return nil, 0, ErrRateLimitExceeded | ||
} | ||
return f.finder.Find(ctx, q, maxLength) | ||
} | ||
|
||
// FindFixedPaths implements the Finder interface and returns ErrRateLimitExceeded if the | ||
// RateLimitedFinder is unable to complete the request due to rate limits. | ||
func (f *RateLimitedFinder) FindFixedPaths( | ||
ctx context.Context, | ||
sourceAsset xdr.Asset, | ||
amountToSpend xdr.Int64, | ||
destinationAssets []xdr.Asset, | ||
maxLength uint, | ||
) ([]Path, uint32, error) { | ||
if !f.limiter.Allow() { | ||
return nil, 0, ErrRateLimitExceeded | ||
} | ||
return f.finder.FindFixedPaths(ctx, sourceAsset, amountToSpend, destinationAssets, maxLength) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package paths | ||
|
||
import ( | ||
"context" | ||
"strconv" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/stellar/go/xdr" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/mock" | ||
) | ||
|
||
func TestRateLimitedFinder(t *testing.T) { | ||
for _, limit := range []int{0, 1, 5} { | ||
t.Run("Limit of "+strconv.Itoa(limit), func(t *testing.T) { | ||
totalCalls := limit + 4 | ||
errorChan := make(chan error, totalCalls) | ||
find := func(finder Finder) { | ||
_, _, err := finder.Find(context.Background(), Query{}, 1) | ||
errorChan <- err | ||
} | ||
findFixedPaths := func(finder Finder) { | ||
_, _, err := finder.FindFixedPaths( | ||
context.Background(), | ||
xdr.MustNewNativeAsset(), | ||
10, | ||
nil, | ||
0, | ||
) | ||
errorChan <- err | ||
} | ||
|
||
wg := &sync.WaitGroup{} | ||
mockFinder := &MockFinder{} | ||
mockFinder.On("Find", mock.Anything, mock.Anything, mock.Anything). | ||
Return([]Path{}, uint32(0), nil).Maybe().Times(limit). | ||
Run(func(args mock.Arguments) { | ||
wg.Done() | ||
wg.Wait() | ||
}) | ||
mockFinder.On("FindFixedPaths", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). | ||
Return([]Path{}, uint32(0), nil).Maybe().Times(limit). | ||
Run(func(args mock.Arguments) { | ||
wg.Done() | ||
wg.Wait() | ||
}) | ||
|
||
for _, f := range []func(Finder){find, findFixedPaths} { | ||
wg.Add(totalCalls) | ||
rateLimitedFinder := NewRateLimitedFinder(mockFinder, uint(limit)) | ||
assert.Equal(t, limit, rateLimitedFinder.Limit()) | ||
for i := 0; i < totalCalls; i++ { | ||
go f(rateLimitedFinder) | ||
} | ||
|
||
requestsExceedingLimit := totalCalls - limit | ||
for i := 0; i < requestsExceedingLimit; i++ { | ||
err := <-errorChan | ||
assert.Equal(t, ErrRateLimitExceeded, err) | ||
} | ||
|
||
wg.Add(-requestsExceedingLimit) | ||
for i := 0; i < limit; i++ { | ||
assert.NoError(t, <-errorChan) | ||
} | ||
} | ||
mockFinder.AssertExpectations(t) | ||
}) | ||
} | ||
} |